[Python进阶] 定制类:构建篇

4.10.6 构建篇

4.10.6.1 init_subclass

init_subclass 是一个特殊的方法 (special method),在 Python 3.6 中被引入。它是在定义一个类的时候会自动调用的一个类方法,而不是实例方法。这个方法可以用来拦截子类的创建,并代替父类完成一些工作,比如添加类变量、检查属性并对其进行修改、注册子类等。
使用 init_subclass 方法可以让我们在子类继承父类时,自动完成一些默认的操作,使代码更加简洁、易读和可维护。
下面是一个简单的例子,演示了如何在父类中使用 init_subclass 方法来注册子类:

# 定义一个 Widget 父类
class Widget:
    # 定义一个空字典,用于存储子类
    registered_widgets = {}
    # 自动注册子类到 registered_widgets 字典中
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        Widget.registered_widgets[cls.__name__] = cls
# 定义两个子类 Button 和 Text ,继承自 Widget
class Button(Widget):
    pass
class Text(Widget):
    pass
# 在控制台输出注册的子类名
print(Widget.registered_widgets) # {'Button': , 'Text': }

这个例子中,我们定义了一个 Widget 父类,它有一个空的 registered_widgets 字典。当子类继承自 Widget 时,会调用 init_subclass 方法,将该子类的名称和类对象添加到 registered_widgets 中。最后,在控制台输出注册的子类名,可以看到 Button 和 Text 都被自动注册到了 Widget 的 registered_widgets 字典中。

4.10.6.2 set_name

__set_name__是一个特殊的Python内置方法,用于在attribute被设置为一个类的属性时被调用。主要用于描述符中,以便可以确定它们所属的类和它们的名称(attribute name)。
当一个属性被定义为一个类的属性时,如果有实现了__set_name__方法的描述符存在,Python会自动将其所属的类和该属性的名称作为参数调用此方法。因此,实现__set_name__方法的描述符可以保存这些参数并进行后续计算或验证等操作。
下面是一个简单的例子说明了__set_name__的应用:

class MyDescriptor:
    def __set_name__(self, owner, name):
        self.name = name
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        instance.__dict__[self.name] = value
class MyClass:
    x = MyDescriptor()
    def __init__(self, x):
        self.x = x

在上面的代码中,我们定义了一个名为MyDescriptor的自定义描述符,并实现了三个方法:set_nameget__和__set。__set_name__方法保存了该描述符所属的类和属性名称,并且在MyClass类定义时由Python自动调用执行。
随后,在MyClass的实例化对象初始化过程中,我们创建了一个名为x的对象并将其值设置为传入的参数x。由于MyDescriptor是作为x属性的描述符存在的,因此当该属性被赋值时,Python将自动调用其__set__方法,进而设置x属性的值。同时,在MyDescriptor的__get__方法中,我们获取了实例属性的值并返回了它。

4.10.6.3 class_getitem

__class_getitem__是Python中的一个特殊方法,用于在运行时为泛型类定义参数化类型。简单来说,它允许我们定义可以接受泛型参数的类。
在Python 3.7及以后版本中,它已经成为了PEP 560的一部分,并且可以作为泛型类型注释或类型变量标记等方面使用。
下面是一个简单的例子:

from typing import TypeVar, Generic
T = TypeVar('T')
class MyGenericClass(Generic[T]):
    def __init__(self, value: T):
        self.value = value
    def get_value(self) -> T:
        return self.value

在上面的示例中,我们定义了一个名为MyGenericClass的泛型类,其中使用[T]来指定参数化类型(也称为类型变量)。此外,我们实现了一个初始化方法和一个获取值的方法。
这个泛型类可以接受不同的类型参数进行实例化,例如:

str_instance = MyGenericClass[str]("Hello World")
int_instance = MyGenericClass[int](10)
print(str_instance.get_value()) # Output: 'Hello World'
print(int_instance.get_value()) # Output: 10

在这里,我们用[str]和[int]作为参数化类型来创建两个不同的实例。当类被实例化时,Python会调用__class_getitem__方法来获取参数化类型并将其应用于类。通过这种方式,我们能够创建一个适用于不同类型参数的泛型类。

4.10.6.4 mro_entries

mro_entries 是一个类属性,是 Python 中的 Method Resolution Order(MRO)算法的一部分。 MRO是确定多重继承中继承属性搜索顺序的算法。
在Python 3中,可以通过使用类属性__mro_entries__ 来控制计算得到方法解析次序元组(MRO tuple )中有哪些类被包含。
使用 mro_entries 可以灵活定制 MRO 元组。例如,在 MRO 计算时不考虑某个基类,或使用一个新的中间类等。
下面我们举一个简单的例子展示如何使用 mro_entries 控制方法解析次序元组:

class A:
    pass
class B(A):
    __mro_entries__ = (A,)  # 将A从B的方法解析次序元组中删除
class C(A):
    def foo(self):
        print("C.foo")
class D(B, C):
    pass
d = D()
d.foo()

输出将是 “C.foo” ,因为在D类的MRO tuple计算时,不包含B类而只包含了 A 类和 C 类。
注意:默认情况下,如果没有定义 mro_entries 属性,默认继承关系是按照宽度优先算法。 因此,我们可以使用 mro_entry 来修改默认的搜索顺序,以解决与默认搜索顺序相关的问题。

4.10.6.5 prepare

在Python中,__prepare__是一种特殊的方法,它可以用于自定义创建类的名称空间(类字典)。
在Python中,当我们定义一个类时,该类的名称空间将存储所有类属性和方法。通常,在类定义中使用dict()来创建这个名称空间。但是,我们可以通过实现 prepare 方法来自定义类的名称空间的创建过程。prepare() 方法必须返回一个映射对象,通常是一个字典,用于存储类成员。
下面是一个简单的示例,说明如何使用__prepare__方法:

class MyMeta(type):
    def __prepare__(name, bases):
        return {'foo': 42}
    
class MyClass(metaclass=MyMeta):
    pass
print(MyClass.foo) # 输出 42

在上述的示例中,我们定义了一个名为MyMeta的元类,并实现了__prepare__方法。 当我们使用这个元类来定义一个名为MyClass的类时,会自动调用MyMeta的 __prepare__方法。 实现的__prepare__方法返回了一个包含一个foo键和对应值的字典作为名称空间,在MyClass中定义了此变量。所以最终MyClass.foo返回42。
虽然这个例子很简单,但是自定义类字典有很多实际用途,例如:
记录每个类成员的定义顺序
检查名称和类型以确保符合特定规则
自动生成一些类成员等
总之,__prepare__方法可以使元类更加灵活,并提供更多自定义类行为的能力。

4.10.6.6 instancecheck、subclasscheck

在Python中,__instancecheck__是一个特殊方法,当使用内置的isinstance()函数检查对象类型时会自动调用。__instancecheck__的作用是控制isinstance()函数的行为,可以根据需要定制对于某个自定义类的实例是否满足某些条件。
下面是一个简单的例子:假设我们要创建一个自定义类 PositiveInteger,该类仅接受大于0的整数作为构造函数的输入,并且提供了一个方法来判断一个对象是否为正整数。

class PositiveInteger:
    def __init__(self, value):
        if not isinstance(value, int) or value <= 0:
            raise ValueError("PositiveInteger must be initialized with a positive integer")
        self.value = value
    
    def is_positive(self):
        return self.value > 0
    
    def __instancecheck__(self, instance):
        return isinstance(instance, int) and instance > 0

在这个类中,我们定义了__instancecheck__方法以定制isinstance()函数的行为。在上述实现中,如果需要检查的对象是一个大于0的整数,则返回True,否则返回False。这样,在使用isinstance()函数检查PositiveInteger类的实例时,就可以支持以下两种方式:

p = PositiveInteger(5)
print(isinstance(p, PositiveInteger)) # True
print(isinstance(10, PositiveInteger)) # True

第一次调用输出True是因为p是PositiveInteger类的实例,第二次输出True是因为10符合自定义类型PositiveInteger对象对应的条件,即一个大于0的整数。注意这里的重点是演示如何进行 instancecheck 方法的实现,关注点不在于 PositiveInteger 类本身的设计。
在Python中,__subclasscheck__是一种特殊方法(也称为魔术方法或Dunder method),用于自定义子类检查逻辑。
当使用issubclass(cls, class_or_tuple)函数时,Python会调用cls.subclasscheck(sub_cls)方法来确定sub_cls是否为cls的子类,如果__subclasscheck__方法返回True,则sub_cls是cls的子类,否则不是。
一个简单的例子如下:

class EvenNumbers:
    def __subclasscheck__(self, sub):
        return isinstance(sub, int) and sub % 2 == 0
class Four(EvenNumbers):
    pass
class Three:
    pass
print(issubclass(Four, EvenNumbers)) # True
print(issubclass(int, EvenNumbers)) # True
print(issubclass(Three, EvenNumbers)) # False

以上代码中,EvenNumbers类定义了一个__subclasscheck__方法,该方法定义了仅当子类是偶数时才被认为是子类。 Four类继承了EvenNumbers并且为偶数,所以Four是EvenNumbers的子类,而int是EvenNumbers的子类,因为它符合“是偶数”这一条件。 Three类既不是偶数也不是整数,因此不是EvenNumbers的子类。

你可能感兴趣的:(Python进阶,#,四,类的进阶知识,python,开发语言)