2012-09-26 85 views
19

属性的问题:描述符的情况下在Python

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

它一直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具有多个支柱的情况:我需要使用唯一的名称标识值和乘数(像here有每个实例描述符对象将允许存储。 。_multiplier(和_value)在描述符本身,简化了一些东西

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

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

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

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

回答

9

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

我不知道如何推荐这个,但你可以在实例存储从描述实例映射属性值:

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] 
+0

您可以通过** break object orientation **扩展您的意思吗?其次,我用这种方法看到的问题是如何提供一个简单的API来改变乘数。用户将不得不像'obj._multiplier [Obj.val] = 10'这样做。这可以包含在一个函数'def change_multiplier(self,attr_name,new_value)'中,但如果Prop属性有多个属性,则不会很好地缩放。像def prop(self,attr_name):返回自我。__dict __ [attr_name]'可以用来做'obj.prop('val')。multiplier = 10'。 – Hernan

+0

@ Hernan有一个通常的假设,即实例具有相同的类型;违反了这一点,各种事情将会破裂。就改变乘数而言,也许是代理财产? - 见上面的编辑。 – ecatmur

+0

确实所有实例的类型都不相同,但是您可能会创建子类,因此isinstance仍然可以工作。关于代理人,我写了类似的东西,但我不确定这是一个好主意。基本上,'obj.prop('val')'返回一个知道'obj'和'val'的代理对象。当你执行'obj.prop('val')。multiplier = 10'时,它会写入'obj._multiplier [val] = 10'。我只是不确定它会如何维护。 – Hernan

15

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

该行为是由设计。首先,保持类定义中的对象行为简化了实现,并使实例 检查更有意义。借用你的注册示例,如果“M”描述符是由某些实例定义的,而不是由类定义的,那么知道对象“reg”是注册实例的 并不告诉 我有关“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将无法简单地将属性 对象保存为数据。仅将属性对象 描述符分配给实例属性的行为将更改ObjectHolder的行为 。与其将“holder.obj”视为简单的数据属性,它将开始在访问 时调用描述符协议以“holder.obj”并最终将其重定向到不存在的和 无意义的“holder.foo”属性,这当然不是该课程的作者所期望的。

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

+0

到Python列表的链接都死了。新链接是https://mail.python.org/pipermail/python-list/2012-January/631340.html? –

+0

是的。我将编辑它。 – nneonneo

相关问题