Loading

封装、隐藏和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配合使用,就可以限制住对象定义的同名属性。


posted @ 2020-12-29 15:09  吃了好多肉  阅读(85)  评论(0编辑  收藏  举报