【问题标题】:Descriptors as instance attributes in python描述符作为python中的实例属性
【发布时间】:2012-09-17 23:24:25
【问题描述】:

问题:

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

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。拥有每个实例描述符对象将允许存储 _multiplier(以及_value) 在描述符本身中,简化了一些事情。

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

  1. 为每个实例创建一个类See here
  2. 覆盖__getattribute__ See here

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

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

【问题讨论】:

    标签: python descriptor


    【解决方案1】:

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

    行为是设计使然。首先,保持对象行为 类定义简化了实现,也使实例化 检查更有意义。借用您的注册示例,如果“M” 描述符是由一些实例而不是类定义的,那么 知道对象“reg”是 Register 的一个实例并不能说明 关于“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 将无法简单地持有该属性 对象作为数据。仅分配属性对象的行为, 描述符,到一个实例属性会改变行为 对象持有者。而不是将“holder.obj”视为简单数据 属性,它将开始在访问时调用描述符协议 到“holder.obj”并最终将它们重定向到不存在的和 无意义的“holder.foo”属性,这肯定不是什么 预期课程的作者。

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

    【讨论】:

      【解决方案2】:

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

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

      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__ 效率低下(因为所有属性访问都必须通过覆盖的特殊方法)并且很脆弱。

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

      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]
      

      【讨论】:

      • 你能不能扩展一下break objectorientation的意思。其次,我在这种方法中看到的问题是如何提供一个简单的 API 来更改乘数。用户必须执行类似obj._multiplier[Obj.val] = 10 的操作。这可以包装在函数def change_multiplier(self, attr_name, new_value) 中,但如果Prop 具有多个属性,则不能很好地扩展。 def prop(self, attr_name): return self.__dict__[attr_name] 之类的东西可以用来做obj.prop('val').multiplier = 10 之类的东西。
      • @Hernan 通常假设实例具有相同的类型;违反这一点,各种事情都会破裂。就改变乘数而言,也许是代理属性? - 见上面的编辑。
      • 确实所有实例都不会属于同一类型,但是您可以创建子类,因此 isinstance 仍然可以工作。关于代理,我写了这样的东西,但我不确定这是一个好主意。基本上,obj.prop('val') 返回一个知道objval 的代理对象。当您执行obj.prop('val').multiplier = 10 时,它会写入obj._multiplier[val] = 10。我只是不确定它的可维护性。
      【解决方案3】:

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

      Python 3.6 adds the __set_name__ method:

      object.__set_name__(self, owner, name)

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

      3.6 版中的新功能。

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

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

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

      【讨论】:

      • 问题:这不是一个完整的描述符实现是什么意思?据我所知,这是一个完全适用的描述符示例。
      • instance.__dict____slots__ 的课程怎么样?
      猜你喜欢
      • 2018-07-10
      • 2011-10-24
      • 1970-01-01
      • 2012-10-02
      • 1970-01-01
      • 2014-05-02
      • 2012-05-19
      • 2013-11-18
      • 1970-01-01
      相关资源
      最近更新 更多