《Python学习笔记本》第七章 类 笔记以及摘要(完结)
其实书的作者对于Python底层的研究真的很厉害,虽然书中有几处小错误,但不影响整本书的质量。
本博客主要对于书中个人认为比较重要的内容或者比较有意思的内容进行标记。
定义
类与函数类似,类也是一种小粒度复用单位,但其行为特征更为复杂。
函数像机械加工,着重于处理过程,类则关注与数据本身,使其"活过来"
作为一种符合结构,类与模块有相似之处。但不同之处在于
类可生成多个实例
类可被继承和扩展
类实例的生命周期可控
类支持远算符,可按需重载
这些是模块没有或不要的。
建议将类和业务逻辑分离。
In [110]: class A: ...: class_name = 'A' ...: def __init__(self,name): ...: self.name = name ...: In [111]: A.__dict__ Out[111]: mappingproxy({'__module__': '__main__', 'class_name': 'A', '__init__': <function __main__.A.__init__(self, name)>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}) In [112]: a= A('lalla') In [113]: a.__dict__ Out[113]: {'name': 'lalla'} In [114]:
类型的名字空间返回mappingproxy只读视图,不允许直接修改。
实例的名字空间是普通字典,可直接修改
函数dir所有所有可访问成员的名字,vars直接返回实例的__dict__属性。
当通过实例或者类访问莫个成员时,会从当前对象开始,依次由近到远向祖先类查找。如此做的好处,就是祖先类的新增功能可直接"广播"给所有后代。
class A:
name = 12
print('class_local===> ', locals())
def test(self):
print("method===>", locals())
print(A.name)
print(name)
if __name__ == '__main__':
demo = A()
demo.test()
/usr/local/bin/python3.7 /Users/shijianzhong/study/Python学习笔记/第七章类/7_1.py Traceback (most recent call last): File "/Users/shijianzhong/study/Python学习笔记/第七章类/7_1.py", line 14, in <module> demo.test() File "/Users/shijianzhong/study/Python学习笔记/第七章类/7_1.py", line 9, in test print(name) NameError: name 'name' is not defined class_local===> {'__module__': '__main__', '__qualname__': 'A', 'name': 12} method===> {'self': <__main__.A object at 0x10634a290>} 12
书中讲过class在内部以函数方式执行,接收类型名字空间字典作为堆栈帧执行的名字空间。这样的话,成员定义作用域内的locals实际指向了class.__dict__(可读写版本)
但从语法上来说,class是类型定义,而非函数定义,这与内部执行方式无关。因此,class不构成E/E-Enclosing function locals;外部嵌套函数的名字空间(例如closure)作用域
字段
类型字段在class语句块内直接定义,而实例字段(属性)必须通过实例引用(self)赋值定义
字段赋值
可使用赋值语句为类或者实例添加新的字段
可一旦以子类或实例重新赋值,就将会在其名字空间建立同名字段,并会遮币原字段。
这与"赋值总是在当前空间建立关联"规则一致
删除操作仅针对当前名字空间,而不会按搜索顺序查找类型或基类。
成员(属性)访问总是按搜索规则进行,一个实例首先查找自身的属性,没有找类的,再没有根据继承方式,找父类的。
私有字段
私有字段就是变量名前面有__双下划线,后面没有双下划线。
所谓重命名,就是被编译器附加了类型名称前缀_类名。但这种做法不能真正的防止用户访问。流畅的Python书中说到,一般__双下划线的私有变量用的比较少,单下划线就够用了。
class X_Demo: __table = 'user' def __init__(self, name): self.__name = name def get_name(self): return self.__name if __name__ == '__main__': x = X_Demo('sidian') print(x.__dict__) print(X_Demo.__dict__) print(x._X_Demo__name)
{'_X_Demo__name': 'sidian'} {'__module__': '__main__', '_X_Demo__table': 'user', '__init__': <function X_Demo.__init__ at 0x10afa6680>, 'get_name': <function X_Demo.get_name at 0x10afa6560>, '__dict__': <attribute '__dict__' of 'X_Demo' objects>, '__weakref__': <attribute '__weakref__' of 'X_Demo' objects>, '__doc__': None} sidian
如果给类属性定义了__的私有变量,那么继承的类或者实例就无法直接读取到该属性。
class A1: __name = 'user' print(locals()) class B1(A1): print(A1.__name) if __name__ == '__main__': b1 = B1()
/usr/local/bin/python3.7 /Users/shijianzhong/study/Python学习笔记/第七章类/7_1.py {'__module__': '__main__', '__qualname__': 'A1', '_A1__name': 'user'} Traceback (most recent call last): File "/Users/shijianzhong/study/Python学习笔记/第七章类/7_1.py", line 25, in <module> class B1(A1): File "/Users/shijianzhong/study/Python学习笔记/第七章类/7_1.py", line 26, in B1 print(A1.__name) AttributeError: type object 'A1' has no attribute '_B1__name'
子类的都无法读取到父类的属性,那实例更加不可能读取到,实例需要通过自身的类去读取
所以在模块中,类属性少用__,继承容易出问题
相关函数
hansattr 按搜索规则查找整个继承层数,搜索对象是否拥有该可访问属性
setattr 设置属性
delattr 删除属性
属性
对私有字段可以通过下划线变量名保护,对于公开字段可以通过property(属性)保护
对过在方法的上面添加装饰器@property
也可以通过xx = property(fget=方法名,fset=, del=)
属性的优先级高于同名实例字段,因为属性属于数据型描述符,定义了__set__
方法
实例方法,类方法,静态方法,这个不介绍了
重点是它们都保存在类型名字空间中,所以不能重名。装饰器和参数的差异,并不能改变在同一个名字空间字典中
对同一key重复赋值会覆盖前一次赋值的现实。
绑定
class X2: def test(self): ... if __name__ == '__main__': x2 = X2() print(x2.test.__self__) print(x2.test.__func__) print(X2.test)
<__main__.X2 object at 0x105519d10> <function X2.test at 0x10550cb90> <function X2.test at 0x10550cb90>
这里面书中讲的比较少,其实主要还是描述符在起作用,__get__,当读取到该方法的时候,会返回__get__返回的数据。
所以当实例调用方法可以分为两步进行
执行了 x.test.__get__,非数据描述符。
第一步将函数包装成方法返回m = x.test.__get__(x, X)
第二步用类去执行函数X.test(m.__self__, 123)
我们执行该方法的时候,其实是执行的第二步。
几个简单的特殊方法记录下
__new__:构造方法,创建对象实例
__init__:初始化方法,设置实例的相关属性
__del__:析构方法,实例被回收时被调用。
继承
面向对象有三个基本特性:封装、继承和多态
封装将就结构复用,逻辑内敛,以固定接口对外提供服务。其遵循单一职责,规定每个类型仅有一个应发变化的原因。多于一个的耦合设计导致脆弱类型,任何职责变更都可能引发连带变故。
单一封装的核心时解耦和内聚,这让设计更简单、清晰、代码更易测试和冻结,避免了不确定性。
继承并非要复原原有类型,而是一种增量进化,在遵循原有设计和不改变既有代码的前提下,添加新功能,或改进算法。
其对应开闭原则,对扩展开放,对修改关闭。
继承要分清楚时功能扩展,还是使用某些功能。如果仅仅时为了使用某些功能,应该用组合来代替。
统一类型
__base__,__subclassses__,还有一个新学的魔法方法__init_subclass__,
class A: # 当所有子类继承该类时候,会直接执行,可以对继承的类进行一些拦截,操作。 def __init_subclass__(cls, **kwargs): cls.name = 'sidian' print(cls, kwargs) class B: ... class C(A, B,info='I am C'): ... if __name__ == "__main__": print(A.__subclasses__()) print(C.__base__) print(C.__bases__) print(vars(C))
/usr/local/bin/python3.7 /Users/shijianzhong/study/Python学习笔记/第七章类/7_5.py <class '__main__.C'> {'info': 'I am C'} [<class '__main__.C'>] <class '__main__.A'> (<class '__main__.A'>, <class '__main__.B'>) {'__module__': '__main__', '__doc__': None, 'name': 'sidian'}
初始化
初始化__init__是可选的
如果子类没有新的构造参数,或者新的初始化逻辑,那么没比哟创建该方法。因为按照搜索顺序,解释器会找到基类的初始化方法并执行。
也因为同样的缘故,我们须在子类的初始化方法中显式调用基类方法。
覆盖
继承覆盖方法,不应该改变方法参数列表和返回值类型,须确保不影响原有调用代码和相关文档。
class A5: def m(self): print('A.m') def do(self): self.m() class B5(A5): # 如果改变了m的参数,直接死给你看 def m(self): print('B.m') if __name__ == "__main__": b5= B5() b5.do()
多继承
多继承允许由多个继承体系,这是曾被疯狂追捧,后来被嫌弃的语言特点。从有点上说,它提供了一种混入(mixin)机制,让既有体系的类型轻松扩展储其他体系的功能。
但另一方面,它却会导致严重的混乱。
比较恶心的是Python可以修改基类__bases__的属性,修改子类的继承方式以及继承顺序。那是相当的没节操
class A6: def a(self):... class B6: def b(self):... class C6: def c(self):... class DD(A6, B6): ... print([i for i in dir(DD) if not i.startswith('_')]) print(DD.__bases__) # 直接修改一个类继承的父类,就是这么直接,这么干 DD.__bases__ = (C6, B6, A6) print([i for i in dir(DD) if not i.startswith('_')])
['a', 'b'] (<class '__main__.A6'>, <class '__main__.B6'>) ['a', 'b', 'c']
__mro__查找继承的所有类(菱形继承),专业的说法用了一套C3算法
步骤如下:
1 按"深度优先,从左到右"顺序获取类型表
2. 移除列表中的重复元素,仅保留最后一个
3. 确保子类总在基类前,并保留错继承定义顺序。
在任何时候,都应避免多条继承线存在交叉的现象,并竭力空值多继承和继承深度。这有助于梳理起相互关系,为代码可读和可维护性提供保障。
super 这本书上我学到的大招
该函数返回基类型代理,完成对基类成员的委托访问。
这里由两个参数,第二参数对象提供__mro__列表(如果是实例,提供其类的__mro__列表,实例没有__mro__属性),第一参数则指定起点。
函数总是返回起点为止后一类型(注意是起点位置后一类型)。
这要求第二参数必须是第一参数的实例或子类型,否则第一参数就不会出现在列表中。
同时,第二餐书还是实例和类型方法所需绑定的对象。
每一个字都很重要。
def super(t, o ): mro = getattr(o, "__mro__", type(o).__mro__) index = mro.index(t) + 1 return mro[index]
上面些的算法伪码,只能返回当前的父类,但不能成为绑定的实例和类型方法所需的绑定对象。
如果使用绑定的方法,需要显示传参
In [137]: class A: ...: @classmethod ...: def demo(cls):... ...: def test(self):print('A') ...: ...: class B(A): ...: def test(self):print('B') ...: ...: class C(B): ...: def test(self):print('C') ...:
# 这里直接返回的是第一个参数的上一级类的方法。 In [138]: super(C,C).test Out[138]: <function __main__.B.test(self)> In [139]: super(B,C).test Out[139]: <function __main__.A.test(self)> In [140]: In [140]: o = C() # super第二参数为实例在__self__属性就是自身 In [141]: super(C,o).__self__ == o Out[141]: True # 通过该函数可以调用父类的方法 In [142]: super(C,o).test Out[142]: <bound method B.test of <__main__.C object at 0x1073f42d0>> # 也可以调用父类的类方法 In [143]: super(C,C).demo Out[143]: <bound method A.demo of <class '__main__.C'>> In [144]:
上面比较详细的演示了
不建议直接使用__class__.__base__访问基类。还是把书中错误的例子抄一下
In [144]: class A: ...: def test(self): ...: print('A.test') ...: ...: class B(A): ...: def test(self): ...: print('B.test')
# 当这个是本层级实例调用是没事情,但子类调用就死循环了,因为子列的selfself.__class__.__base__就是B,那就是B.test一直调用自己,形成了递归 ...: self.__class__.__base__.test(self) ...: ...: class C(B): ...: ... ...: In [145]: b=B() In [146]: b.test() B.test A.test In [147]: c= C() In [148]: c.test() B.test B.test
递归
抽象类
这一节不上代码,就上文字,理解了文字应该也能理解代码
抽象类表示部分完成,且不能被实例化的类型
作为一种设计方式,抽象类可用来分离主题框架和局部实现,或将公用和定制解耦。不同与接口纯粹的调用声明,抽象类属于继承树的组成部分,其允许有实现代码。
从抽象类继承,必须实现所有层级为被实现的抽象方法,否则无法创建实例。
定义抽象类,必须继承ABC或者ABCMeta
from abc import ABC, ABCMeta, abstractmethod class Store(metaclass=ABCMeta): @abstractmethod def change(self):...
抽象方法上定义@abstractmethod
如果从抽象类继承,未实现全部方法,或添加新的抽象定义,那么该类型依旧是抽象类。另外抽象装饰器还可以用于属性、类型和静态方法。
抽象装饰器写靠近被装饰函数的这一层
既便是抽象类型方法,依然需要实现,否则无法创建实例。
开放类
将已绑定的方法添加到实例名字空间,只能算是字段赋值,算不上添加方法。而将一个函数添加到实例,即便手工设定__self__也无法构成绑定。(types.MethodType
,可以给实例添加方法。)
In [1]: class X:... In [2]: o = X() In [3]: o.test = lambda self:None In [4]: o.test.__self__ = o In [5]: o.test Out[5]: <function __main__.<lambda>(self)> In [6]: o.test() --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-6-365a749fed7a> in <module> ----> 1 o.test() TypeError: <lambda>() missing 1 required positional argument: 'self' In [7]: from types import MethodType In [12]: o.test = MethodType(lambda self:None, o) In [13]: o.test() In [14]: o.test Out[14]: <bound method <lambda> of <__main__.X object at 0x10d1c7c10>> In [15]:
其实书中已经给函数都赋值属性__self__但还是没用,如果一定要用,可以通过Types.MethodType函数
下面是给类添加字段属性,收益与动态成员查找方式,新增的方法对已创建的实例同样有效。也就是所谓的猴子补丁
In [15]: class X:... In [16]: o = X() In [17]: X.a = lambda self:print(f'instance method') In [18]: X.b = classmethod(lambda cls:print('class methd')) In [19]: X.c = staticmethod(lambda :print('staticmethod')) In [20]: o.a Out[20]: <bound method <lambda> of <__main__.X object at 0x10d115810>> In [21]: o.a() instance method In [22]: o.b Out[22]: <bound method <lambda> of <class '__main__.X'>> In [23]: o.b() class methd In [24]: o.c Out[24]: <function __main__.<lambda>()> In [25]: o.c() staticmethod
这很好的解释了猴子补丁,类后期添加的字段,前期的已经创建的实例,后续还能读取到,但这个很危险,很容易引起冲突,少用,慎用。
object
作为祖先根类的object,我们无法向其类型和实例添加任何成员
In [41]: dir(object) Out[41]: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] In [42]: object.x=1 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-42-018fdb0cb3be> in <module> ----> 1 object.x=1 TypeError: can't set attributes of built-in/extension type 'object' In [43]: o = object() In [44]: o.x=1 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-44-b825e1834c74> in <module> ----> 1 o.x=1 AttributeError: 'object' object has no attribute 'x' In [45]: dir(o) Out[45]: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] In [46]:
实例里面也没有__dict__属性。
可以用SimpleNamespace直接创建简单实例
In [46]: from types import SimpleNamespace In [47]: o = SimpleNamespace(a='12',b=77) In [48]: o.__dict__ Out[48]: {'a': '12', 'b': 77} In [49]:
__slots__
名字空间字典带来变量的同时,也会内存和性能问题。
In [50]: dir(A) Out[50]: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'x', 'y'] In [51]: class B: ...: ... ...: In [52]: dir(B) Out[52]: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
In [53]: set(dir(A)).difference(set(dir(B))) Out[53]: {'__slots__', 'x', 'y'} In [54]: set(dir(B)).difference(set(dir(A))) Out[54]: {'__dict__', '__weakref__'} In [55]:
从两个类的可访问字段可以看出,定义了__slots__的类,多了__slots__内字符串变量的属性,
In [55]: A.x.__class__ Out[55]: member_descriptor In [56]: dir(A.x) Out[56]: ['__class__', '__delattr__', '__delete__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__set__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] In [57]:
可以看出这个__slots__内的字符串变成了类的属性变量名以后指向了一个描述符。
定义了__slots__,类与实例少了__weakref__的属性,可以通过在__slots__中添加该属性,要不然,后续不能使用对象的弱引用。
In [60]: class C: ...: __slots__=('l1','l2','__weakref__') ...: ...: In [61]: dir(C) Out[61]: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', 'l1', 'l2'] In [62]: C.__weakref__.__class__ Out[62]: getset_descriptor In [63]:
对于弱引用,流畅的Python8.6章节有简单的使用介绍。
这里先不写了。
注意__slots__限制的是实例,而非类型,因此类型任然可以添加新成员
如果在__slots__中添加了__dict__,可回归其"原来"的样子,不过那就没什么意义了。
继承有__slots__设置的类型,同样需要添加该设置。其可未空,或是新增字段,以指示解释器继续使用特定分配策略。
In [66]: class A: ...: __slots__=('x','y') ...: def __init__(self,x, y): ...: self.x =x ...: self.y =y ...: In [68]: class Y(A): ...: __slots__=('z',) ...: def __init__(self,x,y,z): ...: super().__init__(x,y) ...: self.z=z ...: In [69]: class X(A): ...: def __init__(self,x,y,z): ...: super().__init__(x,y) ...: self.z=z ...: In [70]: x=X(1,2,3) In [71]: x.__dict__ Out[71]: {'z': 3} In [72]: y=Y(1,2,3) In [73]: y.__dict__ --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-73-b423eee3f37d> in <module> ----> 1 y.__dict__ AttributeError: 'Y' object has no attribute '__dict__' In [74]: dir(y) Out[74]: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'x', 'y', 'z']
上面的代码显示了,继承有__slots__的类,如果该类定义了__slots__会继承父类的__slots__内的字段,然后添加自己的字段,如果子类不写__slots__,那子类没有__slots__的效果。
书最后演示了通过测试工具测试,百万级对象的内存使用对比,测试工具我暂时还没如何用过,等那里学过了,再来补上
7.7运算符重载
每种运算符都对应一个有特殊名称的方法
解释器会将运算符指令转换成方法调用,方法名可参考标准库的operator文档。
运算符重载就是定义目标方法。但不限于运算符,还包括内置函数和某些语句。
class X: def __init__(self, date): self.date = date def __repr__(self): return f'{self.__class__} {self.date!r}' # 定义加法 def __add__(self, other): return X(self.date + other.date) # 定义+= def __iadd__(self, other): self.date.extend(other.date) return self if __name__ == '__main__': a, b = X([1, 2]), X([3, 4]) print(a + b) a += b print(a)
/usr/local/bin/python3.7 /Users/shijianzhong/study/Python学习笔记/第七章类/7_7.py <class '__main__.X'> [1, 2, 3, 4] <class '__main__.X'> [1, 2, 3, 4]
__repr__与__str__就不讲了,我另外的几篇内容更加详细
__repr__倾向与输出运行期状态,比如类型,id,以及关键性内容,其适合调试和观察。
而__str__通常返回内容数据,其面向用户,易于都,可输出到终端会日志
__getitem__,__setitem__,__delitem__,__call__我就不写了,前面也写过,使用也比较简单。
__dir__定制dir函数的返回值,隐藏部分成员
In [100]: class X: ...: def __dir__(self): ...: return (m for m in vars(self).keys() if not m.startswith('_')) ...: ...: ...: ...: ...: In [101]: o = X() In [102]: o.a='a' In [103]: o._b= 'b' In [105]: dir(o) Out[105]: ['a'] In [106]:
__getattr___,当这个搜索路径都找不到目标属性时触发
__setter__,拦截对任何属性的赋值操作
__delattr__,拦截对任何属性的删除操作
__getattribute__拦截对实例任何属性的访问(无论其是否存在)只要通过.去访问的都会被拦截
拦截到赋值和删除操作后,有拦截方法负责处理赋值和删除行为,忽略则被视为放弃该操作。
在拦截方法内部,通过属性或setattr等函数调用可能再次被拦截,甚至引发递归调用错误,应直接操作__dict__,或者使用基类object.__setattr__方法
class A: a = 1234 class B(A): def __init__(self, x): self.x = x # 找不到属性的运行 def __getattr__(self, item): print(f'get: {item}') return self.__dict__.get(item) # 属性赋值 def __setattr__(self, key, value): print(f'set: {key} = {value}') # self.__dict__[key] = value # 使用书中调用基类的方法 object.__setattr__(self, key, value) # 删除属性 def __delattr__(self, item): print(f'del: {item}') self.__dict__.pop(item) if __name__ == '__main__': # 初始化__init__的时候调用__setattr__ o = B(1) # 读取属性,调用__getattribute__,我没重写,调用父类的 print(o.a) # 读取一个没有的属性,调用__getattr__ print(o.xxx) # 都属性进行赋值,调用__setattr__ o.x = 100 # 对属性进行删除,调用__delattr__ del o.x
/usr/local/bin/python3.7 /Users/shijianzhong/study/Python学习笔记/第七章类/7_7.py set: x = 1 1234 get: xxx None set: x = 100 del: x
__getattribute__拦截的目标包括类属性与实例属性,这意味这只能通过基类的方法
访问不存在的成员时,__getattribute__拦截后不在触发__getattr__,除非显式调用或者触发异常
在下面的实例中,因object.__getattribute__找不到目标属性,所以会触发A.__getattr__调用,继而还会拦截到其内部对__dict__的访问。
class A: def __init__(self, x): self.x = x def __getattr__(self, item): print(f'getattr:{item}') # 这里会再次被__getattribute__拦截 return self.__dict__.get(item) def __getattribute__(self, item): print(f'getattribute: {item}') # 显式传递,通过super执行绑定父类的方法 return super(A, self).__getattribute__(item) o = A(1) print(o.x) o.s
/usr/local/bin/python3.7 /Users/shijianzhong/study/Python学习笔记/第七章类/7_7.py getattribute: x 1 getattribute: s getattr:s getattribute: __dict__
OVER