这次我们主要来聊聊跟对象的属性有关的魔术方法。
__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
实例,然后访问了它的属性 a
、b
和 c
,它们都存在于 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
实例,然后访问了它的属性 a
、b
和 c
,它们都存在于 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__()
方法,那么该属性就是一个非数据描述符。如果一个属性没有定义任何描述符方法,那么该属性就是一个普通属性。
描述符:https://docs.python.org/zh-cn/3/howto/descriptor.html
__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__
变量限制了实例只能有 name
和 age
两个属性。由于 __slots__
变量的存在,当创建 Person
类的实例时,实例不再拥有一个字典用于存储属性,而是使用一个固定大小的数组来存储属性,从而减少了内存消耗。