Skip to content Skip to sidebar Skip to footer

In Python, Count The Number Of Variables In A Class Or Prevent Adding New Class Variables

In python, is there a way to prevent adding new class variables after defining the object? For example: class foo: def __init__(self): self.a = 1 self.b = 2

Solution 1:

I suggest using __setattr__ to avoid the oddities of __slots__.

You always have to be careful when messing with __setattr__, since it takes care of setting all instance attributes, including those you set in __init__. Therefore it has to have some way of knowing when to allow the setting of an attribute, and when to deny it. In this solution I've designated 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:
    a.d = 89
except AttributeError:
    print "It works!"
else:
    print "It doesn't work."
a.c = 42
print a.a
print a.c
a.freeze = False
a.d = 28
a.freeze = True
print a.d

Result:

It works!
1
42
28

Also see gnibblers answer that wraps this concept neatly up in a class decorator, so it doesn't clutter up the class definition and can be reused in several classes without duplicating code.


EDIT:

Coming back to this answer a year later, I realize a context manager might solve this problem even better. Here's a modified version of gnibbler's 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's 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

So whenever you want to set new attributes, just use the with statement as above. It can also be done from outside the instance:

a = A()
try:
    a.d = 89
except AttributeError:
    print "It works!"
else:
    print "It doesn't work."
a.c = 42
print a.a
print a.c
with a.declare_attributes():
    a.d = 28
print a.d

Solution 2:

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

Yes. __slots__. But do carefully read the notes.


Solution 3:

How about a class decorator based on lazyr's 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"

Solution 4:

  1. Preventing adding new attibutes using __slots__ class 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")
    
  2. Counting attributes:

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

Solution 5:

use this to count no.of attributes of an instance:

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

Post a Comment for "In Python, Count The Number Of Variables In A Class Or Prevent Adding New Class Variables"