2

I'm writing code with a lot of class definition, and I want to make all the attributes of class only being defined in __init__().

The main reason is that avoiding my own fault to define new (unexpected) attributes and I don't want new attributes to be defined when the module is imported to run (see the following).

You can run this code with impunity, which I'm trying to avoid.

class A():pass
a=A()
a.a=1
print(a.a)
YiFei
  • 1,752
  • 1
  • 18
  • 33
  • The term is "attribute", not "attribution". While there are ways to do this, I would suggest using a linter to perform the check instead. – user2357112 Jan 08 '16 at 13:32
  • 1
    This has already been answered here: http://stackoverflow.com/questions/3603502/prevent-creating-new-attributes-outside-init – rafaelc Jan 08 '16 at 13:39

2 Answers2

3

Here are two options:

First, you could use __slots__, which will restrict your class to a specific set of attributes.

Not exactly what you want (and slots weren't designed for this in the first place!), but close-ish:

class A(object):
    __slots__ = ["a"]

a = A()
a.a = 1  # Will work
a.b = 2  # Will not work

Second, you could override __setattr__, and have it look up a flag to prevent creating new attributes after init has completed:

class A(object):

    __frozen__ = False

    def __init__(self, a):
        self.a = a
        self.__frozen__ = True  # At this point no more changes can be made

    def __setattr__(self, attr, value):
        if self.__frozen__ and not hasattr(self, attr):
            raise Exception("New attributes cannot be added!")  # Create your own Exception subclass for this
        super(A, self).__setattr__(attr, value)

  a = A(1)
  a.a = 2  # Works
  a.b = 2  # Throws an Exception

This is closer to what you want. However, you should think hard about whether this is actually desirable. Is this the best way to avoid mistakes? Maybe you'd want to e.g. write tests instead?

Among other things, this will break subclassing if your subclass calls super(TheSubClass, self).__init__(*args, **kwargs) and then tries to access an attribute.

Thomas Orozco
  • 53,284
  • 11
  • 113
  • 116
2

You want Slots:

class MyAttrOnly:
    __slots__ = ('a', 'b')


m = MyAttrOnly()
m.d = "doesn't work"

This will throw an exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyAttrOnly' object has no attribute 'd'
Community
  • 1
  • 1
Wayne Werner
  • 49,299
  • 29
  • 200
  • 290
  • 1
    In Ryan's answer for [Python `__slots__`](http://stackoverflow.com/a/472017/5215536), what does "It's highly discouraged to use `__slots__` for constraining attribute creation, and in general you want to avoid it because it breaks pickle, along with some other introspection features of python." mean if using `__slots__`? – YiFei Jan 08 '16 at 13:36
  • It means that `__slots__` are here for performance and not intended as an access restraining mechanism. Because of that, you will have trouble when using your class with code that expects regular dynamic class. – kjaquier Jan 08 '16 at 13:40
  • 3
    `__slots__` is a bit like pouring sugar in your gas tank to keep someone from stealing your car. Yeah, *they* can't drive off with it anymore, but it makes life much more difficult for you, too. – Wayne Werner Jan 08 '16 at 13:41