Python 描述符是一种定制对象属性访问、赋值和删除的方式。它们提供了一种强大的机制来管理属性的行为,通过定义获取、设置和删除其值的方法。描述符经常用于实现属性、方法和属性验证。
描述符是指实现了至少一个如 __get__
、__set__
和 __delete__
方法的对象。这些方法控制属性值如何被访问和修改。
Python 描述符的工作原理?
当在一个实例上访问一个属性时,Python 会在实例的类中查找该属性。如果找到了并且它是一个描述符,那么 Python 会调用相应的描述符方法而不是简单地返回属性的值。这允许描述符控制在属性访问期间发生的情况。
描述符协议是一个低级别的机制,被 Python 中许多高级特性所使用,例如属性、方法、静态方法和类方法。描述符可用于实现诸如延迟加载、类型检查和计算属性的模式。
描述符方法
Python 描述符包含三个主要的方法 __get__()
、__set__()
和 __delete__()
。正如我们上面已经讨论过的,这些方法分别控制属性的访问、赋值和删除行为。
1. __get__()
方法
__get__()
方法是 Python 描述符协议中的关键部分。它用于从实例或类中检索属性的值。理解 __get__()
方法是如何工作的对于创建能够以复杂方式管理属性访问的自定义描述符至关重要。
语法
下面是 Python 描述符 __get__
方法的语法:
def __get__(self, instance, owner):
"""
instance: 当通过实例访问属性时,为该实例;当通过类访问时为 None。
owner: 定义描述符的类。
"""
参数
下面是此方法的参数:
-
-
instance
: 访问属性的类的实例。如果通过类而不是实例访问属性,则为 None。
-
示例
下面是一个简单的 __get__()
方法示例,当访问 obj.attr
时,它返回存储的值 _value
:
class Descriptor:
def __get__(self, instance, owner):
if instance is None: return self
return instance._value
class MyClass:
attr = Descriptor()
def __init__(self, value):
self._value = value
obj = MyClass(42)
print(obj.attr)
输出
42
2. __set__()
方法
__set__()
方法是 Python 描述符协议的一部分,用于控制设置属性值的行为。当由描述符管理的属性被分配新值时,__set__()
方法会被调用,允许用户定制或强制属性赋值的规则。
语法
下面是 Python 描述符 __set__()
方法的语法:
def __set__(self, instance, value):
"""
instance: 正在设置属性的类的实例。
value: 要分配给属性的值。
"""
参数
下面是此方法的参数:
示例
下面是一个简单的 __set__()
方法示例,它确保分配给 attr
的值是一个整数:
class Descriptor:
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError("Value must be an integer")
instance._value = value
class MyClass:
attr = Descriptor()
def __init__(self, value):
self.attr = value
obj = MyClass(42)
print(obj.attr)
obj.attr = 100
print(obj.attr)
输出
42
100
3. __delete__()
方法
描述符协议中的 __delete__()
方法允许我们控制在从实例中删除属性时发生的情况。这对于管理资源、清理或在删除属性时强制约束很有用。
语法
下面是 Python 描述符 __delete__()
方法的语法:
def __delete__(self, instance):
"""
instance: 正在从中删除属性的类的实例。
"""
参数
下面是此方法的参数:
示例
下面是一个简单的 __delete__()
方法示例,它确保在删除 attr
时记录操作:
class LoggedDescriptor:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
def __delete__(self, instance):
if self.name in instance.__dict__:
print(f"Deleting {self.name} from {instance}")
del instance.__dict__[self.name]
else:
raise AttributeError(f"{self.name} not found")
class Person:
name = LoggedDescriptor("name")
age = LoggedDescriptor("age")
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Tutorialspoint", 30)
print(p.name)
print(p.age)
del p.name
print(p.name)
del p.age
print(p.age)
输出
Tutorialspoint
30
Deleting name from <__main__.Person object at 0x0000021A1A67E2D0>
None
Deleting age from <__main__.Person object at 0x0000021A1A67E2D0>
None
Python 描述符的类型
根据它们实现的方法,Python 描述符大致可以分为两种类型:数据描述符和非数据描述符。
数据描述符
数据描述符是 Python 中的一种描述符类型,定义了 __get__()
和 __set__()
方法。这些描述符优先于实例属性,这意味着即使存在同名的实例属性,描述符的 __get__()
和 __set__()
方法也会始终被调用。
示例
下面是一个数据描述符的例子,它确保一个属性始终是一个整数,并记录访问和修改操作:
class Integer:
def __get__(self, instance, owner):
print("Getting value")
return instance._value
def __set__(self, instance, value):
print("Setting value")
if not isinstance(value, int):
raise TypeError("Value must be an integer")
instance._value = value
def __delete__(self, instance):
print("Deleting value")
del instance._value
class MyClass:
attr = Integer()
obj = MyClass()
obj.attr = 42
print(obj.attr)
obj.attr = 100
print(obj.attr)
del obj.attr
输出
Setting value
Getting value
42
Setting value
Getting value
100
Deleting value
非数据描述符
非数据描述符是 Python 中的一种描述符类型,只定义了 __get__()
方法。与数据描述符不同,非数据描述符可以被实例属性覆盖。这意味着如果存在同名的实例属性,那么它将优先于非数据描述符。
示例
下面是一个非数据描述符的例子,它提供了默认值,如果实例中未设置属性的话:
class Default:
def __init__(self, default):
self.default = default
def __get__(self, instance, owner):
return getattr(instance, '_value', self.default)
class MyClass:
attr = Default("default_value")
obj = MyClass()
print(obj.attr)
obj._value = "Tutorialspoint"
print(obj.attr)
输出
default_value
Tutorialspoint
数据描述符 vs. 非数据描述符
理解 Python 描述符中的数据描述符和非数据描述符的区别对于有效地利用它们的能力至关重要。
标准 |
数据描述符 |
非数据描述符 |
定义 |
实现了 __get__() 、__set__() 方法,可选地实现 __delete__() 方法。 |
只实现了 __get__() 方法。 |
方法 |
__get__(self, instance, owner)
__set__(self, instance, value)
__delete__(self, instance)(可选) |
__get__(self, instance, owner) |
优先级 |
优先于实例属性。 |
被实例属性覆盖。 |
使用场景 |
属性验证和强制实施。 管理属性(例如,属性)。 记录属性访问和修改。 强制只读属性。 |
方法绑定。 缓存。 提供默认值。 |
最后可以说,Python 中的描述符提供了一种强大的机制来管理属性的访问和修改。理解数据描述符和非数据描述符的区别以及它们的适当使用场合对于创建健壮且可维护的 Python 代码至关重要。通过利用描述符协议,开发人员可以实现诸如类型检查、缓存和只读属性等高级行为。