理解uuu get uuu和uuu set uuu和Python描述符
我试图理解Python的描述符是什么以及它们的用途。我理解它们是如何工作的,但我对此表示怀疑。考虑下面的代码:理解uuu get uuu和uuu set uuu和Python描述符,python,descriptor,Python,Descriptor,我试图理解Python的描述符是什么以及它们的用途。我理解它们是如何工作的,但我对此表示怀疑。考虑下面的代码: 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, valu
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()
实例
和所有者
?(在\uuuu获取
中)。这些参数的用途是什么描述符是Python的
属性
类型的实现方式。描述符只是实现\uuuuu get\uuuuuu
,\uuuu set\uuuuuuuu
等,然后添加到其定义中的另一个类中(正如上面对温度类所做的那样)。例如:
temp=Temperature()
temp.celsius #calls celsius.__get__
访问为其分配描述符的属性(在上例中为摄氏度)将调用相应的描述符方法
中的实例
是类的实例(如上所述,实例
将接收临时
,而所有者
是带有描述符的类(因此它将是温度
)
您需要使用描述符类来封装为其提供动力的逻辑。这样,如果描述符用于缓存某些昂贵的操作(例如),它可以将值存储在自身而不是其类上
可以找到一篇关于描述符的文章
编辑:正如jchl在评论中指出的,如果您只是尝试Temperature.centrics
,instance
将是None
为什么我需要描述符类
它为您提供了对属性工作方式的额外控制。例如,如果您习惯于Java中的getter和setter,那么这就是Python的方法。一个优点是,在用户看来,它就像一个属性(语法没有变化)。因此,您可以从一个普通属性开始,然后,当您需要做一些有趣的事情时,切换到描述符
属性只是一个可变值。描述符允许您在读取或设置(或删除)值时执行任意代码。因此,您可以想象使用它将属性映射到数据库中的字段,例如,一种ORM
另一种用法可能是通过在\uuuuu set\uuuuu
中抛出异常来拒绝接受新值,从而有效地使“属性”为只读
这里的实例
和所有者
是什么?(在\uu获取
中)。这些参数的用途是什么
这是相当微妙的(也是我在这里写一个新答案的原因——我发现这个问题的同时也在想同样的事情,但没有发现现有的答案那么好)
描述符是在类上定义的,但通常是从实例中调用的。当从实例中调用描述符时,instance
和owner
都已设置(并且您可以从instance
中计算出owner
,所以它似乎没有意义).但是当从类调用时,只设置了owner
,这就是它存在的原因
这仅适用于\uuuu get\uuuu
,因为它是唯一可以在类上调用的函数。如果设置类值,则设置描述符本身。删除时也是如此。这就是不需要所有者的原因
我将如何调用/使用此示例
下面是一个使用类似类的很酷的技巧:
class Celsius:
def __get__(self, instance, owner):
return 5 * (instance.fahrenheit - 32) / 9
def __set__(self, instance, value):
instance.fahrenheit = 32 + 9 * value / 5
class Temperature:
celsius = Celsius()
def __init__(self, initial_f):
self.fahrenheit = initial_f
t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.fahrenheit)
(我使用的是Python3;对于Python2,您需要确保这些划分是/5.0
和/9.0
)。这将提供:
100.0
32.0
现在有其他可以说更好的方法在python中实现同样的效果(例如,如果celsius是一个属性,这是相同的基本机制,但将所有源代码都放在Temperature类中),但这表明了可以做什么…我尝试了Andrew Cooke答案中的代码(建议做一些小改动)。(我正在运行python 2.7)
守则:
#!/usr/bin/env python
class Celsius:
def __get__(self, instance, owner): return 9 * (instance.fahrenheit + 32) / 5.0
def __set__(self, instance, value): instance.fahrenheit = 32 + 5 * value / 9.0
class Temperature:
def __init__(self, initial_f): self.fahrenheit = initial_f
celsius = Celsius()
if __name__ == "__main__":
t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.fahrenheit)
结果是:
C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212
C:\Users\gkuhn\Desktop>python test2.py
212
对于Python 3之前的版本,请确保从object中创建子类,这将使描述符正常工作,因为get魔术不适用于旧式类
我试图理解Python的描述符是什么,它们有什么用处。
描述符是具有以下任何特殊方法的类属性(如属性或方法):
\uuuuu get\uuuuu
(非数据描述符方法,例如在方法/函数上)
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
(数据描述符方法,例如在属性实例上)
\uuuu删除\uuuuu
(数据描述符方法)
这些描述符对象可以用作其他对象类定义上的属性(也就是说,它们位于类对象的\uu dict\uu
中)
描述符对象可用于以编程方式管理普通表达式、赋值甚至删除中的点式查找(例如foo.Descriptor
)的结果
函数/方法、绑定方法、property
、classmethod
和staticmethod
都使用这些特殊方法来控制如何通过点查找访问它们
数据描述符,如属性
,可以允许基于对象的简单状态对属性进行延迟计算,从而允许实例使用比预先计算每个可能属性更少的内存
另一个数据描述符是由创建的成员描述符
,它允许类将数据存储在类似元组的可变数据结构中,而不是更灵活但占用空间的\uuuuu dict
,从而节省内存
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))
非数据描述符,通常是实例、类和静态方法,从它们的非数据描述符方法\uuu get\uuu
中获取它们的隐式第一个参数(通常分别命名为cls
和self
)
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))
大多数Python用户只需要学习简单的用法,并且
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
obj_instance.attribute
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()
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
class Temperature(object):
celsius = 0.0
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()
>>> 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
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
class LineItem:
price = 10.9
weight = 2.1
def __init__(self, name, price, weight):
self.name = name
self.price = price
self.weight = weight
item = LineItem("apple", 2.9, 2.1)
item.price = -0.9 # it's price is negative, you need to refund to your customer even you delivered the apple :(
item.weight = -0.8 # negative weight, it doesn't make sense
class Quantity(object):
__index = 0
def __init__(self):
self.__index = self.__class__.__index
self._storage_name = "quantity#{}".format(self.__index)
self.__class__.__index += 1
def __set__(self, instance, value):
if value > 0:
setattr(instance, self._storage_name, value)
else:
raise ValueError('value should >0')
def __get__(self, instance, owner):
return getattr(instance, self._storage_name)
class LineItem(object):
weight = Quantity()
price = Quantity()
def __init__(self, name, weight, price):
self.name = name
self.weight = weight
self.price = price
def test_function(self):
return self
class TestClass(object):
def test_method(self):
...
>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>
>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>
>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>
class Property(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
class TypedProperty(object):
__slots__ = ('_name', '_type')
def __init__(self, typ):
self._type = typ
def __get__(self, instance, klass=None):
if instance is None:
return self
return instance.__dict__[self._name]
def __set__(self, instance, value):
if not isinstance(value, self._type):
raise TypeError(f"Expected class {self._type}, got {type(value)}")
instance.__dict__[self._name] = value
def __delete__(self, instance):
del instance.__dict__[self._name]
def __set_name__(self, klass, name):
self._name = name
class Test(object):
int_prop = TypedProperty(int)
>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10
>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>
class LazyProperty(object):
__slots__ = ('_fget', '_name')
def __init__(self, fget):
self._fget = fget
def __get__(self, instance, klass=None):
if instance is None:
return self
try:
return instance.__dict__[self._name]
except KeyError:
value = self._fget(instance)
instance.__dict__[self._name] = value
return value
def __set_name__(self, klass, name):
self._name = name
class Test(object):
@LazyProperty
def lazy(self):
print('calculating')
return 10
>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius = 20 # setting it on one instance
>>> t2.celsius # looking it up on another instance
20.0
>>> Temperature.celsius # looking it up on the class
20.0