我试图了解什么是Python的描述符以及它们可用于什么。
描述符是具有以下任何特殊方法的类属性(如属性或方法):
__get__
(非数据描述符方法,例如方法/函数)
__set__
(数据描述符方法,例如在属性实例上)
__delete__
(数据描述符方法)
这些描述符对象可用作其他对象类定义的属性。(也就是说,它们位于__dict__
类对象的中。)
描述符对象可用于以编程方式管理foo.descriptor
正则表达式,赋值甚至删除中的点分查找(例如)的结果。
函数/方法,绑定方法,property
,classmethod
和staticmethod
所有使用这些特殊的方法来控制它们是如何通过点查找访问。
像这样的数据描述符property
可以根据对象的简单状态对属性进行延迟评估,与实例中预先计算每个可能的属性相比,允许实例使用更少的内存。
member_descriptor
创建的另一个数据描述符a __slots__
通过允许类将数据存储在可变的类似元组的数据结构中而不是更灵活但占用空间的方法来节省内存__dict__
。
非数据描述符(通常是实例,类和静态方法)从其非数据描述符方法中获取其隐式第一个参数(通常分别命名为cls
和self
)__get__
。
大多数Python用户只需要学习简单的用法,而无需进一步学习或理解描述符的实现。
深入:什么是描述符?
描述符是具有以下任何一种方法(__get__
,__set__
或__delete__
)的对象,旨在通过点分查找来使用,就好像它是实例的典型属性一样。对于obj_instance
具有一个descriptor
对象的所有者对象,:
obj_instance.descriptor
调用
descriptor.__get__(self, obj_instance, owner_class)
返回a。value
这就是所有方法和get
on属性的工作方式。
obj_instance.descriptor = value
调用
descriptor.__set__(self, obj_instance, value)
返回None
这就是setter
on属性的工作方式。
del obj_instance.descriptor
调用
descriptor.__delete__(self, obj_instance)
返回None
这就是deleter
on属性的工作方式。
obj_instance
是实例,其类包含描述符对象的实例。self
是描述符的实例(可能只是的类的一个obj_instance
)
要使用代码定义此对象,如果对象的属性集与任何必需的属性相交,则该对象为描述符:
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
甲数据描述符具有一个__set__
和/或__delete__
。
一个非数据描述既没有__set__
也没有__delete__
。
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
内置描述符对象示例:
classmethod
staticmethod
property
- 一般功能
非数据描述符
我们可以看到,classmethod
和staticmethod
在非数据描述符:
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
两者都只有__get__
方法:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
请注意,所有函数也是非数据描述符:
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
数据描述符 property
但是,property
是一个数据描述符:
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
点分查找顺序
这些是重要的区别,因为它们会影响点分查找的查找顺序。
obj_instance.attribute
- 首先,上面的代码看一下该属性是否是实例类上的Data-Descriptor,
- 如果不是,它将查看该属性是否在
obj_instance
的中__dict__
,然后
- 最后,它归结为非数据描述符。
此查找顺序的结果是实例可以覆盖诸如函数/方法之类的非数据描述符。
回顾与下一步
我们已经了解到,描述与任何对象__get__
,__set__
或__delete__
。这些描述符对象可用作其他对象类定义的属性。现在,以您的代码为例,看看它们的用法。
从问题中分析代码
这是您的代码,然后是每个问题和答案:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
- 为什么需要描述符类?
您的描述符可确保您始终为的此类属性具有浮点数Temperature
,并且不能用于del
删除该属性:
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
否则,描述符将忽略所有者类和所有者实例,而是将状态存储在描述符中。您可以使用一个简单的class属性轻松地在所有实例之间共享状态(只要您始终将其设置为该类的float且从不删除它,或者让您的代码用户满意):
class Temperature(object):
celsius = 0.0
这样可以使您获得与示例完全相同的行为(请参见下面对问题3的回答),但是使用Python内置(property
),并且会被认为更惯用:
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
- 什么是实例和所有者?(获得)。这些参数的目的是什么?
instance
是调用描述符的所有者的实例。所有者是使用描述符对象管理对数据点的访问的类。有关更多描述性变量名,请参见此答案第一段旁边的定义描述符的特殊方法的描述。
- 我将如何调用/使用此示例?
这是一个示范:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
您无法删除该属性:
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
而且您不能分配不能转换为浮点数的变量:
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
否则,您这里拥有的是所有实例的全局状态,可以通过分配给任何实例来进行管理。
最有经验的Python程序员完成此结果的预期方式是使用property
装饰器,该装饰器在幕后使用相同的描述符,但将行为带入了owner类的实现(同样,如上所定义):
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
具有与原始代码完全相同的预期行为:
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
结论
我们已经介绍了定义描述符的属性,数据描述符和非数据描述符之间的区别,使用它们的内置对象以及有关使用的特定问题。
同样,您将如何使用问题的示例?我希望你不会。我希望您从我的第一个建议(一个简单的类属性)开始,如果有必要,请继续进行第二个建议(属性装饰器)。
self
和之间有什么区别instance
?