理解 __get__ 和 __set__ 以及 Python 描述符

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3798835/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 12:48:05  来源:igfitidea点击:

Understanding __get__ and __set__ and Python descriptors

pythondescriptor

提问by Matt Bronson

I am tryingto understand what Python's descriptors are and what they are useful for. I understand how they work, but here are my doubts. Consider the following code:

试图了解 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. Why do I need the descriptor class?

  2. What is instanceand ownerhere? (in __get__). What is the purpose of these parameters?

  3. How would I call/use this example?

  1. 为什么我需要描述符类?

  2. 什么是instanceowner这里?(在__get__)。这些参数的目的是什么?

  3. 我将如何调用/使用此示例?

采纳答案by li.davidm

The descriptor is how Python's propertytype is implemented. A descriptor simply implements __get__, __set__, etc. and is then added to another class in its definition (as you did above with the Temperature class). For example:

描述符是 Pythonproperty类型的实现方式。描述符只是实现__get____set__等,然后在其定义中添加到另一个类(就像上面对 Temperature 类所做的那样)。例如:

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

Accessing the property you assigned the descriptor to (celsiusin the above example) calls the appropriate descriptor method.

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

instancein __get__is the instance of the class (so above, __get__would receive temp, while owneris the class with the descriptor (so it would be Temperature).

instancein__get__是类的实例(所以在上面,__get__会接收到tempowner而是带有描述符的类(所以它会是Temperature)。

You need to use a descriptor class to encapsulate the logic that powers it. That way, if the descriptor is used to cache some expensive operation (for example), it could store the value on itself and not its class.

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

An article about descriptors can be found here.

可以在此处找到有关描述符的文章。

EDIT: As jchl pointed out in the comments, if you simply try Temperature.celsius, instancewill be None.

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

回答by andrew cooke

Why do I need the descriptor class?

为什么我需要描述符类?

It gives you extra control over how attributes work. If you're used to getters and setters in Java, for example, then it's Python's way of doing that. One advantage is that it looks to users just like an attribute (there's no change in syntax). So you can start with an ordinary attribute and then, when you need to do something fancy, switch to a descriptor.

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

An attribute is just a mutable value. A descriptor lets you execute arbitrary code when reading or setting (or deleting) a value. So you could imagine using it to map an attribute to a field in a database, for example – a kind of ORM.

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

Another use might be refusing to accept a new value by throwing an exception in __set__– effectively making the "attribute" read only.

另一种用途可能是通过抛出异常来拒绝接受新值__set__——有效地使“属性”只读。

What is instanceand ownerhere? (in __get__). What is the purpose of these parameters?

什么是instanceowner这里?(在__get__)。这些参数的目的是什么?

This is pretty subtle (and the reason I am writing a new answer here - I found this question while wondering the same thing and didn't find the existing answer that great).

这是非常微妙的(也是我在这里写一个新答案的原因——我在想同样的事情时发现了这个问题,但没有找到那么好的现有答案)。

A descriptor is defined on a class, but is typically called from an instance. When it's called from an instance both instanceand ownerare set (and you can work out ownerfrom instanceso it seems kinda pointless). But when called from a class, only owneris set – which is why it's there.

描述符在类上定义,但通常从实例调用。当它从一个实例中被调用instance并且owner被设置时(你可以从中计算出来ownerinstance所以它看起来有点毫无意义)。但是当从一个类中调用时, onlyowner被设置 - 这就是它存在的原因。

This is only needed for __get__because it's the only one that can be called on a class. If you set the class value you set the descriptor itself. Similarly for deletion. Which is why the ownerisn't needed there.

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

How would I call/use this example?

我将如何调用/使用此示例?

Well, here's a cool trick using similar classes:

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

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)

(I'm using Python 3; for python 2 you need to make sure those divisions are / 5.0and / 9.0). That gives:

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

100.0
32.0

Now there are other, arguably better ways to achieve the same effect in python (e.g. if celsius were a property, which is the same basic mechanism but places all the source inside the Temperature class), but that shows what can be done...

现在还有其他可以说更好的方法可以在 python 中实现相同的效果(例如,如果 celsius 是一个属性,这是相同的基本机制,但将所有源放在 Temperature 类中),但这显示了可以做什么......

回答by Gregory Kuhn

I tried (with minor changes as suggested) the code from Andrew Cooke's answer. (I am running python 2.7).

我尝试了(按照建议进行了细微的更改)安德鲁库克的答案中的代码。(我正在运行 python 2.7)。

The code:

编码:

#!/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)

The result:

结果:

C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212

With Python prior to 3, make sure you subclass from object which will make the descriptor work correctly as the getmagic does not work for old style classes.

对于 3 之前的 Python,请确保您从 object 子类化,这将使描述符正常工作,因为get魔术不适用于旧样式类。

回答by Aaron Hall

I am trying to understand what Python's descriptors are and what they can be useful for.

我试图了解 Python 的描述符是什么以及它们的用途。

Descriptors are class attributes (like properties or methods) with any of the following special methods:

描述符是具有以下任何特殊方法的类属性(如属性或方法):

  • __get__(non-data descriptor method, for example on a method/function)
  • __set__(data descriptor method, for example on a property instance)
  • __delete__(data descriptor method)
  • __get__(非数据描述符方法,例如在方法/函数上)
  • __set__(数据描述符方法,例如在属性实例上)
  • __delete__(数据描述符方法)

These descriptor objects can be used as attributes on other object class definitions. (That is, they live in the __dict__of the class object.)

这些描述符对象可以用作其他对象类定义的属性。(也就是说,它们存在于__dict__类对象的 中。)

Descriptor objects can be used to programmatically manage the results of a dotted lookup (e.g. foo.descriptor) in a normal expression, an assignment, and even a deletion.

描述符对象可用于以编程方式管理foo.descriptor普通表达式、赋值甚至删除中的点查找(例如)的结果。

Functions/methods, bound methods, property, classmethod, and staticmethodall use these special methods to control how they are accessed via the dotted lookup.

函数/方法、绑定方法、propertyclassmethodstaticmethod所有使用这些特殊方法来控制如何通过点状查找访问它们。

A data descriptor, like property, can allow for lazy evaluation of attributes based on a simpler state of the object, allowing instances to use less memory than if you precomputed each possible attribute.

像 一样的数据描述符property可以允许基于对象的更简单状态对属性进行延迟评估,与预先计算每个可能的属性相比,允许实例使用更少的内存。

Another data descriptor, a member_descriptor, created by __slots__, allow memory savings by allowing the class to store data in a mutable tuple-like datastructure instead of the more flexible but space-consuming __dict__.

member_descriptor由 创建的另一个数据描述符 a__slots__允许类将数据存储在可变的元组类数据结构中,而不是更灵活但占用空间的 ,从而节省内存__dict__

Non-data descriptors, usually instance, class, and static methods, get their implicit first arguments (usually named clsand self, respectively) from their non-data descriptor method, __get__.

非数据描述符,通常情况下,类和静态方法,得到他们的隐含的首参数(通常命名clsself从他们的非数据描述方法分别为,) __get__

Most users of Python need to learn only the simple usage, and have no need to learn or understand the implementation of descriptors further.

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

In Depth: What Are Descriptors?

深入:什么是描述符?

A descriptor is an object with any of the following methods (__get__, __set__, or __delete__), intended to be used via dotted-lookup as if it were a typical attribute of an instance. For an owner-object, obj_instance, with a descriptorobject:

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

  • obj_instance.descriptorinvokes
    descriptor.__get__(self, obj_instance, owner_class)returning a value
    This is how all methods and the geton a property work.

  • obj_instance.descriptor = valueinvokes
    descriptor.__set__(self, obj_instance, value)returning None
    This is how the setteron a property works.

  • del obj_instance.descriptorinvokes
    descriptor.__delete__(self, obj_instance)returning None
    This is how the deleteron a property works.

  • obj_instance.descriptor调用
    descriptor.__get__(self, obj_instance, owner_class)返回 avalue
    这是所有方法和get属性的工作方式。

  • obj_instance.descriptor = value调用
    descriptor.__set__(self, obj_instance, value)返回None
    这就是setter属性的工作方式。

  • del obj_instance.descriptor调用
    descriptor.__delete__(self, obj_instance)返回None
    这就是deleter属性的工作方式。

obj_instanceis the instance whose class contains the descriptor object's instance. selfis the instance of the descriptor(probably just one for the class of the obj_instance)

obj_instance是其类包含描述符对象的实例的实例。self描述符的实例(可能只是 的类的一个obj_instance

To define this with code, an object is a descriptor if the set of its attributes intersects with any of the required attributes:

为了用代码定义它,如果一个对象的属性集与任何必需的属性相交,则该对象是一个描述符:

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

A Data Descriptorhas a __set__and/or __delete__.
A Non-Data-Descriptorhas neither __set__nor __delete__.

数据描述符具有一个__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))

Builtin Descriptor Object Examples:

内置描述符对象示例:

  • classmethod
  • staticmethod
  • property
  • functions in general
  • classmethod
  • staticmethod
  • property
  • 一般功能

Non-Data Descriptors

非数据描述符

We can see that classmethodand staticmethodare Non-Data-Descriptors:

我们可以看到,classmethod并且staticmethod是非数据描述符:

>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)

Both only have the __get__method:

两者都只有__get__方法:

>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))

Note that all functions are also Non-Data-Descriptors:

请注意,所有函数也是非数据描述符:

>>> def foo(): pass
... 
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)

Data Descriptor, property

数据描述符, property

However, propertyis a Data-Descriptor:

然而,property是一个数据描述符:

>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])

Dotted Lookup Order

虚线查找顺序

These are important distinctions, as they affect the lookup order for a dotted lookup.

这些是重要的区别,因为它们会影响点查找的查找顺序。

obj_instance.attribute
  1. First the above looks to see if the attribute is a Data-Descriptor on the class of the instance,
  2. If not, it looks to see if the attribute is in the obj_instance's __dict__, then
  3. it finally falls back to a Non-Data-Descriptor.
  1. 首先上面查看属性是否是实例类上的数据描述符,
  2. 如果不是,则查看该属性是否在obj_instance's 中__dict__,然后
  3. 它最终回退到非数据描述符。

The consequence of this lookup order is that Non-Data-Descriptors like functions/methods can be overridden by instances.

这种查找顺序的结果是,像函数/方法这样的非数据描述符可以被实例覆盖

Recap and Next Steps

回顾和后续步骤

We have learned that descriptors are objects with any of __get__, __set__, or __delete__. These descriptor objects can be used as attributes on other object class definitions. Now we will look at how they are used, using your code as an example.

我们已经了解到,描述与任何对象__get____set____delete__。这些描述符对象可以用作其他对象类定义的属性。现在我们将看看它们是如何使用的,以您的代码为例。



Analysis of Code from the Question

从问题中分析代码

Here's your code, followed by your questions and answers to each:

这是您的代码,然后是您的问题和对每个问题的回答:

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. Why do I need the descriptor class?
  1. 为什么我需要描述符类?

Your descriptor ensures you always have a float for this class attribute of Temperature, and that you can't use delto delete the attribute:

您的描述符确保您始终为 的此类属性提供浮点数Temperature,并且您不能使用它del来删除该属性:

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

Otherwise, your descriptors ignore the owner-class and instances of the owner, instead, storing state in the descriptor. You could just as easily share state across all instances with a simple class attribute (so long as you always set it as a float to the class and never delete it, or are comfortable with users of your code doing so):

否则,您的描述符将忽略所有者类和所有者的实例,而是在描述符中存储状态。您可以使用简单的类属性轻松地在所有实例之间共享状态(只要您始终将其设置为类的浮点数并且永远不会删除它,或者您的代码用户这样做很舒服):

class Temperature(object):
    celsius = 0.0

This gets you exactly the same behavior as your example (see response to question 3 below), but uses a Pythons builtin (property), and would be considered more idiomatic:

这使您的行为与您的示例完全相同(请参阅下面对问题 3 的回复),但使用 Pythons 内置 ( 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. What is instance and owner here? (in get). What is the purpose of these parameters?
  1. 这里的实例和所有者是什么?(在get 中)。这些参数的目的是什么?

instanceis the instance of the owner that is calling the descriptor. The owner is the class in which the descriptor object is used to manage access to the data point. See the descriptions of the special methods that define descriptors next to the first paragraph of this answer for more descriptive variable names.

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

  1. How would I call/use this example?
  1. 我将如何调用/使用此示例?

Here's a demonstration:

这是一个演示:

>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>> 
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0

You can't delete the attribute:

您不能删除该属性:

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

And you can't assign a variable that can't be converted to a float:

并且您不能分配无法转换为浮点数的变量:

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

Otherwise, what you have here is a global state for all instances, that is managed by assigning to any instance.

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

The expected way that most experienced Python programmers would accomplish this outcome would be to use the propertydecorator, which makes use of the same descriptors under the hood, but brings the behavior into the implementation of the owner class (again, as defined above):

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

Which has the exact same expected behavior of the original piece of code:

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

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

Conclusion

结论

We've covered the attributes that define descriptors, the difference between data- and non-data-descriptors, builtin objects that use them, and specific questions about use.

我们已经介绍了定义描述符的属性、数据描述符和非数据描述符之间的区别、使用它们的内置对象以及有关使用的具体问题。

So again, how would you use the question's example? I hope you wouldn't. I hope you would start with my first suggestion (a simple class attribute) and move on to the second suggestion (the property decorator) if you feel it is necessary.

那么,你会如何使用这个问题的例子?我希望你不会。我希望您从我的第一个建议(一个简单的类属性)开始,如果您觉得有必要,请继续讨论第二个建议(属性装饰器)。

回答by wllbll

Why do I need the descriptor class?

为什么我需要描述符类?

Inspired by Fluent Pythonby Buciano Ramalho

受Buciano Ramalho 的Fluent Python启发

Imaging you have a class like this

想象一下你有一个这样的课程

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

We should validate the weight and price in avoid to assign them a negative number, we can write less code if we use descriptor as a proxy as this

我们应该验证权重和价格以避免给它们分配一个负数,如果我们使用描述符作为代理,我们可以编写更少的代码,因为这样

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)

then define class LineItem like this:

然后像这样定义 LineItem 类:

class LineItem(object):
     weight = Quantity()
     price = Quantity()

     def __init__(self, name, weight, price):
         self.name = name
         self.weight = weight
         self.price = price

and we can extend the Quantity class to do more common validating

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

回答by Yonks Somarl

You'd see https://docs.python.org/3/howto/descriptor.html#properties

你会看到https://docs.python.org/3/howto/descriptor.html#properties

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    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)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

回答by MSeifert

Before going into the details of descriptors it may be important to know how attribute lookup in Python works. This assumes that the class has no metaclass and that it uses the default implementation of __getattribute__(both can be used to "customize" the behavior).

在进入描述符的细节之前,了解 Python 中的属性查找是如何工作的可能很重要。这假设该类没有元类并且它使用的默认实现__getattribute__(两者都可用于“自定义”行为)。

The best illustration of attribute lookup (in Python 3.x or for new-style classes in Python 2.x) in this case is from Understanding Python metaclasses (ionel's codelog). The image uses :as substitute for "non-customizable attribute lookup".

在这种情况下,属性查找(在 Python 3.x 中或在 Python 2.x 中的新样式类中)的最佳说明来自理解 Python 元类(ionel 的代码日志)。该图像:用作“不可定制的属性查找”的替代品。

This represents the lookup of an attribute foobaron an instanceof Class:

这代表一个属性的查找foobarinstanceClass

enter image description here

在此处输入图片说明

Two conditions are important here:

这里有两个条件很重要:

  • If the class of instancehas an entry for the attribute name and it has __get__and __set__.
  • If the instancehas noentry for the attribute name but the class has one and it has __get__.
  • 如果 的类instance具有属性名称的条目并且它具有__get____set__
  • 如果instance已经没有了属性名称条目,但类有一个和它有__get__

That's where descriptors come into it:

这就是描述符的用武之地:

  • Data descriptorswhich have both __get__and __set__.
  • Non-data descriptorswhich only have __get__.
  • 具有__get__和 的数据描述符__set__
  • 非数据描述符中,只有拥有__get__

In both cases the returned value goes through __get__called with the instance as first argument and the class as second argument.

在这两种情况下,返回值都会通过__get__调用实例作为第一个参数和类作为第二个参数。

The lookup is even more complicated for class attribute lookup (see for example Class attribute lookup (in the above mentioned blog)).

对于类属性查找,查找更加复杂(参见例如类属性查找(在上面提到的博客中))。

Let's move to your specific questions:

让我们转到您的具体问题:

Why do I need the descriptor class?

为什么我需要描述符类?

In most cases you don't need to write descriptor classes! However you're probably a very regular end user. For example functions. Functions are descriptors, that's how functions can be used as methods with selfimplicitly passed as first argument.

在大多数情况下,您不需要编写描述符类!但是,您可能是一个非常普通的最终用户。例如函数。函数是描述符,这就是函数如何用作self隐式传递为第一个参数的方法。

def test_function(self):
    return self

class TestClass(object):
    def test_method(self):
        ...

If you look up test_methodon an instance you'll get back a "bound method":

如果你查看test_method一个实例,你会得到一个“绑定方法”:

>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>

Similarly you could also bind a function by invoking its __get__method manually (not really recommended, just for illustrative purposes):

同样,您也可以通过__get__手动调用函数来绑定函数(不是真正推荐,仅用于说明目的):

>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>

You can even call this "self-bound method":

你甚至可以称之为“自绑定方法”:

>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>

Note that I did not provide any arguments and the function did return the instance I had bound!

请注意,我没有提供任何参数,该函数确实返回了我绑定的实例!

Functions are Non-data descriptors!

函数是非数据描述符

Some built-in examples of a data-descriptor would be property. Neglecting getter, setter, and deleterthe propertydescriptor is (from Descriptor HowTo Guide "Properties"):

数据描述符的一些内置示例是property. 忽略gettersetterdeleterproperty描述符(从描述方法文档指南“属性”):

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)

Since it's a data descriptor it's invoked whenever you look up the "name" of the propertyand it simply delegates to the functions decorated with @property, @name.setter, and @name.deleter(if present).

由于它是一个数据描述符,因此每当您查找 的“名称”时都会调用它,property并且它只是委托给用@property@name.setter、 和@name.deleter(如果存在)修饰的函数。

There are several other descriptors in the standard library, for example staticmethod, classmethod.

标准库中还有其他几个描述符,例如staticmethod, classmethod.

The point of descriptors is easy (although you rarely need them): Abstract common code for attribute access. propertyis an abstraction for instance variable access, functionprovides an abstraction for methods, staticmethodprovides an abstraction for methods that don't need instance access and classmethodprovides an abstraction for methods that need class access rather than instance access (this is a bit simplified).

描述符的要点很简单(尽管您很少需要它们):用于属性访问的抽象通用代码。property是实例变量访问function的抽象,为方法staticmethod提供抽象,为不需要实例访问classmethod的方法提供抽象,并为需要类访问而不是实例访问的方法提供抽象(这有点简化)。

Another example would be a class property.

另一个例子是类属性

One fun example (using __set_name__from Python 3.6) could also be a property that only allows a specific type:

一个有趣的例子(__set_name__来自 Python 3.6)也可以是一个只允许特定类型的属性:

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

Then you can use the descriptor in a class:

然后你可以在类中使用描述符:

class Test(object):
    int_prop = TypedProperty(int)

And playing a bit with it:

并玩了一下:

>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10

>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>

Or a "lazy property":

或“懒惰的财产”:

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

These are cases where moving the logic into a common descriptor might make sense, however one could also solve them (but maybe with repeating some code) with other means.

在这些情况下,将逻辑移动到公共描述符中可能是有意义的,但是也可以通过其他方式解决它们(但可能需要重复一些代码)。

What is instanceand ownerhere? (in __get__). What is the purpose of these parameters?

什么是instanceowner这里?(在__get__)。这些参数的目的是什么?

It depends on how you look up the attribute. If you look up the attribute on an instance then:

这取决于您如何查找属性。如果您在实例上查找属性,则:

  • the second argument is the instance on which you look up the attribute
  • the third argument is the class of the instance
  • 第二个参数是您在其上查找属性的实例
  • 第三个参数是实例的类

In case you look up the attribute on the class (assuming the descriptor is defined on the class):

如果您在类上查找属性(假设描述符是在类上定义的):

  • the second argument is None
  • the third argument is the class where you look up the attribute
  • 第二个论点是 None
  • 第三个参数是您查找属性的类

So basically the third argument is necessary if you want to customize the behavior when you do class-level look-up (because the instanceis None).

所以基本上第三个参数是必要的,如果你想在你做类级查找时自定义行为(因为instanceis None)。

How would I call/use this example?

我将如何调用/使用此示例?

Your example is basically a property that only allows values that can be converted to floatand that is shared between all instances of the class (and on the class - although one can only use "read" access on the class otherwise you would replace the descriptor instance):

您的示例基本上是一个属性,它只允许可以转换为float并在类的所有实例之间共享的值(以及在类上 - 尽管只能在类上使用“读取”访问权限,否则您将替换描述符实例):

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

That's why descriptors generally use the second argument (instance) to store the value to avoid sharing it. However in some cases sharing a value between instances might be desired (although I cannot think of a scenario at this moment). However it makes practically no sense for a celsius property on a temperature class... except maybe as purely academic exercise.

这就是为什么描述符通常使用第二个参数 ( instance) 来存储值以避免共享它。然而,在某些情况下,可能需要在实例之间共享一个值(虽然我现在想不出一个场景)。然而,对于温度等级上的摄氏度属性来说,这几乎没有任何意义......除非可能是纯粹的学术练习。