__set_name__魔法方法

介绍

__set_name__ 方法是 Python 3.6 中引入的一种特殊方法,它可以在类属性被赋值时自动调用。这个方法可以用来处理类属性的名称绑定问题,例如将类属性与其所在的类进行绑定。

具体来说,当一个类定义了一个描述符(descriptor)并将其作为类属性时,Python 将在该类定义完成后自动调用描述符的 __set_name__ 方法,并将该类属性的名称作为参数传递给该方法。这样,我们就可以在 __set_name__ 方法中访问该类属性的名称,并将其与描述符进行绑定。

示例

class Descriptor:
    def __set_name__(self, owner, name):
        print(f"Descriptor.__set_name__(owner={owner}, name={name})")
        
    def __get__(self, instance, owner):
        print(f"Descriptor.__get__(instance={instance}, owner={owner})")
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        print(f"Descriptor.__set__(instance={instance}, value={value})")
        instance.__dict__[self.name] = value


class MyClass:
    my_attr = Descriptor()

代码分析

在这个示例中,我们定义了一个名为 Descriptor 的类,它实现了描述符协议(即包含 __get__、__set__ 和 __delete__ 方法)。然后,我们定义了一个名为 MyClass 的类,并将 Descriptor 实例作为其类属性 my_attr 的值。

当 Python 解释器执行到 MyClass 类定义结束时,它会自动调用 Descriptor.__set_name__() 方法,并将 MyClass 类和属性名 my_attr 作为参数传递给该方法。在本例中,我们只是简单地打印了一条消息来演示 __set_name__ 方法的调用时机。

接下来,当我们创建 MyClass 实例并访问其 my_attr 属性时,Python 将自动调用描述符的 __get__ 和 __set__ 方法,并分别输出相应的消息以演示这些方法的调用时机。

代码示例

from typing import Callable, Any


class Validation:

    def __init__(
            self, validation_function: Callable[[Any], bool], error_msg: str
    ) -> None:
        print("Validation初始化被执行")
        self.validation_function = validation_function  # 传进来的是匿名函数
        self.error_msg = error_msg

    def __call__(self, value):
        print("call被执行")
        if not self.validation_function(value):  # lambda x: isinstance(x, (int, float))
            raise ValueError(f"{value!r} {self.error_msg}")


class Field:  # 描述符类

    def __init__(self, *validations):  # 用*接收,表示可以传多个,目前代码可以理解为传进来的就是一个个Validation的实例
        print("Field初始化被执行")
        self._name = None
        self.validations = validations  # 接收完后的类型是元组

    def __set_name__(self, owner, name):
        print("set_name被执行")
        self._name = name  # 会自动将托管类ClientClass的类属性descriptor带过来

    def __get__(self, instance, owner):
        print("get被执行")
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def validate(self, value):
        print("验证被执行")
        for validation in self.validations:
            validation(value) # 这是是将对象当成函数执行时,调用Validation的__call__魔法方法

    def __set__(self, instance, value):
        """
        :param self: 指的是Field对象
        :param instance: ClientClass对象
        :param value: 给属性赋值的值
        :return: 
        """
        print("set被执行")
        self.validate(value)
        instance.__dict__[self._name] = value  # 给ClientClass对象赋值  {"descriptor": 42}


class ClientClass:  # 托管类
    descriptor = Field(
        Validation(lambda x: isinstance(x, (int, float)), "is not a number"),
        # Validation(lambda x: x >= 0, "is not >= 0"),
    )


if __name__ == '__main__':
    """
    Validation初始化被执行
    Field初始化被执行
    set_name被执行  # 当Field()赋值给descriptor变量时,执行__set_name__
    ---------------------
    set被执行
    验证被执行
    call被执行
    """
    client = ClientClass()  # 实例化对象
    print("---------------------")
    # 给上面实例化的对象中的属性(Field实例化对象)赋值为42
    client.descriptor = 42

posted @ 2023-04-08 18:56  我在路上回头看  阅读(541)  评论(0编辑  收藏  举报