Setting a property inside a Python method

2019-02-10 15:03发布

I want to set a read-only attribute inside a class method.
I have already tried this:

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

but it is horrible. Is there another way? What I need to do is setting the property:

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

but I need to do it inside a method. Is there another way?

Thank you,
rubik

P.S. I have already checked this post, but it does not solve my problem: Using Python property() inside a method

EDIT: I cannot use property, because I want to set it inside a method. I can use property only from outside:

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)

And I can't do that because I don't know the property name and I have to set it dynamically. Something like this:

class Foo(object):
    def __init__(self, v):
        self._set_property_from_inside('v', v)
>>> f = Foo(42)
>>> f.v
42

4条回答
混吃等死
2楼-- · 2019-02-10 15:42

property() is exactly the solution here. Why shouldn't is solve your problem? Overriding the setter an getter method allows you exactly what you want and need: full control over the property.

Please check with official documentation like

http://docs.python.org/library/functions.html#property

in order to understand the whole story.

查看更多
爷的心禁止访问
3楼-- · 2019-02-10 15:47

I've thought of what I think is a cleaner solution for implementing a pure read-only attribute, if that's all you want. It's a variant of the solution tangentstorm gave, but dispenses with the need for a __getattr__ method altogether.

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)

It works like this:

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

Making a read-only attribute read-write again is easy:

    def unset_readonly(self, attr):
        self.readonly.remove(attr)

In my first attempt at writing this idea I used self.__readonly instead of self.readonly, but that leads to a problem with actually setting the __readonly attribute, since I'd need to do un-munge the "private" attribute to check for its presence (hasattr(self, "_Foo__readonly")), and this is discouraged.

查看更多
乱世女痞
4楼-- · 2019-02-10 15:50
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)

Then:

>>> 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
查看更多
霸刀☆藐视天下
5楼-- · 2019-02-10 15:53

I think you're looking for python descriptors.

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

Here you can do whatever you want to control access in the __get__ and __set__ methods. If you call obj.get_v in __get__ and obj.set_v in __set__, this is very close to the actual implementation of a property, as you can see in the above link.

Edit: Fixed. I should have read that page better myself. Quoting:

For objects, the machinery is in object.__getattribute__ which transforms b.x into type(b).__dict__['x'].__get__(b, type(b))

So if you put descriptors in the __dict__ of the instance, they'll simply get overwritten when you set that attribute to a new value.

查看更多
登录 后发表回答