likes
comments
collection
share

[python]魔术方法大全(三)-- 属性篇

作者站长头像
站长
· 阅读数 15

这次我们主要来聊聊跟对象的属性有关的魔术方法。

基础属性魔法方法

__getattr__魔术方法

__getattr__ 是 Python 中的一个特殊方法,用于在访问一个不存在的属性时被调用。如果一个对象定义了 __getattr__ 方法,那么当它的属性被访问时,如果该属性不存在,则 Python 会调用该对象的 __getattr__ 方法来处理这个访问。

以下是一个简单的示例,演示了如何使用 __getattr__ 方法:

class Example:
    def __init__(self):
        self.data = {'a': 1, 'b': 2, 'c': 3}

    def __getattr__(self, name):
        if name in self.data:
            return self.data[name]
        else:
            raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")

在这个示例中,我们定义了一个 Example 类,它有一个 data 属性,其中包含一些数据。在 __getattr__ 方法中,我们检查属性名称是否存在于 data 字典中。如果是,我们返回相应的值;否则,我们抛出一个 AttributeError 异常,表示该属性不存在。

现在,我们可以创建一个 Example 实例,并访问它的属性,即使这些属性并不存在:

>>> ex = Example()
>>> ex.a
1
>>> ex.b
2
>>> ex.c
3
>>> ex.d
AttributeError: 'Example' object has no attribute 'd'

在这个示例中,我们首先创建了一个 Example 实例,然后访问了它的属性 abc,它们都存在于 data 字典中。然后我们试图访问属性 d,这个属性并不存在,于是 Python 调用了 __getattr__ 方法,并抛出了一个 AttributeError 异常。

需要注意的是,如果一个对象同时定义了 __getattr____getattribute__ 方法,那么 __getattribute__ 方法会比 __getattr__ 方法更先被调用,因为它处理所有属性的访问,而不仅仅是不存在的属性。因此,如果您要使用 __getattr__ 方法,需要确保它只用于处理不存在的属性。

__getattribute__魔术方法

__getattribute__ 是 Python 中的一个特殊方法,用于在访问对象属性时被调用。当我们使用点号(.)或 getattr() 函数来访问一个对象的属性时,Python 会调用该对象的 __getattribute__ 方法来获取属性的值。与 __getattr__ 方法不同的是,__getattribute__ 方法对于对象的所有属性都会被调用,而不仅仅是不存在的属性。

以下是一个简单的示例,演示了如何使用 __getattribute__ 方法:

class Example:
    def __init__(self):
        self.data = {'a': 1, 'b': 2, 'c': 3}

    def __getattribute__(self, name):
        try:
            return object.__getattribute__(self, name)
        except AttributeError:
            if name in self.data:
                return self.data[name]
            else:
                raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")

在这个示例中,我们定义了一个 Example 类,它有一个 data 属性,其中包含一些数据。在 __getattribute__ 方法中,我们首先尝试使用 object.__getattribute__ 方法来获取属性的值。如果该属性存在,则直接返回它的值。如果该属性不存在,则抛出一个 AttributeError 异常,表示该属性不存在。在这种情况下,我们检查属性名称是否存在于 data 字典中。如果是,我们返回相应的值;否则,我们抛出一个 AttributeError 异常。

现在,我们可以创建一个 Example 实例,并访问它的属性:

>>> ex = Example()
>>> ex.a
1
>>> ex.b
2
>>> ex.c
3
>>> ex.d
AttributeError: 'Example' object has no attribute 'd'

在这个示例中,我们首先创建了一个 Example 实例,然后访问了它的属性 abc,它们都存在于 data 字典中。然后我们试图访问属性 d,这个属性并不存在,于是 Python 调用了 __getattribute__ 方法,并抛出了一个 AttributeError 异常。

需要注意的是,__getattribute__ 方法可能会导致无限递归的问题。因为它处理所有属性的访问,如果在 __getattribute__ 方法中又访问了其他属性,那么就会再次调用 __getattribute__ 方法,从而导致递归调用。为了避免这个问题,通常在 __getattribute__ 方法中使用 object.__getattribute__ 方法来获取属性的值。

__setattr__魔术方法

__setattr__ 方法是 Python 中的一个特殊方法,用于在设置对象属性时被调用。当我们使用赋值语句或 setattr() 函数来设置一个对象的属性时,Python 会调用该对象的 __setattr__ 方法来进行赋值操作。该方法通常被用于实现属性的访问控制或修改属性赋值的行为。

以下是一个简单的示例,演示了如何使用 __setattr__ 方法:

class Example:
    def __setattr__(self, name, value):
        if name == 'value':
            self.__dict__[name] = value
        else:
            raise AttributeError(f"cannot set attribute '{name}'")

ex = Example()
ex.value = 42
print(ex.value)  # 输出 42
ex.other = 'other'  # 抛出 AttributeError: cannot set attribute 'other'

在这个示例中,我们定义了一个 Example 类,它有一个 value 属性。在 __setattr__ 方法中,我们首先检查要设置的属性名称是否是 value。如果是,我们直接设置该属性的值。如果不是,我们抛出一个 AttributeError 异常,表示不能设置该属性。这样,我们就实现了对 value 属性的访问控制,只允许对它进行赋值操作。

现在,我们可以创建一个 Example 实例,并设置它的属性:

>>> ex = Example()
>>> ex.value = 42
>>> print(ex.value)
42
>>> ex.other = 'other'
AttributeError: cannot set attribute 'other'

在这个示例中,我们首先创建了一个 Example 实例,并设置了它的 value 属性。然后,我们试图设置一个 other 属性,它并不存在于 Example 类中,于是 Python 调用了 __setattr__ 方法,并抛出了一个 AttributeError 异常,表示不能设置该属性。

需要注意的是,__setattr__ 方法可能会导致无限递归的问题。因为它处理所有属性的设置,如果在 __setattr__ 方法中又设置了其他属性,那么就会再次调用 __setattr__ 方法,从而导致递归调用。为了避免这个问题,通常在 __setattr__ 方法中使用 self.__dict__[name] = value 来设置属性的值,而不是直接调用 super().__setattr__(name, value) 方法。

__delattr__魔术方法

__delattr__ 方法是 Python 中的一个特殊方法,用于在删除对象属性时被调用。当我们使用 del 关键字或 delattr() 函数来删除一个对象的属性时,Python 会调用该对象的 __delattr__ 方法来进行删除操作。

以下是一个简单的示例,演示了如何使用 __delattr__ 方法:

class Example:
    def __init__(self):
        self.value = 42
    
    def __delattr__(self, name):
        if name == 'value':
            del self.__dict__[name]
        else:
            raise AttributeError(f"cannot delete attribute '{name}'")

ex = Example()
del ex.value
print(ex.__dict__)  # 输出 {}
del ex.other  # 抛出 AttributeError: cannot delete attribute 'other'

在这个示例中,我们定义了一个 Example 类,它有一个 value 属性。在 __delattr__ 方法中,我们首先检查要删除的属性名称是否是 value。如果是,我们直接删除该属性。如果不是,我们抛出一个 AttributeError 异常,表示不能删除该属性。这样,我们就实现了对 value 属性的访问控制,只允许删除它。

现在,我们可以创建一个 Example 实例,并删除它的属性:

>>> ex = Example()
>>> del ex.value
>>> print(ex.__dict__)
{}
>>> del ex.other
AttributeError: cannot delete attribute 'other'

在这个示例中,我们首先创建了一个 Example 实例,并删除了它的 value 属性。然后,我们试图删除一个 other 属性,它并不存在于 Example 类中,于是 Python 调用了 __delattr__ 方法,并抛出了一个 AttributeError 异常,表示不能删除该属性。

需要注意的是,__delattr__ 方法同样可能会导致无限递归的问题。因为它处理所有属性的删除,如果在 __delattr__ 方法中又删除了其他属性,那么就会再次调用 __delattr__ 方法,从而导致递归调用。为了避免这个问题,通常在 __delattr__ 方法中使用 del self.__dict__[name] 来删除属性,而不是直接调用 super().__delattr__(name) 方法。

__dir__魔术方法

__dir__() 是 Python 中的一个特殊方法,用于返回对象的属性列表。该方法返回的属性列表应该是对象的属性名称的迭代器,可以用于 Python 的 dir() 内置函数。

如果一个类没有定义 __dir__() 方法,Python 会尝试调用对象的 __dict__ 属性,并返回其中所有的属性名称和方法名称。这意味着在没有定义 __dir__() 方法的情况下,dir() 函数仍然能够返回一个对象的所有属性名称和方法名称。

以下是一个简单的示例,演示了如何使用 __dir__() 方法:

class Example:
    def __init__(self):
        self.value = 42
    
    def __dir__(self):
        return ['value', 'foo', 'bar']

ex = Example()
print(dir(ex))  # 输出 ['bar', 'foo', 'value']

在这个示例中,我们定义了一个 Example 类,它有一个 value 属性。在 __dir__() 方法中,我们返回了一个包含 'value''foo''bar' 三个字符串的列表。这意味着在调用 dir() 函数时,它将返回 ex 对象的 'value''foo''bar' 三个属性名称。

需要注意的是,__dir__() 方法应该返回一个迭代器,而不是一个列表。如果方法返回一个列表,那么在调用 dir() 函数时,Python 将在内部创建一个新的列表,从而可能导致性能问题。

如果需要使用默认的 __dir__() 行为,可以返回 None 或者调用 super().__dir__() 方法来获取默认的属性列表。例如:

class Example:
    def __init__(self):
        self.value = 42
    
    def __dir__(self):
        return None

ex = Example()
print(dir(ex))  # 输出 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'value']

在这个示例中,我们返回了 None,这意味着 __dir__() 方法不会影响 dir() 函数的行为。因此,我们得到了默认的属性列表,其中包括 Example 类和 ex 对象的所有属性和方法名称。

描述符相关的魔术方法

描述符(Descriptor)是 Python 中一种协议,用于控制属性的访问和修改行为。如果一个类的属性被赋予了描述符,那么每次对该属性进行访问或修改操作时,Python 都会自动调用描述符中相应的方法。

Python 中有三种描述符,分别是数据描述符、非数据描述符和属性。其中,数据描述符实现了 __get__()__set__()__delete__() 方法,可以控制属性的读写和删除操作;非数据描述符只实现了 __get__() 方法,仅控制属性的读操作;属性既可以是数据描述符也可以是非数据描述符,其默认实现是仅读取操作。

下面是一个简单的示例,演示了如何定义一个数据描述符:

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    def to_fahrenheit(self):
        return (self._celsius * 1.8) + 32
    
    def __get__(self, instance, owner):
        print("Getting celsius value...")
        return self._celsius
    
    def __set__(self, instance, value):
        print("Setting celsius value...")
        if value < -273.15:
            raise ValueError("Temperature too low")
        self._celsius = value
    
    def __delete__(self, instance):
        print("Deleting celsius value...")
        del self._celsius
    
    celsius = Temperature()

temp = Temperature()
temp.celsius = 25  # 设置 celsius 属性的值
print(temp.celsius)  # 输出 25
print(temp.to_fahrenheit())  # 输出 77.0
del temp.celsius  # 删除 celsius 属性
print(hasattr(temp, "celsius"))  # 输出 False

在这个示例中,我们定义了一个名为 Temperature 的类,并在其中实现了 __get__()__set__()__delete__() 方法。然后我们将 Temperature 类的实例作为 celsius 属性的值赋给了 temp 对象,并通过 temp.celsius 访问和修改 celsius 属性的值。每次访问和修改 celsius 属性时,Python 都会自动调用 __get__()__set__()__delete__() 方法。

需要注意的是,如果一个属性同时定义了 __get__()__set__()__delete__() 方法,那么该属性就是一个数据描述符。如果一个属性只定义了 __get__() 方法,那么该属性就是一个非数据描述符。如果一个属性没有定义任何描述符方法,那么该属性就是一个普通属性。

描述符:docs.python.org/zh-cn/3/how…

__get__魔术方法

__get__() 方法是 Python 中的一个特殊方法,用于定义一个描述符(Descriptor)。描述符是一种 Python 对象,它定义了另一个对象的访问方式,可以用于控制对该对象的访问、修改或者删除。

在 Python 中,描述符可以是一个实现了 __get__()__set__()__delete__() 方法的对象。其中,__get__() 方法用于获取描述符所关联的属性的值。当一个属性被访问时,Python 会自动调用该属性所关联的描述符的 __get__() 方法,并返回描述符的返回值。

以下是一个简单的示例,演示了如何使用 __get__() 方法:

class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature
    
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    
    def __get__(self, instance, owner):
        return instance.temperature
    
    def __set__(self, instance, value):
        if value < -273.15:
            raise ValueError("Temperature too low")
        instance.temperature = value

class Temperature:
    celsius = Celsius()

temp = Temperature()
temp.celsius = 25
print(temp.celsius)  # 输出 25
print(temp.celsius.to_fahrenheit())  # 输出 77.0

在这个示例中,我们定义了一个 Celsius 类,它实现了一个描述符。描述符的 __get__() 方法返回 instance.temperature,因此在访问 Temperature 类的 celsius 属性时,它将返回 Celsius 实例的 temperature 属性的值。描述符的 __set__() 方法用于设置 Celsius 实例的 temperature 属性的值,并检查该值是否符合要求。

我们还定义了一个 Temperature 类,它包含一个 celsius 属性,该属性关联了 Celsius 描述符。当我们设置 temp.celsius 属性时,Python 会自动调用 Celsius 描述符的 __set__() 方法,并将 temp 对象作为 instance 参数传递给该方法。当我们获取 temp.celsius 属性时,Python 会自动调用 Celsius 描述符的 __get__() 方法,并返回描述符的返回值。

需要注意的是,描述符可以是类级别的或实例级别的。在上面的示例中,我们定义了一个类级别的描述符,因为它是一个类属性。如果将描述符定义为实例属性,则该描述符只与一个特定的实例对象相关联。

__set__魔术方法

__set__() 方法是 Python 中的一个特殊方法,用于定义描述符(Descriptor)对象的设置操作。描述符是一种 Python 对象,它定义了另一个对象的访问方式,可以用于控制对该对象的访问、修改或者删除。

在 Python 中,描述符可以是一个实现了 __get__()__set__()__delete__() 方法的对象。其中,__set__() 方法用于设置描述符所关联的属性的值。当一个属性被设置时,Python 会自动调用该属性所关联的描述符的 __set__() 方法,并将被设置的值作为参数传递给该方法。

以下是一个简单的示例,演示了如何使用 __set__() 方法:

class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature
    
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    
    def __get__(self, instance, owner):
        return instance.temperature
    
    def __set__(self, instance, value):
        if value < -273.15:
            raise ValueError("Temperature too low")
        instance.temperature = value

class Temperature:
    celsius = Celsius()

temp = Temperature()
temp.celsius = 25  # 设置 celsius 属性的值
print(temp.celsius)  # 输出 25
print(temp.celsius.to_fahrenheit())  # 输出 77.0

在这个示例中,我们定义了一个 Celsius 类,它实现了一个描述符。描述符的 __get__() 方法返回 instance.temperature,因此在访问 Temperature 类的 celsius 属性时,它将返回 Celsius 实例的 temperature 属性的值。描述符的 __set__() 方法用于设置 Celsius 实例的 temperature 属性的值,并检查该值是否符合要求。

我们还定义了一个 Temperature 类,它包含一个 celsius 属性,该属性关联了 Celsius 描述符。当我们设置 temp.celsius 属性时,Python 会自动调用 Celsius 描述符的 __set__() 方法,并将 temp 对象作为 instance 参数传递给该方法,将 25 作为 value 参数传递给该方法。

需要注意的是,描述符可以是类级别的或实例级别的。在上面的示例中,我们定义了一个类级别的描述符,因为它是一个类属性。如果将描述符定义为实例属性,则该描述符只与一个特定的实例对象相关联。

__delete__魔术方法

__delete__() 方法是 Python 中的一个特殊方法,用于定义描述符(Descriptor)对象的删除操作。描述符是一种 Python 对象,它定义了另一个对象的访问方式,可以用于控制对该对象的访问、修改或者删除。

在 Python 中,描述符可以是一个实现了 __get__()__set__()__delete__() 方法的对象。其中,__delete__() 方法用于删除描述符所关联的属性。当一个属性被删除时,Python 会自动调用该属性所关联的描述符的 __delete__() 方法。

以下是一个简单的示例,演示了如何使用 __delete__() 方法:

class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature
    
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    
    def __get__(self, instance, owner):
        return instance.temperature
    
    def __set__(self, instance, value):
        if value < -273.15:
            raise ValueError("Temperature too low")
        instance.temperature = value
    
    def __delete__(self, instance):
        del instance.temperature

class Temperature:
    celsius = Celsius()

temp = Temperature()
temp.celsius = 25  # 设置 celsius 属性的值
print(temp.celsius)  # 输出 25
print(temp.celsius.to_fahrenheit())  # 输出 77.0
del temp.celsius  # 删除 celsius 属性
print(hasattr(temp, "celsius"))  # 输出 False

在这个示例中,我们添加了一个 __delete__() 方法到 Celsius 描述符中。当我们使用 del temp.celsius 删除 celsius 属性时,Python 会自动调用 Celsius 描述符的 __delete__() 方法,并将 temp 对象作为 instance 参数传递给该方法。

需要注意的是,如果一个描述符不支持删除操作,那么该描述符的 __delete__() 方法可以省略不写。

__slot__特殊方法

Python 中的 __slots__ 方法是一种类变量,它可以用来限制类实例的属性。当一个类定义了 __slots__ 变量时,Python 解释器就会为该类实例分配一个固定大小的数组来保存其属性,从而避免了使用字典来存储属性所带来的额外开销。

使用 __slots__ 变量的主要优点是可以提高实例访问的速度和减少内存消耗。因为 __slots__ 变量指定了实例可以拥有的属性,所以在访问实例属性时,Python 解释器不需要查找字典,而是可以直接通过数组下标来访问属性。此外,由于 __slots__ 变量限制了实例的属性,所以可以避免实例因为拥有过多的属性而消耗过多的内存。

下面是一个使用 __slots__ 变量的例子:

class Person:
    __slots__ = ('name', 'age')

    def __init__(self, name, age):
        self.name = name
        self.age = age

在上面的代码中,Person 类使用 __slots__ 变量限制了实例只能有 nameage 两个属性。由于 __slots__ 变量的存在,当创建 Person 类的实例时,实例不再拥有一个字典用于存储属性,而是使用一个固定大小的数组来存储属性,从而减少了内存消耗。