了解__get__和__set__以及Python描述符

了解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, value): self.value = float(value) class Temperature(object): celsius = Celsius() 
  1. 为什么我需要描述符类? 请用这个例子或者你认为更好的例子来解释。

  2. 什么是instanceowner ? (在__get__ )。 所以我的问题是,这里的第三个参数的目的是什么?

  3. 我将如何调用/使用这个例子?

描述符是Python的propertytypes是如何实现的。 描述符只是简单地实现了__get____set__等,然后被添加到其定义的另一个类中(就像上面的Temperature类一样)。 例如:

 temp=Temperature() temp.celsius #calls celsius.__get__ 

访问您分配给描述符的属性(上例中的celsius )调用适当的描述符方法。

__get__中的实例是类的实例(如上所述, __get__将接收temp ,而owner是具有描述符的类(所以它将是Temperature )。

您需要使用描述符类来封装为其提供支持的逻辑。 这样,如果描述符被用于caching一些昂贵的操作(例如),它可以将值存储在自身而不是它的类。

有关描述符的文章可以在http://martyalchin.com/2007/nov/23/python-descriptors-part-1-of-2/上find

编辑:正如jchl在评论中指出,如果你只是尝试Temperature.celsiusinstance将是None

为什么我需要描述符类? 请用这个例子或者你认为更好的例子来解释。

它给你额外的控制属性如何工作。 例如,如果你习惯于java中的getter和setter,那么这是python的方式。 一个好处是,它看起来像用户一样的属性(语法没有改变)。 所以你可以从一个普通的属性开始,然后,当你需要做一些奇特的事情时,切换到一个描述符。

一个属性只是一个可变的值。 描述符允许您在读取或设置(或删除)值时执行任意代码。 所以你可以想象使用它来将一个属性映射到数据库中的一个字段,例如 – 一种ORM。

另一个用法可能是通过在__set__抛出一个exception来拒绝接受一个新的值 – 有效地使“属性”是只读的。

什么是实例和所有者? (在__get__ )。 所以我的问题是,这里的第三个参数的目的是什么?

这是非常微妙的(我在这里写一个新的答案 – 我发现这个问题,同时想知道同样的事情,没有find现有的答案,伟大的原因)的原因。

描述符是在一个类上定义的,但通常是从一个实例中调用的。 当从一个实例调用时, instanceowner都被设置(你可以从instance找出owner ,所以它似乎有点没有意义)。 但是当从一个class级调用,只有owner被设置 – 这就是为什么它在那里。

这只是__get__需要的,因为它是唯一可以在类上调用的。 如果你设置了类的值,你可以设置描述符本身。 类似地删除。 这就是为什么owner不需要那里。

我将如何调用/使用这个例子?

好吧,使用类似的类有一个很酷的技巧:

 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) 

(我使用python 3;对于python 2,你需要确保这些分区是/ 5.0/ 9.0 )。 这给了:

 100.0 32.0 

现在还有其他的,可以说是更好的方式来达到python相同的效果(例如,如果摄氏是一个属性,这是一个基本的机制,但放置在温度类的所有来源),但是这表明可以做什么…

**我想了解Python的描述符是什么以及它们是什么

可以用于。**

简答

描述符是具有__get____set____delete__任何一个的对象。 这些描述符对象可以用作其他对象类定义的属性。

描述符对象可用于以编程方式pipe理正常expression式,赋值甚至删除操作中的虚线查找(例如foo.descriptor )的结果。 绑定方法, propertyclassmethod方法和staticmethod方法都使用这些特殊的方法来pipe理如何通过虚线查找来访问它们。

property这样的数据描述符可以允许基于更简单的对象状态对属性进行懒惰评估,允许实例使用比预先计算每个可能的属性更less的内存。 由__slots__创build的另一个数据描述符通过允许类将数据存储在可变的元组数据结构中而不是更灵活但空间更大的__dict__来节省内存。

非数据描述符,类和实例方法从它们的非数据描述符方法__get__获得它们的隐式第一个参数(通常分别命名为clsself )。

大多数Python用户只需要学习简单的用法,而不需要进一步学习或理解描述符的实现。


什么是描述符?

描述符是具有以下任何方法( __get____set____delete__ )的对象,旨在通过虚线查找来使用,就好像它是实例的典型属性。 对于拥有descriptor对象的所有者对象obj_instance

  • descriptor.__get__(self, obj_instance, owner_class) (返回value
    被调用
    obj_instance.descriptor

  • descriptor.__set__(self, obj_instance, value) (返回None
    被调用
    obj_instance.descriptor = value

  • descriptor.__delete__(self, obj_instance) (返回None
    被调用
    del obj_instance.descriptor

obj_instance是其类包含描述符对象实例的实例。 self描述符的实例(可能只是obj_instance类的一个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)) 

内build描述符对象示例:

  • classmethod
  • staticmethod
  • property
  • 一般的function

非数据描述符

我们可以看到, classmethodstaticmethod是非数据描述符:

 >>> 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 
  1. 首先,上面看看是否该属性是实例类的Data-Descriptor,
  2. 如果不是,那么它看起来是否属于obj_instance__dict__ ,然后
  3. 它最终会回落到一个非数据描述符。

这个查找顺序的结果是非数据描述符(如函数/方法)可以被实例覆盖 。

总结和下一步

我们已经知道描述符是__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() 
  1. 为什么我需要描述符类? 请用这个例子或者你认为更好的例子来解释。

您的描述符可以确保您始终拥有Temperature此类属性的浮点数,并且不能使用del删除该属性:

 >>> t1 = Temperature() >>> del t1.celsius Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: __delete__ 

否则,描述符将忽略所有者的所有者类和实例,而是将状态存储在描述符中。 你可以用简单的类属性轻松地在所有实例之间共享状态(只要你总是把它设置为类的一个浮点,永远不要删除它,或者对你的代码的用户来说很舒服):

 class Temperature(object): celsius = 0.0 

这会得到与你的例子完全相同的行为(参见下面的问题3的回答),但是使用Pythons的内build( 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) 
  1. 什么是instanceowner ? (在__get__ )。 所以我的问题是,这里的第三个参数的目的是什么?

instance是调用描述符的所有者的实例。 所有者是描述符对象用于pipe理对数据点的访问的类。 有关更多描述性variables名,请参阅此答案第一段旁边的描述符定义的特殊方法的描述。

  1. 我将如何调用/使用这个例子?

这是一个示范:

 >>> 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__ 

而且你不能分配一个不能转换成浮点的variables:

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

否则,你在这里是所有实例的全局状态,这是通过分配给任何实例来pipe理的。

大多数有经验的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) 

它具有与原始代码完全相同的预期行为:

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

结论

我们已经介绍了定义描述符的属性,数据描述符与非数据描述符之间的区别,使用它们的内build对象以及有关使用的特定问题。

那么,你将如何使用这个问题的例子呢? 我希望你不会。 我希望你能从我的第一个build议(一个简单的类属性)开始,如果你觉得有必要的话,可以转到第二个build议(属性装饰器)。

为什么我需要描述符类? 请用这个例子或者你认为更好的例子来解释。

灵感来自Buciano Ramalho的stream畅Python

你有一个这样的class级的影像

 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 

我们应该validation重量和价格,避免给他们一个负数,如果我们使用描述符作为代理,我们可以写更less的代码

 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) 

然后像这样定义类LineItem:

 class LineItem(object): weight = Quantity() price = Quantity() def __init__(self, name, weight, price): self.name = name self.weight = weight self.price = price 

我们可以扩展Quantity类来做更常见的validation

我试着(根据build议进行小的修改)来自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 

使用3之前的Python,确保你的子类从对象,这将使描述符正常工作,因为得到魔法不适用于旧的风格类。