在Python方法中设置属性

5 投票
4 回答
7375 浏览
提问于 2025-04-16 12:17

我想在一个类的方法里设置一个只读属性。
我已经试过这个:

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 个回答

2
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

然后:

3

我觉得你在找的是 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__ 里,当你把那个属性设置为新值时,它们会被简单地覆盖掉。

1

我想到了一个更简洁的办法来实现一个纯只读属性,如果你只想要这个的话。这是对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")),而这并不被推荐。

撰写回答