特殊方法(special method)
定制化属性访问
__getattribute__(self, name)
: 被称作属性拦截器, 即所有对实例属性的访问都会先受到此方法的影响。此方法应该返回一个我们处理后的值,或者抛出一个AttributeError异常。 此方法应该谨慎使用。一般我们对个别属性做特殊处理后,都要加一个调用父类该方法,以免无限递归调用。
__getattr__(self, name)
: 当按照普通的属性访问方法访问不到一个属性时(即遇到AttributeError异常),此方法将被触发。这个方法也该要么返回一个处理后的值,要么抛出AttributeError异常。
__setattr__(self, name, value)
: 会拦截普通的属性赋值操作(也包括属性不存在时的创建并赋初值)。如果希望数据赋值给实例属性,则应该调用父类方法防止无限递归。
__delattr__(self, name)
: 当删除一个属性时,会触发此方法。比如 del obj.prop
使用上面这些方法,可以高度定制对属性的访问,修改,删除等操作。比如,可以控制不将属性存储在实例的__dict__
里,而是存储在某个指定的对象里。
class Foo():
def __getattribute__(self, item):
if item == 'prop1':
return '被拦截了'
else:
# return self.item # 这种写法会造成无限递归
return super().__getattribute__(item)
def __getattr__(self, name):
return '被__getattr_了'
def __setattr__(self, key, value):
if key == 'prop2':
# self.prop2 = '被__setattr__了' # 这种写法会造成无限递归
super().__setattr__(key, '被__setattr__了')
else:
super().__setattr__(key, value)
foo = Foo()
foo.prop1 = 'prop1'
foo.prop2 = 'prop2'
print('dir:', dir(foo))
print('vars:',vars(foo))
print('__dict__:',foo.__dict__)
print('foo.prop1:', foo.prop1)
print('foo.prop2:', foo.prop2)
print('foo.prop3:', foo.prop3)
返回结果:
dir: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'prop1', 'prop2']
vars: {'prop1': 'prop1', 'prop2': '被__setattr__了'}
__dict__: {'prop1': 'prop1', 'prop2': '被__setattr__了'}
foo.prop1: 被拦截了
foo.prop2: 被__setattr__了
foo.prop3: 被__getattr_了
__set_name__(self, owner, name)
: __set_name__
是一个回调方法,当一个类实现了这个方法时,如果其实例做为另一个类的类属性时,这个方法将会在另一个类声明该属性的时候就会被自动调用。owner为另一个类的名字,而name为那个类属性的名字。
class A:
def __set_name__(self, owner, name):
print('__set_name','owner is', owner, ", name is ", name)
self.public_name = name
self.private_name = '_' + name
class B:
cls_b_prop = A() # 在B类定义执行到声明类属性为A类的实例时,就会自动调用A类中的__set_name__方法
print(vars(vars(B)['cls_b_prop'])) # 如果类属性cls_b_prop是描述器,那么更应该使用vars()这种方式来查看该描述器里都保存了什么内容
输出结果:
__set_name owner is <class '__main__.B'> , name is cls_b_prop
{'public_name': 'cls_b_prop', 'private_name': '_cls_b_prop'}
__subclasshook__(cls, __subclass)
: 用于自定义判断虚拟子类的逻辑。 该方法只能用在抽象类中,并且必须使其为类方法,在普通类中定义无效。
例如,我在一个普通类中定义该方法:
class Base:
@classmethod
def __subclasshook__(cls, __subclass):
print('Base.__subclasshook__ 被调用')
return True
class Foo:
pass
print(issubclass(Foo, Base)) # 输出为Fase
在抽象类中定义该方法:
from abc import ABC
class Base(ABC):
@classmethod
def __subclasshook__(cls, __subclass):
print('Base.__subclasshook__ 被调用')
return True
class Foo:
pass
print(issubclass(Foo, Base)) # 返回True
输出结果:
Base.__subclasshook__ 被调用
True
下面再举一个稍微有点意义的例子,来体会一下可能会使用__subclasshook__
方法的场景。
from abc import ABC, ABCMeta
class MyABC(ABC):
@classmethod
def __subclasshook__(cls, subclass):
# 如果子类有 'method1' 和 'method2' 属性或方法,就认为它是 MyABC 的子类
if hasattr(subclass, 'method1') and hasattr(subclass, 'method2'):
return True
return NotImplemented
class SubClass:
def method1(self):
pass
def method2(self):
pass
class AnotherClass:
def method1(self):
pass
# 检查子类关系
print(issubclass(SubClass, MyABC)) # True
print(issubclass(AnotherClass, MyABC)) # False
__instancecheck__
与__subclasscheck__
方法: 与__subclasshook__
一样,都会影响instance()与insubclass()函数的结果。是高优先级的自定义方法,用于重定义 isinstance() 和 issubclass() 的行为。这两个方法都应该定义在元类中。
class MyMeta(type):
def __instancecheck__(cls, instance):
# 自定义规则:如果对象有 "special_attr" 属性,则认为它是 cls 的实例
print('MyMeta.__instancecheck__ 被调用, instance is', instance)
return hasattr(instance, 'special_attr')
def __subclasscheck__(cls, subclass):
# 自定义规则:如果子类名称以 "Special" 开头,则认为它是 cls 的子类
print('MyMeta.__subclasscheck__ 被调用, subclass is', subclass)
return subclass.__name__.startswith("Special")
class MyClass(metaclass=MyMeta):
pass
class OtherClass:
special_attr = True
class SpecialSubClass:
pass
class RegularClass:
pass
obj = OtherClass()
# 自定义逻辑生效
print(isinstance(obj, MyClass)) # True
print('-' * 25, '分隔线', '-' * 25)
print(issubclass(SpecialSubClass, MyClass)) # True
print('-' * 25, '分隔线', '-' * 25)
print(issubclass(RegularClass, MyClass)) # False
输出结果:
MyMeta.__instancecheck__ 被调用, instance is <__main__.OtherClass object at 0x0000024565E86390>
True
------------------------- 分隔线 -------------------------
MyMeta.__subclasscheck__ 被调用, subclass is <class '__main__.SpecialSubClass'>
True
------------------------- 分隔线 -------------------------
MyMeta.__subclasscheck__ 被调用, subclass is <class '__main__.RegularClass'>
False
一般不是必要的情况,我们不应该重写这两个方法。
重新回到前面的__subclasshook__
方法, 它是定义在抽象基类中的,而这两个方法是定义在元类中的。 其实abc模块的抽象基类的元类ABCMeta内部就实现了__instancecheck__
和__subclasscheck__
方法。 并且其__subclasscheck__内部会调用抽象基类中的__subclasshook__
方法。 我们一般如果需要重定义issubclass()函数的行为,推荐覆写__subclasshook__
方法,而不是改动基类的__subclasscheck__
方法。
__init_subclass__
: 在一个类被子类化(继承)时,自动调用该方法。用于自定义子类的行为,如检查、验证或动态修改子类。它是 类方法,会自动将子类作为参数传递给 cls。如果定义子类时传递了关键字参数,这些参数会被传递到基类的 __init_subclass__
方法。
先看下简单用法示例:
class BaseFoo:
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('BaseFoo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
class Foo(BaseFoo, prop='abc'):
pass
输出结果:
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
其实想传给__init_subclass__
参数,只能通过关键字方式传入。因为在类定义时,类名后面的括号里放位置参数的话都会被认为是要继承的基类名称。所有,我这里定义的*args参数其实没有意义。
我尝试换种调用方法:
print('-' * 25, '分隔线', '-' * 25)
BaseFoo.__init_subclass__('456',wawa='gaga')
print('-' * 25, '分隔线', '-' * 25)
Foo.__init_subclass__('123',hehe='haha')
print('-' * 25, '分隔线', '-' * 25)
foo = Foo()
foo.__init_subclass__('789',foo='im foo')
输出结果:
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.BaseFoo'>
BaseFoo __init_subclass__被执行
args is ('456',)
kwargs is {'wawa': 'gaga'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ('123',)
kwargs is {'hehe': 'haha'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ('789',)
kwargs is {'foo': 'im foo'}
可以看出,__init_subclass__
方法是类方法,无论使用类名,或实例名加点的方式调用它,都会自动将类名或实例所属的类传递给该方法的第一个参数。
我们还可以观察到,由于Foo类内部没有定义__init_subclass__
方法,所以Foo.__init_subclass__
其实是调用了其基类的方法,也就是BaseFoo.__init_subclass__
如果我们在子类,即Foo中覆写__init_subclass__方法,我们观察到的结果如下:
class BaseFoo:
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('BaseFoo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
class Foo(BaseFoo, prop='abc'):
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('Foo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('-' * 25, '分隔线', '-' * 25)
BaseFoo.__init_subclass__('456',wawa='gaga')
print('-' * 25, '分隔线', '-' * 25)
Foo.__init_subclass__('123',hehe='haha')
print('-' * 25, '分隔线', '-' * 25)
foo = Foo()
foo.__init_subclass__('789',foo='im foo')
输出结果:
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.BaseFoo'>
BaseFoo __init_subclass__被执行
args is ('456',)
kwargs is {'wawa': 'gaga'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.Foo'>
Foo __init_subclass__被执行
args is ('123',)
kwargs is {'hehe': 'haha'}
------------------------- 分隔线 -------------------------
cls is <class '__main__.Foo'>
Foo __init_subclass__被执行
args is ('789',)
kwargs is {'foo': 'im foo'}
继承链调用示例:
class BaseFoo:
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('BaseFoo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('-' * 25, '分隔线', '-' * 25)
class Foo(BaseFoo, prop='abc'):
print('定义Foo类')
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('Foo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('调用基类__init_subclass')
super().__init_subclass__(*args, **kwargs) # 可以根据需要来决定是否要继续调用上层基类的__init_subclass__方法
print('-' * 25, '分隔线', '-' * 25)
class Bar(Foo, bar='xiao bar'):
print('定义Bar类')
pass
输出结果:
------------------------- 分隔线 -------------------------
定义Foo类
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
------------------------- 分隔线 -------------------------
定义Bar类
cls is <class '__main__.Bar'>
Foo __init_subclass__被执行
args is ()
kwargs is {'bar': 'xiao bar'}
调用基类__init_subclass
cls is <class '__main__.Bar'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'bar': 'xiao bar'}
最后,需要注意的是,python中的最终基类的__init_subclass__
没有做任何事情,也不接收任何参数。比如,object.__init_subclass__(Foo)
执行是会报错的。
另外,python会自动将关键字参数metaclass排除在外,使它不会被传递给基类的__init_subclass__
class BaseFoo:
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('BaseFoo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('-' * 25, '分隔线', '-' * 25)
class Foo(BaseFoo, prop='abc', metaclass=type):
print('定义Foo类')
def __init_subclass__(cls, *args, **kwargs):
print('cls is',cls)
print('Foo __init_subclass__被执行')
print('args is', args)
print('kwargs is', kwargs)
print('调用基类__init_subclass')
super().__init_subclass__(*args, **kwargs) # 可以根据需要来决定是否要继续调用上层基类的__init_subclass__方法
输出结果:
------------------------- 分隔线 -------------------------
定义Foo类
cls is <class '__main__.Foo'>
BaseFoo __init_subclass__被执行
args is ()
kwargs is {'prop': 'abc'}
可以看到,__init_subclass__
方法的kwargs没有捕获到metaclass参数。