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

Her*_*nan 21 python descriptor

对于这个问题:

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

有人回答说:

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

因为这是实施的方式__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
Run Code Online (Sandbox Code Playgroud)

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

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

  1. 创建每个实例类请参见此处
  2. 覆盖__getattribute__ 见这里

我知道之前已经提出过类似的问题,但我还没有找到真正的解释:

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

nne*_*neo 16

今年早些时候在Python列表中提出了这个确切的问题.我只想引用Ian G. Kelly的回答:

行为是设计的.首先,在类定义中保持对象行为简化了实现,并使实例检查更有意义.借用你的Register示例,如果"M"描述符是由某些实例而不是类定义的,那么知道对象"reg"是Register的一个实例并没有告诉我"reg.M"是否是一个有效属性或错误.因此,我需要使用try-except结构来保护"reg.M"的几乎所有访问,以防万一"reg"是错误的寄存器.

其次,类与实例的分离也有助于将对象行为与对象数据分开.考虑以下课程:

class ObjectHolder(object):
    def __init__(self, obj):
        self.obj = obj
Run Code Online (Sandbox Code Playgroud)

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

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

由于该类旨在保存任意对象,因此有人可能希望在那里存储描述符对象:

>>> holder.obj = property(lambda x: x.foo)
>>> print(holder.obj) <property object at 0x02415AE0>
Run Code Online (Sandbox Code Playgroud)

现在假设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'
Run Code Online (Sandbox Code Playgroud)

在这种情况下,ObjectHolder将无法简单地将属性对象保存为数据.仅仅将属性对象(描述符)分配给实例属性的行为改变 ObjectHolder 的行为.它不是将"holder.obj"视为一个简单的数据属性,而是在访问"holder.obj"时开始调用描述符协议,并最终将它们重定向到不存在且无意义的"holder.foo"属性,这当然是不是班级作者的意图.

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


eca*_*mur 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}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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]
Run Code Online (Sandbox Code Playgroud)