In Python, count the number of variables in a class or don't add new class variables

In python, is there a way to prevent the addition of new class variables after defining an object?

For instance:

class foo: def __init__(self): self.a = 1 self.b = 2 self.c = 3 bar = foo() try: bar.d = 4 except Exception, e: print "I want this to always print" 

Alternatively, is there a way to count the number of variables in an object?

 class foo: def __init__(self): self.a = 1 self.b = 2 self.c = 3 def count(self): ... bar = foo() if bar.count() == 3: print "I want this to always print" 

The only way I thought about this is to use a dictionary or list:

 class foo: def __int__(self): self.dict = {'foo':1, 'bar':2} self.len = 2 def chk(): return self.len == len(self.list) 

However, this makes it rather cumbersome for python. (Obj.dict ['Foo']). I would prefer just obj.foo if possible.

I want to have this so that I never declare a variable when I want to modify an existing one.

 f = foo() f.somename = 3 ... f.simename = 4 #this is a typo if f.somename == 3: solve_everything() 

Thanks for the advanced.

+3
source share
7 answers

I suggest using __setattr__ to avoid the oddities of __slots__ .

You should always be careful when starting with __setattr__ , as it takes care of setting all the attributes of the instance, including the ones you set in __init__ . Therefore, it should have some way of knowing when to allow the setting of an attribute and when to deny it. In this solution, I assigned a special attribute that controls whether new attributes are allowed or not:

 class A(object): def __init__(self): self.a = 1 self.b = 2 self.c = 3 self.freeze = True def __setattr__(self, attr, value): if getattr(self, "freeze", False) and not hasattr(self, attr): raise AttributeError("You shall not set attributes!") super(A, self).__setattr__(attr, value) 

Testing:

 a = A() try: ad = 89 except AttributeError: print "It works!" else: print "It doesn't work." ac = 42 print aa print ac a.freeze = False ad = 28 a.freeze = True print ad 

Result:

  It works!
 1
 42
 28

Also see the gnibblers answer , which neatly completes this concept in the class decorator, so it does not clutter the class definition and can be reused in multiple classes without duplicate code.


EDIT:

Returning to this answer after a year, I understand that the context manager can solve this problem even better. Here's a modified version of the gnibbler class decorator:

 from contextlib import contextmanager @contextmanager def declare_attributes(self): self._allow_declarations = True try: yield finally: self._allow_declarations = False def restrict_attributes(cls): cls.declare_attributes = declare_attributes def _setattr(self, attr, value): disallow_declarations = not getattr(self, "_allow_declarations", False) if disallow_declarations and attr != "_allow_declarations": if not hasattr(self, attr): raise AttributeError("You shall not set attributes!") super(cls, self).__setattr__(attr, value) cls.__setattr__ = _setattr return cls 

And here is how to use it:

 @restrict_attributes class A(object): def __init__(self): with self.declare_attributes(): self.a = 1 self.b = 2 self.c = 3 

Therefore, when you want to set new attributes, just use the with statement as described above. This can also be done from outside the instance:

 a = A() try: ad = 89 except AttributeError: print "It works!" else: print "It doesn't work." ac = 42 print aa print ac with a.declare_attributes(): ad = 28 print ad 
+5
source

In python, is there a way to prevent the addition of new class variables after defining an object?

Yes. __slots__ . But read the notes carefully.

+4
source

How about a class decorator based on lazyr answer

 def freeze(cls): _init = cls.__init__ def init(self, *args, **kw): _init(self, *args, **kw) self.freeze = True cls.__init__ = init def _setattr(self, attr, value): if getattr(self, "freeze", None) and (attr=="freeze" or not hasattr(self, attr)): raise AttributeError("You shall not set attributes!") super(cls, self).__setattr__(attr, value) cls.__setattr__ = _setattr return cls @freeze class foo(object): def __init__(self): self.a = 1 self.b = 2 self.c = 3 bar = foo() try: bar.d = 4 except Exception, e: print "I want this to always print" 
+3
source
  • Prevent adding new attributes using class __slots__ attribute:

     class foo(object): __slots__ = ['a', 'b', 'c'] def __init__(self): self.a = 1 self.b = 2 self.c = 3 bar = foo() try: bar.d = 4 except Exception as e: print(e,"I want this to always print") 
  • Attribute Count:

     print(len([attr for attr in dir(bar) if attr[0] != '_' ])) 
+2
source

use this to count the number of instance attributes:

 >>> class foo: def __init__(self): self.a = 1 self.b = 2 self.c = 3 >>> bar=foo() >>> bar.__dict__ {'a': 1, 'c': 3, 'b': 2} >>> len(bar.__dict__) #returns no. of attributes of bar 3 
+1
source

Do you mean new class variables or new instance variables? The latter is similar to what you mean and much easier to do.

In response to Ignacio Vasquez-Abrams, answer __slots__ , probably what you want. Just make __slots__ = ('a', 'b', 'c') inside your class and this will prevent the creation of any other attributes. Note that this only applies to instances of your class level attributes that can still be set, and subclasses can add any attributes that they like. And he is right - there are some oddities, so read the related documentation before you start scattering slots everywhere.

If you do not use slots, return len(vars(self)) works like a body for your proposed count method.

As an alternative to slots, you can define __setattr__ , which rejects any attribute not in the "known good" list, or reject any new attributes after the frozen attribute is set to True at the end of __init__ , etc. This is harder to achieve, but more flexible.

If you really want your instances to be fully read-only after initialization, and you are using the latest version of Python, consider defining a namedtuple or subclass. The subclass of classes also has some limitations; if you need to go this route, I can expand it, but I would stick to the slots if you have no reason for this.

+1
source

Suppose now you want your class to have a fixed set of both mutable and immutable attributes? I cracked the gnibbler answer to make class attributes immutable after init:

 def frozenclass(cls): """ Modify a class to permit no new attributes after instantiation. Class attributes are immutable after init. The passed class must have a superclass (eg, inherit from 'object'). """ _init = cls.__init__ def init(self, *args, **kw): _init(self, *args, **kw) self.freeze = True cls.__init__ = init def _setattr(self, attr, value): if getattr(self, "freeze", None): if attr=="freeze" or not hasattr(self, attr): raise AttributeError("You shall not create attributes!") if hasattr(type(self), attr): raise AttributeError("You shall not modify immutable attributes!") super(cls, self).__setattr__(attr, value) cls.__setattr__ = _setattr return cls 

And an example:

 @frozenclass class myClass(object): """ A demo class.""" # The following are immutable after init: a = None b = None c = None def __init__(self, a, b, c, d=None, e=None, f=None): # Set the immutable attributes (just this once, only during init) self.a = a self.b = b self.c = c # Create and set the mutable attributes (modifyable after init) self.d = d self.e = e self.f = f 
0
source

Source: https://habr.com/ru/post/925066/


All Articles