5

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
asked Feb 22, 2011 at 13:26
2
  • 2
    I don't understand what is wrong with your example. Commented Feb 22, 2011 at 13:38
  • Hey why someone voted me down??? Commented Feb 22, 2011 at 16:09

4 Answers 4

3

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.

answered Feb 22, 2011 at 15:16
Sign up to request clarification or add additional context in comments.

2 Comments

But I can still override that attribute... :( >>> f.v # prints 42; >>> f.v = 4; >>> f.v # prints 4
I've fixed it now, but if you want to have different attributes on different instances of the same class, my solution will not be good enough for you, and you should go with tangentstorms solution.
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)

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
answered Feb 22, 2011 at 16:13

5 Comments

Thank you! Now I can do what I want!
Overriding __getattr__(), __setattr__() for this purpose is completely insane and not recommended. Using property() is the way to go. Such code was reasonable ten years ago and is no longer appropriate nowadays. This is very bad practice.
Perhaps, but that's what he asked how to do. :) He doesn't know what the properties are going to be called until runtime, so I suspect this is simpler than dynamically creating properties on the class at runtime. Whether he should be doing it or not is up to him. :)
Perhaps it will be a slightly less insane solution if you redirected to a new dict attribute instead. Define self.protected = {} in __init__ and do if name in self.protected: return self.protected[name] in __getattr__ and if name in self.protected: raise ValueError(...) in __setattr__. Then you access self.protected directly to "override" your protection.
Good point about the second dict. I sometimes also like to use an arbitrary object called self.private to stick stuff like this on. In general, I do think messing with dict directly is pretty bad form.
1

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.

answered Feb 23, 2011 at 8:54

1 Comment

Thank you! I think using a set for read-only attrs is the best solution: clean and easy. Thank you for your effort! :)
1

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.

answered Feb 22, 2011 at 13:35

3 Comments

Do I have to use self.__dict__[myattr] = property(myfunc)?
Dude, read the documentation properly - especially the examples.
Dude, I have already read the documentation, and I have already said that I cannot set the property directly, because I want to create them at runtime.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.