Reputation: 9104
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
Upvotes: 5
Views: 7597
Reputation: 50975
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.
Upvotes: 1
Reputation: 50975
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 transformsb.x
intotype(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.
Upvotes: 3
Reputation: 7295
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
Upvotes: 2
Reputation:
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.
Upvotes: 1