Preventing the creation of new attributes outside __init__

I want to be able to create a class (in Python) that, after initializing with __init__ does not accept new attributes, but accepts changes to existing attributes. There are several ways that I can see, for example, having the __setattr__ method, for example

 def __setattr__(self, attribute, value): if not attribute in self.__dict__: print "Cannot set %s" % attribute else: self.__dict__[attribute] = value 

and then editing __dict__ directly inside __init__ , but I was wondering if there is a β€œright” way to do this?

+42
python class
Aug 30 '10 at 19:20
source share
9 answers

I would not use __dict__ directly, but you can add a function to explicitly freeze the instance:

 class FrozenClass(object): __isfrozen = False def __setattr__(self, key, value): if self.__isfrozen and not hasattr(self, key): raise TypeError( "%r is a frozen class" % self ) object.__setattr__(self, key, value) def _freeze(self): self.__isfrozen = True class Test(FrozenClass): def __init__(self): self.x = 42# self.y = 2**3 self._freeze() # no new attributes after this point. a,b = Test(), Test() ax = 10 bz = 10 # fails 
+50
Aug 30 2018-10-10T00:
source share

Actually you do not need __setattr__ , you want __slots__ . Add __slots__ = ('foo', 'bar', 'baz') to the class body, and Python will make sure that in any instance there are only foo, bar and baz. But read the reservations in the documentation lists!

+15
Aug 30 '10 at 19:38
source share

If someone is interested in doing this with a decorator, here is a working solution:

 from functools import wraps def froze_it(cls): cls.__frozen = False def frozensetattr(self, key, value): if self.__frozen and not hasattr(self, key): print("Class {} is frozen. Cannot set {} = {}" .format(cls.__name__, key, value)) else: object.__setattr__(self, key, value) def init_decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): func(self, *args, **kwargs) self.__frozen = True return wrapper cls.__setattr__ = frozensetattr cls.__init__ = init_decorator(cls.__init__) return cls 

Pretty easy to use:

 @froze_it class Foo(object): def __init__(self): self.bar = 10 foo = Foo() foo.bar = 42 foo.foobar = "no way" 

Result:

 >>> Class Foo is frozen. Cannot set foobar = no way 
+15
Mar 31 '15 at 12:26
source share

The correct way is to override __setattr__ . This is what it is.

+6
Aug 30 '10 at 19:22
source share

I really like the solution that the decorator uses, because it is easy to use for many classes in the project, with minimal additions for each class. But with inheritance, this does not work well. So here is my version: it only redefines the __setattr__ function - if the attribute does not exist, and the caller function is not __init__, it displays an error message.

 import inspect def froze_it(cls): def frozensetattr(self, key, value): if not hasattr(self, key) and inspect.stack()[1][3] != "__init__": print("Class {} is frozen. Cannot set {} = {}" .format(cls.__name__, key, value)) else: self.__dict__[key] = value cls.__setattr__ = frozensetattr return cls @froze_it class A: def __init__(self): self._a = 0 a = A() a._a = 1 a._b = 2 # error 
+3
Apr 27 '16 at 18:57
source share

Here is the approach I came up with that does not require the _frozen attribute or the method for freezing () in init.

During init, I just add all the attributes of the class to the instance.

I like this because there is no _frozen, freeze () and _frozen also not showing up in the output of vars (instance).

 class MetaModel(type): def __setattr__(self, name, value): raise AttributeError("Model classes do not accept arbitrary attributes") class Model(object): __metaclass__ = MetaModel # init will take all CLASS attributes, and add them as SELF/INSTANCE attributes def __init__(self): for k, v in self.__class__.__dict__.iteritems(): if not k.startswith("_"): self.__setattr__(k, v) # setattr, won't allow any attributes to be set on the SELF/INSTANCE that don't already exist def __setattr__(self, name, value): if not hasattr(self, name): raise AttributeError("Model instances do not accept arbitrary attributes") else: object.__setattr__(self, name, value) # Example using class Dog(Model): name = '' kind = 'canine' d, e = Dog(), Dog() print vars(d) print vars(e) e.junk = 'stuff' # fails 
+1
Feb 19 '16 at 17:47
source share

How about this:

 class A(): __allowed_attr=('_x', '_y') def __init__(self,x=0,y=0): self._x=x self._y=y def __setattr__(self,attribute,value): if not attribute in self.__class__.__allowed_attr: raise AttributeError else: super().__setattr__(attribute,value) 
+1
Jun 24 '16 at 15:51
source share

I like Frozen Jochen Ritzel. The inconvenient thing is that isfrozen variable appears when printing the .__ dict class. I circumvented this problem this way by creating a list of allowed attributes (similar to slots ):

 class Frozen(object): __List = [] def __setattr__(self, key, value): setIsOK = False for item in self.__List: if key == item: setIsOK = True if setIsOK == True: object.__setattr__(self, key, value) else: raise TypeError( "%r has no attributes %r" % (self, key) ) class Test(Frozen): _Frozen__List = ["attr1","attr2"] def __init__(self): self.attr1 = 1 self.attr2 = 1 
0
Jun 06 '16 at 4:14
source share

FrozenClass Jochen Ritzel is cool, but calling _frozen() every time you initialize a class is not that cool (and you need to take a chance to forget it). I added the __init_slots__ function:

 class FrozenClass(object): __isfrozen = False def _freeze(self): self.__isfrozen = True def __init_slots__(self, slots): for key in slots: object.__setattr__(self, key, None) self._freeze() def __setattr__(self, key, value): if self.__isfrozen and not hasattr(self, key): raise TypeError( "%r is a frozen class" % self ) object.__setattr__(self, key, value) class Test(FrozenClass): def __init__(self): self.__init_slots__(["x", "y"]) self.x = 42# self.y = 2**3 a,b = Test(), Test() ax = 10 bz = 10 # fails 
0
Aug 31 '16 at 7:49
source share



All Articles