理解uuu get uuu和uuu set uuu和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

我试图理解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()
  • 为什么我需要描述符类

  • 什么是
    实例
    所有者
    ?(在
    \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