问题:
为什么描述符不能成为实例属性?
已经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
),简化了一些事情。
要实现每个实例描述符属性,您需要:
答案 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}
与其他两个建议的选项相比,这有明显的优势:
__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]
答案 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'
请注意,这不是完整的描述符实现,当然如果您决定使用它,则风险自负。