描述符作为python中的实例属性

时间:2012-09-26 10:45:43

标签: python descriptor

问题:

  

为什么描述符不能成为实例属性?

已经answered

  

描述符对象需要存在于类中,而不是实例

因为这是实现__getattribute__的方式。

一个简单的例子。考虑一个描述符:

class Prop(object):

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj._value * obj._multiplier

    def __set__(self, obj, value):
        if obj is None:
            return self
        obj._value = value

class Obj(object):

    val = Prop()

    def __init__(self):
        self._value = 1
        self._multiplier = 0

考虑每个obj有多个Prop的情况:我需要使用唯一的名称来标识值和乘数(如here。。拥有每个实例描述符对象将允许存储{{1}在描述符本身中(和_multiplier),简化了一些事情。

要实现每个实例描述符属性,您需要:

  1. 创建每个实例类See here
  2. 覆盖_value See here
  3. 我知道之前已经提出过类似的问题,但我没有找到真正的解释:

    1. 为什么Python是这样设计的?
    2. 存储描述符需要的信息的建议方法是什么?每个实例是什么?

3 个答案:

答案 0 :(得分:16)

今年早些时候,这个确切的问题是raised on Python-list。我只想引用Ian G. Kelly's response

  

行为是设计的。首先,保持对象的行为   类定义简化了实现,也简化了实例   检查更有意义。借用你的Register示例,如果是“M”   描述符是由某些实例而不是由类定义的   知道对象“reg”是Register的一个实例并没有说明   关于“reg.M”是有效属性还是错误的任何事情。如   结果,我需要保护几乎所有“reg.M”的访问权限   try-except构造,以防“reg”是错误的寄存器。

     

其次,类与实例的分离也有助于你保持   对象行为与对象数据分开。考虑以下   类:

class ObjectHolder(object):
    def __init__(self, obj):
        self.obj = obj
     

不要担心这个类可能对什么有用。只知道   它意味着保持并提供对任意Python的无限制访问   对象:

>>> holder = ObjectHolder(42)
>>> print(holder.obj) 42
>>> holder.obj = range(5)
>>> print(holder.obj) [0, 1, 2, 3, 4]
     

由于该类旨在保存任意对象,因此它甚至有效   有人可能想在那里存储一个描述符对象:

>>> holder.obj = property(lambda x: x.foo)
>>> print(holder.obj) <property object at 0x02415AE0>
     

现在假设Python调用了描述符协议   存储在实例属性中的描述符:

>>> holder = ObjectHolder(None)
>>> holder.obj = property(lambda x: x.foo)
>>> print(holder.obj)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'ObjectHolder' object has no attribute 'foo'
     

在这种情况下,ObjectHolder将无法简单地保存属性   对象作为数据。仅仅是分配属性对象的行为,a   描述符,实例属性将更改行为   ObjectHolder。而不是将“holder.obj”视为简单数据   属性,它将开始在访问时调用描述符协议   到“holder.obj”并最终将它们重定向到不存在的和   无意义的“holder.foo”属性,这当然不是什么了   该课程的作者。

如果您希望能够支持描述符的多个实例,只需使该描述符的构造函数采用名称参数(前缀),并使用该名称为添加的属性添加前缀。您甚至可以在类实例中创建一个名称空间对象(字典)来保存所有新属性实例。

答案 1 :(得分:11)

许多高级功能仅在类而不是实例上定义时才有效;例如,所有特殊方法。除了使代码评估更有效之外,这还明确了实例和类型之间的分离,否则这些分离将会崩溃(因为当然所有类型都是对象)。

我不确定这是怎么推荐的,但你可以在实例中存储从描述符实例到属性值的映射:

class Prop(object):
     def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj._value * obj._multiplier[self]

    def __set__(self, obj, value):
        if obj is None:
            return self
        obj._value = value

class Obj(object):
    val = Prop()

    def __init__(self):
        self._value = 1
        self._multiplier = {Obj.val: 0}

与其他两个建议的选项相比,这有明显的优势:

  1. 每个实例类会破坏对象方向并增加内存使用量;
  2. 覆盖__getattribute__效率低下(因为所有属性访问都必须通过重写的特殊方法)并且很脆弱。

  3. 作为替代方案,您可以使用代理属性:

    class PerInstancePropertyProxy(object):
        def __init__(self, prop):
            self.prop = prop
        def __get__(self, instance, owner):
            if instance is None:
                return self
            return instance.__dict__[self.prop].__get__(instance, owner)
        def __set__(self, instance, value):
            instance.__dict__[self.prop].__set__(instance, value)
    class Prop(object):
        def __init__(self, value, multiplier):
            self.value = value
            self.multiplier = multiplier
        def __get__(self, instance, owner):
            if instance is None:
                return self
            return self.value * self.multiplier
        def __set__(self, instance, value):
            self.value = value
    class Obj(object):
        val = PerInstancePropertyProxy('val')
        def __init__(self):
            self.__dict__['val'] = Prop(1.0, 10.0)
        def prop(self, attr_name):
            return self.__dict__[attr_name]
    

答案 2 :(得分:1)

在Python 3.6中,这可以很容易地完成。也许它并不像预期的那样但是嘿,如果它有效,对吧? ;)

Python 3.6 adds the __set_name__ method

  

object.__set_name__(self, owner, name)

     

创建拥有类所有者时调用。描述符已分配给name。

     

版本3.6中的新功能。

使用此名称将内部值存储在实例的dict中似乎工作正常。

>>> class Prop:
...     def __set_name__(self, owner, name):
...         self.name = name
...     def __get__(self, instance, owner):
...         print('get')
...         return instance.__dict__.setdefault(self.name, None)
...     def __set__(self, instance, value):
...         print('set')
...         instance.__dict__[self.name] = value
... 
>>> class A:
...     prop = Prop()
... 
>>> a = A()
>>> a.prop = 'spam'
set
>>> a.prop
get
'spam'

请注意,这不是完整的描述符实现,当然如果您决定使用它,则风险自负。