Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent / alter access to class variables [duplicate]

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
like image 379
elhefe Avatar asked Sep 10 '12 21:09

elhefe


2 Answers

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

like image 176
Ben Avatar answered Oct 26 '22 03:10

Ben


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

like image 23
Duncan Avatar answered Oct 26 '22 04:10

Duncan