在Python方法中设置属性
我想在一个类的方法里设置一个只读属性。
我已经试过这个:
class Foo(object):
def __init__(self, v):
self._set('_v', v)
def _set(self, attr, v):
setattr(self, attr, v)
setattr(Foo, attr[1:], property(lambda self: getattr(self, attr)))
但是效果很糟糕。有没有其他的方法呢?我需要做的是设置这个属性:
class Foo(object):
def __init__(self, v):
self._v = v
@ property
def v(self):
return self._v
>>> f = Foo(42)
>>> f.v
42
>>> f.v = 41
AttributeError: can't set attribute ## This is what I want: a read-only attribute
但我需要在一个方法里做到这一点。还有其他的办法吗?
谢谢,
rubik
附言:我已经查看过这个帖子,但它没有解决我的问题:在方法中使用Python的property()
编辑:我不能使用property
,因为我想在一个方法里设置它。我只能在外部使用property
:
class Foo(object):
def __init__(self, v):
self._v = v
@ property
def v(self):
return self._v
## ...OR
def getv(self):
return self._v
v = property(getv)
而且我不能这样做,因为我不知道属性的名字,我必须动态地设置它。像这样:
class Foo(object):
def __init__(self, v):
self._set_property_from_inside('v', v)
>>> f = Foo(42)
>>> f.v
42
4 个回答
class Foo(object):
def __getattr__(self, name):
return getattr(self, "_" + name)
def __setattr__(self, name, value):
if name.startswith('_'):
self.__dict__[name] = value
else:
raise ValueError("%s is read only" % name)
>>> f = Foo()
>>> f.x = 5
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 8, in __setattr__
ValueError: x is read only
>>> f._x = 5
>>> f.x
5
然后:
我觉得你在找的是 Python 的 描述符。
class MyDescriptor(object):
def __init__(self, protected_attr_name):
self.attr = protected_attr_name
def __get__(self, obj, objtype):
return getattr(obj, self.attr)
def __set__(self, obj, value):
#setattr(obj, self.attr, value)
raise AttributeError("Can't set attribute")
class Foo(object):
def __init__(self, k, v):
setattr(self.__class__, k, MyDescriptor("_" + k))
setattr(self, "_" + k, v)
f = Foo("v", 42)
print f.v # Prints 42
try:
f.v = 32
except AttributeError:
pass
print f.v # Prints 42
在这里,你可以通过 __get__
和 __set__
方法来控制访问权限。比如,如果你在 __get__
里调用 obj.get_v,在 __set__
里调用 obj.set_v,这样就非常接近属性的实际实现,具体可以参考上面的链接。
编辑: 修正一下。我应该更仔细地阅读那一页。引用一下:
对于对象来说,机制在于
object.__getattribute__
,它把b.x
转换成type(b).__dict__['x'].__get__(b, type(b))
所以,如果你把描述符放在实例的 __dict__
里,当你把那个属性设置为新值时,它们会被简单地覆盖掉。
我想到了一个更简洁的办法来实现一个纯只读属性,如果你只想要这个的话。这是对tangentstorm提供的解决方案的一种变体,但完全不需要使用__getattr__
这个方法。
class Foo(object):
def __init__(self):
self.readonly = set()
def set_readonly(self, attr, value):
setattr(self, attr, value)
self.readonly.add(attr)
def __setattr__(self, attr, value):
if hasattr(self, "readonly") and attr in self.readonly:
raise AttributeError("Read only attribute: %s" % (attr,))
object.__setattr__(self, attr, value)
它的工作原理是这样的:
>>> f = Foo()
>>> f.x = 5
>>> f.set_readonly("y", 9)
>>> f.x, f.y
(5, 9)
>>> f.x = 7
>>> f.y = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "ro.py", line 13, in __setattr__
raise AttributeError("Read only attribute: %s" % (name,))
AttributeError: Read only attribute: y
如果想把一个只读属性变回可写的,操作起来也很简单:
def unset_readonly(self, attr):
self.readonly.remove(attr)
在我第一次尝试写这个想法的时候,我用了self.__readonly
而不是self.readonly
,但这会导致一个问题,就是在实际设置__readonly
属性时会遇到麻烦,因为我需要去解开这个“私有”属性来检查它是否存在(hasattr(self, "_Foo__readonly")
),而这并不被推荐。