• 封装、隐藏和property装饰器



    封装

    封装指的是将类中的多个属性或方法封装成一个接口,这样当我们调用这一个接口的时候,不需要去考虑该接口背后进行了什么操作,只需要知道该接口提供了什么功能即可。封装的主要优点是隔离复杂度。

    # 定义一个相机类,每次照相都需要进行调整光圈、延时和按快门
    class Camera:
        def set_aperture(self):
            print('调光圈')
    
        def set_delayed(self):
            print('调延时')
    
        def shutter(self):
            print('按快门')
    
    camera = Camera()
    
    camera.set_aperture()
    camera.set_delayed()
    camera.shutter()
    

    而傻瓜式相机只需要按快门即可拍照,不用考虑光圈、延时。

    class Camera:
        def set_aperture(self):
            print('调光圈')
    
        def set_delayed(self):
            print('调延时')
    
        def shutter(self):
            print('按快门')
    
        def take_photo(self):
            self.set_aperture()
            self.set_delayed()
            self.shutter()
    
    camera =Camera()
    
    camera.take_photo()   # 这样使用者无需考虑内部做了什么操作,仅调用接口即可。
    

    但封装后的属性与方法还是能直接被使用者以对象.属性的方式调用,如果我们仅想让使用者仅能使用我们提供的功能,那么我们可以将不想暴露给使用者的属性隐藏起来(设置成私有的)。


    隐藏属性

    Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),封装的属性可以直接在内部使用,而不能被外部直接使用,但其实这仅仅只是一种变形操作,类中所有双下划线开头的属性都会再类定义阶段、检测语法时自动变成_类名__属性名的形式。

    隐藏方法:作用1,隔离复杂度。

    class Camera:
        __money = 2000  # 变形为_Camera__money
        def __set_aperture(self):
            print('调光圈')
    
        def __set_delayed(self):  # 变形为 _Camera__set_delayed
            print('调延时')
    
        def __shutter(self):
            print('按快门')
    
        def take_photo(self):  # 定义函数时,会检测函数语法,所以__开头的属性也会变形.
            self.__set_aperture() 
            self.__set_delayed()
            self.__shutter()  # 变形为self._Camera__shutter
    
    
    camera =Camera()
    camera.take_photo()
    
    camera.__shutter() # 保错 AttributeError: 'Camera' object has no attribute '__shutter'
    

    作用2:在继承中,如果父类不想让子类覆盖自己的方法,可以将方法定义为私有的。

    class A:
        def __f1(self):  # __f1()已经变形为_A__f1
            print('A.__f1')
    
        def test(self):
            self.__f1()  # 这里的__f1也变形为_A__f1,相当于调用" 对象._A__f1 ",会先从b对象自身找,再从B类找,没有则从A类中找。
    
    class B(A):
        def __f1(self):  # __f1变形为_B__f1
            print("B.f1")
    
        def __f2(self):
            print('B.f2')
    
    b = B()
    b.test()
    
    A.__f1
    

    隐藏属性:隐藏起来后对外提供操作该数据的接口,然后开发者就可以在接口附近加上对该数据操作的限制,以此来完成对数据属性操作的严格控制,保证数据安全。

    class Coder:
        def __init__(self,name):
            if name.startswith('a'):
                print('不能以a开头')
            else:
                self.__NAME = name
        def info(self):
            return self.__NAME
    
    x = Coder('abc')
    不能以a开头
    

    这种变形需要注意的问题是:

    1、在类外部无法直接访问双下划线开头的属性,但知道了类名和属性名就可以拼出名称:_类名__属性,然后就可以访问了,所以隐藏并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。

    print(Camera.__dict__)
    
    {......'_Camera__money': 2000, '_Camera__set_aperture': <function Camera.__set_aperture at 
     0x0000000002778B80>, '_Camera__set_delayed': <function Camera.__set_delayed at 0x0000000002778C10>, 
     '_Camera__shutter': <function Camera.__shutter at 0x0000000002778CA0>, 'take_photo': <function 
     Camera.take_photo at 0x0000000002778D30>,......}
    

    2、在类内部是可以直接访问双下划线开头的属性的,因为在类定义阶段类内部的双下划线开头的属性统一发生了变形。

    camera._Camera__shutter()  # 这种方式也能访问到.
    按快门
    

    3、变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。

    Camera.__color = 'blue'
    print(Camera.__dict__)
    
    {.......'__color': 'blue'}
    

    隐藏属性与开放接口本质就是为了明确地区分内外,类内部可以修改封装内的东西而不影响外部调用者的代码;而类外部只需拿到一个接口,只要接口名和参数不变,那么无论设计者如何改变内部实现代码,使用者均无需改变代码。这就提供一个良好的合作基础,只要接口这个基础约定不变,则代码的修改不足为虑。

    Python并不会真的阻止访问私有的属性,模块也遵循这种约定,如果模块内的成员以单下划线开头,那么from module import *时不能被导入,但是不使用*的方式直接指定成员名也是可以导入的,这些以单下划线开头的都是私有的,原则上是供内部调用,作为外部使用者其实也能强行调用。

    注意:封装是将多个属性或方法封装成一个方法或称接口,使用者调用这一个接口即可使用这些被封装的属性。隐藏属性是将开发者不希望暴露给使用者的属性给隐藏住。二者虽然经常搭配使用,但要区分清楚。


    property

    对于某些方法,它们所返回的结果更像是对象的属性,而非调用功能。比如拿到圆的半径,调用计算面积的方法得到面积结果,面积更像是圆的属性,而非功能。为此Python专门提供了一个装饰器函数property,可以将类中的函数 “ 伪装成 ” 对象的数据属性,对象在访问该特殊属性时,会触发功能的运行,然后将返回值作为本次访问的结果,例如

    class Circular:
        def __init__(self, radius):
            self.radius = radius
    
        @property
        def area(self):
            from math import pi
            return pi * self.radius ** 2
    
    c1 = Circular(10)
    print(c1.area)
    
    314.1592653589793
    

    使用property有效地保证了属性访问的一致性。另外面向对象的封装有三种方式:

    【public】
    这种其实就是不封装,是对外公开的
    【protected】
    这种方式不对外公开,但对子类或者朋友(friend)公开
    【private】
    这种封装对谁都不公开
    

    python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现:

    class Person:
        def __init__(self,name):
            self.__NAME = name
    
        @property
        def name(self):
            return self.__NAME  # obj.name 访问的是self.name,这也是真实值.
    
        @name.getter
        def name(self):
            return self.__NAME, '哈哈'  # getter就是obj.name 时返回的值,会覆盖掉property返回的值.
    
        @name.setter
        def name(self, value):
            if not (type(value) is str):  # 在设定值之前进行类型检查.
                print(f'name must be str')
            self.__NAME = value  # 通过检查后,才会将值value放到真实的位置self.name
    
        @name.deleter
        def name(self):
            del self.__NAME
    
    lisi = Person('lisi')
    
    lisi.name = 11   # 在修改name属性时,会调用@name.setter装饰后的name方法
    # name must be str
    
    print(lisi.name)   # 在查看name属性时,会调用@name.getter装饰后的方法,如果没有getter则会调用property装饰后的方法.
    # (11, '哈哈')
    
    del lisi.name  # 会调用@name.deleter装饰后的name方法.
    print(lisi.name)
    # AttributeError: 'Person' object has no attribute '_Person__NAME'
    

    必须有property装饰后,才能使用getter、setter和deleter,如果有getter那么就不会调用property。

    隐藏属性隐藏的是类内的属性,对象也可以定义与属性同名的属性,与property的setter配合使用,就可以限制住对象定义的同名属性。


  • 相关阅读:
    为什么使用内部类?怎样使用内部类? 2016年12月15号
    java内部类 2016年12月13号
    接口与抽象类的区别与联系 2016年12月13日
    多态的向上转型和向下转型 2016.12.8
    构造器的调用顺序 2016.12.8
    static final 和final的区别 2016.12.07
    根据进程号查询占用资源多的线程
    Intellij idea启动项目提示"ClassNotFoundException"
    IntelliJ IDEA setup JDK无效
    (转)面试合集
  • 原文地址:https://www.cnblogs.com/ChiRou/p/14206697.html
Copyright © 2020-2023  润新知