Python 描述符
描述符(Descriptor)
是 Python 中一个非常强大的特性,它允许我们自定义属性的访问行为。使用描述符,我们可以创建一些特殊的属性,在访问这些属性时执行自定义的逻辑,如数据验证、属性计算等。
类型:
**数据描述符:**用于修改属性的访问和修改行为。
**方法描述符:**用于修改方法的行为。
数据描述符:
**get:**在属性被访问时被调用。
**set:**在属性被设置时被调用。
**delete:**在属性被删除时被调用。
方法描述符:
**call:**在方法被调用时被调用。
下面我们来看一个具体的例子:
class MyDescriptor:
def __init__(self, initial_value=None):
self._value = initial_value
def __get__(self, instance, owner):
print(f"Getting value: {self._value}")
return self._value
def __set__(self, instance, value):
print(f"Setting value to: {value}")
self._value = value
def __delete__(self, instance):
print("Deleting value")
del self._value
class MyClass:
my_attr = MyDescriptor(42)
obj = MyClass()
print(obj.my_attr) # Output: Getting value: 42
obj.my_attr = 100 # Output: Setting value to: 100
del obj.my_attr # Output: Deleting value
在这个例子中,我们定义了一个 MyDescriptor
类,它实现了三个描述符协议方法:__get__
、__set__
和 __delete__
。这些方法分别在访问、设置和删除属性时被调用。
在 MyClass
中,我们定义了一个 my_attr
属性,它使用 MyDescriptor
作为描述符。当我们访问、设置或删除 obj.my_attr
时,相应的描述符方法会被调用,并执行我们自定义的逻辑。
描述符的要包括以下几点:
- 数据验证: 可以在
__set__
方法中添加数据验证逻辑,确保属性值符合预期。 - 属性计算: 在
__get__
方法中实现动态计算属性值的逻辑。 - 属性缓存: 使用描述符可以实现属性值的缓存,提高访问性能。
- 懒加载: 描述符可以用于实现懒加载,即在第一次访问属性时才计算或加载属性值。
- 属性权限控制: 可以使用描述符实现只读、只写或读写属性。
- 属性依赖管理: 描述符可以用于管理属性之间的依赖关系,确保属性值的一致性。
下面是一个的代码示例,实现了一个带有数据验证和属性缓存的描述符:
class CachedProperty:
def __init__(self, getter):
self.getter = getter
self._cache = {}
def __get__(self, instance, owner):
if instance is None:
return self
if instance not in self._cache:
self._cache[instance] = self.getter(instance)
return self._cache[instance]
def __set__(self, instance, value):
self._cache[instance] = value
def __delete__(self, instance):
if instance in self._cache:
del self._cache[instance]
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@CachedProperty
def full_name(self):
print("Calculating full name...")
return f"{self.name} Smith"
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("Age cannot be negative")
self._age = value
p = Person("John", 30)
print(p.full_name) # Output: Calculating full name... John Smith
print(p.full_name) # Output: John Smith (from cache)
p.age = 40
print(p.age) # Output: 40
p.age = -10 # Raises ValueError: Age cannot be negative
在这个场景下,self
和 instance
的区别如下:
-
self
:self
代表的是Person
类本身的实例,也就是Person
类的一个对象。- 在
__get__
方法中,self
指向的是Person
类的age
属性本身,而不是某个特定的Person
对象。
-
instance
:instance
代表的是正在访问age
属性的Person
对象实例。- 在
__get__
方法中,instance
指向的是调用age
属性的具体Person
对象,比如上例中的person
对象。
简单来说:
self
指向的是属性本身(即age
属性),而instance
指向的是正在访问该属性的对象实例。self
是属性级别的,而instance
是对象级别的。owner
是属性的类对象
这个区别很重要,因为在 __get__
方法中,我们需要根据具体的 Person
对象实例(instance
)来计算年龄,而不是直接使用 self
(即 age
属性本身)。
方法描述符
class Calculator:
def __init__(self):
self.num1 = 0
self.num2 = 0
def __call__(self, a, b):
self.num1 = a
self.num2 = b
return self
def add(self):
return self.num1 + self.num2
calc = Calculator()
result = calc(10, 20)
print(result) # 结果为30
解释:
__call__
方法描述符允许将Calculator
类本身作为函数调用。- 在
__call__
方法中,self
参数表示Calculator
对象,a
和b
参数表示方法参数。 - 方法返回
Calculator
对象本身,以便可以继续使用其方法。
优点:
- 简化了方法调用过程。
- 允许将类作为函数使用。
- 提高了代码可读性。
注意事项:
__call__
方法描述符仅适用于类。- 如果
__call__
方法描述符不正确定义,会导致错误。
实现缓存
在这个例子中,当我们尝试访问一个实例对象的属性时,__get__
方法会被调用。如果实例对象没有该属性的缓存值,它会调用 self.func(instance)
来计算属性值,并将其缓存在实例对象上。
这种技术被称为"惰性计算"(lazy evaluation),它可以提高性能,因为属性值只有在第一次被访问时才会计算。之后,后续的访问都会直接返回缓存的值,而不需要再次计算。
下面是一个更具体的例子:
class LazyProperty:
def __init__(self, func):
self.func = func
self.cache_name = f"_{func.__name__}"
def __get__(self, instance, owner):
if instance is None:
return self
if not hasattr(instance, self.cache_name):
value = self.func(instance)
setattr(instance, self.cache_name, value)
return getattr(instance, self.cache_name)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@LazyProperty
def full_name(self):
print("Calculating full name...")
return f"{self.name} Smith"
person = Person("Alice", 30)
print(person.full_name) # 输出: "Calculating full name..." 和 "Alice Smith"
print(person.full_name) # 输出: "Alice Smith"
在这个例子中,我们定义了一个 LazyProperty
描述符类,它在第一次访问 full_name
属性时计算并缓存该值。后续访问都会直接返回缓存的值,而不需要再次计算。
总的来说,self.func(instance)
是描述符对象用来计算属性值的方法调用。通过使用描述符,我们可以自定义属性的访问行为,实现惰性计算等优化手段,提高代码的性能和可维护性。
原文地址:https://blog.csdn.net/slowsnowscar/article/details/137472017
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!