特殊方法(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参数。

posted @ 2024-11-30 18:52  RolandHe  阅读(4)  评论(0编辑  收藏  举报