Python - 描述符



Python 描述符

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: the instance that the attribute is accessed through, or None when accessed through the owner class.
	 	owner: the owner class where the descriptor is defined.
	 	"""

参数

以下是此方法的参数 -

  • self:描述符实例。
  • instance:访问属性的类的实例。当通过类而不是实例访问属性时,它是 None。
  • owner:拥有描述符的类。

以下是 __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: the instance of the class where the attribute is being set.
	 	 value: the value to assign to the attribute.
	 	 """

参数

以下是此方法的参数 -

  • self:描述符实例。
  • 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) 	

输出

<__main__.Descriptor object at 0x000001E5423ED3D0>
<__main__.Descriptor object at 0x000001E5423ED3D0>

3. __delete__() 方法

描述符协议中的 __delete__() 方法允许我们控制从实例中删除属性时会发生什么。这对于在删除属性时管理资源、清理或强制执行约束非常有用。

语法

以下是 Python 描述符 __delete__() 方法的语法 -


def __delete__(self, instance):
	 	 """
	 	 instance: the instance of the class from which the attribute is being deleted.
	 	 """

参数

以下是此方法的参数 -

  • self:描述符实例。
  • instance:要删除属性的类的实例。

以下是 __set__() 方法的基本示例,其中确保分配给 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

# Example usage
p = Person("qikepu", 30)
print(p.name) 	
print(p.age) 		

del p.name 	 	
print(p.name)	

del p.age 	 	
print(p.age) 		

输出

qikepu
30
Deleting name from <__main__.Person object at 0x0000021A1A67E2D0>
None
Deleting age from <__main__.Person object at 0x0000021A1A67E2D0>
None

Python 描述符的类型

在 Python 中,描述符可以根据它们实现的方法大致分为两种类型。他们是 −

  • 数据描述符
  • 非数据描述符

为了更好地理解,我们来详细了解一下这两种类型的 python 描述符。

1. 数据描述符

数据描述器是 Python 中的一种描述符,用于定义 __get__() __set__() 方法。这些描述符优先于实例属性,即使存在同名的 instance 属性,也始终会调用描述符的 __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()

# Usage
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

2. 非数据描述符

非数据描述符是 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")

# Usage
obj = MyClass()
print(obj.attr) 	
obj._value = "qikepu"
print(obj.attr)	

输出

default_value
qikepu

数据描述符与非数据描述符

了解 python 描述符的数据描述符和非数据描述符之间的区别对于有效利用它们的功能至关重要。

标准 数据描述符 非数据描述符
定义 可选地实现 __get__()、__set__() 方法和 __delete__() 方法。 仅实现 __get__() 方法。
方法 __get__(self, instance, owner)
__set__(self, instance, value)
__delete__(self, instance) (可选)
__get__(self, instance, owner)
优先 优先于实例属性。 被实例属性覆盖。
使用案例 属性验证和实施、
托管属性(例如属性)、
记录属性访问和修改、
实施只读属性。
方法绑定、
缓存和提供
默认值..

最后,我们可以说 Python 中的 描述符提供了一种强大的机制来管理属性访问和修改。了解数据描述符和非数据描述符之间的区别及其适当的用例对于创建健壮且可维护的 Python 代码至关重要。

通过利用描述符协议,开发人员可以实现高级行为,例如类型检查、缓存和只读属性。