Is there a way to prevent or alter access to class variables in Python as one can via overriding __setattr__
for instance variables?
Note that this question is mistitled and actually refers to instance variables, not class variables.
Based on reading multiple posts about the (apparent) deathtrap that is __slots__
, I'd prefer not to go that route (and I haven't looked into it enough to know if it does what I'm asking).
Example:
class A(object):
foo = "don't change me"
def __setattr__(self, name, value):
raise ValueError
if __name__ == '__main__':
a1 = A()
print a1.foo # don't change me
print A.foo # don't change me
A.foo = 'bar' # ideally throw an exception or something here
print A.foo # bar
a2 = A()
print a1.foo # bar
print a2.foo # bar
Yep! You do it exactly the same way.
A __setattr__
method on a class controls setting attributes on instances of that class. In Python, everything is an object. And objects have classes. So you just need to arrange for your class to be an instance of some class that has a __setattr__
method, exactly as you do when you want to prevent instances having their attributes modified!
The class of a class is called a metaclass. The default metaclass is type
: the "type of types", or "class of classes". You want to make a subclass of type
that doesn't allow its instances attributes to be set (exactly as you make a subclass of object
that doesn't allow its instances attributes to be set when you're doing this at the instance level).
class CantTouchThis(type):
def __setattr__(cls, attr, value):
raise Exception("NO!")
class SomeClass(object):
__metaclass__ = CantTouchThis
Note carefully the distinction between the base class(es) of SomeClass
, and the class of SomeClass
. SomeClass
is a subclass of object
. It is an instance of CantTouchThis
.
Almost everything else that works at instance level can be similarly applied to the class level with metaclasses; classes are simply instances like everything else. They only have their distinctive classy behaviour because of the methods and implementation of type
(exactly as ... etc, etc).
You're nearly there, you just need to move your __setattr__
method up to the metaclass:
class ReadOnlyClass(type):
def __setattr__(self, name, value):
raise ValueError(name)
class A(object, metaclass=ReadOnlyClass):
foo = "don't change me"
>>> a1 = A()
>>> a1.foo
"don't change me"
>>> A.foo
"don't change me"
>>> A.foo = 'bar'
Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
A.foo = 'bar'
File "<pyshell#4>", line 3, in __setattr__
raise ValueError, name
ValueError: foo
>>> print A.foo
don't change me
>>> a2 = A()
>>> a2.foo
"don't change me"
>>> a2.foo = 'x'
>>> a2.foo
'x'
Note that although the metaclass __setattr__
stops you changing attributes in the class it doesn't stop you hiding them with instance attributes: if that's what you want then you need to define __setattr__
in both places.
Edit: updated to use Python 3.x syntax instead of ancient Python 2.x
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With