zlr
zlr

Reputation: 829

Behaviour of class variable when it's not in __init__

I have the following Class named MyClass :

 def __init__(self, config):
        self.response = Response(config)
        self.utility = Utility()
        self.listl = {}

It has the following method :

 def findSt(self, lv):      
        dvp = self.listl
        if lv not in dvp:
            raise Exception("ERROR: ....", lv)
        else:
             ...

In my main code i use another method (getvalues) of that same class to give the correct value to self.listl ie:

    myobject = MyClass(config)
    myobject.listl = myobject.getvalues()
    lr = []
    for lv in lvs:
        lr.append(myobject.findSt(lv))   

This works, although i'm not sure it's the proper way to do this. The reason i'm asking is because if i do the following typo in the main code :

myobject.listL = myobject.getvalues()

No error is raised ! Of course in that case myobject.listl doesn't have the proper runtime values so it doesn't work, but why can I create a variable that is not in __init__ ?

Upvotes: 0

Views: 46

Answers (2)

Bakuriu
Bakuriu

Reputation: 101979

User-defined class instances can add/remove attributes at runtime:

>>> class A(object): pass
... 
>>> a = A()
>>> a.something = 1   #no error. Now a has a new something attibute
>>> 

While built-in types usually don't allow this:

>>> a = object()
>>> a.something = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'something'

In python every object has an associated dictionary that contains its attributes: __dict__. This dictionary is mutable. When you perform an assignment:

instance.attribute = value

What actually happens is that you are assigning to the __dict__ of the object:

instance.__dict__['attribute'] = value

and in fact:

>>> class A(object):
...     def __init__(self):
...             self.something = 1
... 
>>> a = A()
>>> a.__dict__
{'something': 1}
>>> a.other = 2
>>> a.__dict__
{'other': 2, 'something': 1}
>>> a.__dict__['other2'] = 3
>>> a.other2
3

__init__ is not special in this respect. It's just a convention that all attributes are defined there, but when you put self.attribute = value inside __init__ you are doing exactly the same thing as the above code: creating a new attribute. It is only the proper method to do initialization most of the time.

Classes do not (usually) declare a predefined set of attributes of their instances. It is possible with some meta-programming to add this kind of declaration (e.g. using class-decorators/metaclasses for example).

If you want to define the attributes of a class in advance you can use __slots__:

>>> class A(object):
...     __slots__ = ('something',)
... 
>>> a = A()
>>> a.something = 1
>>> a.other = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'other'

When you define __slots__ in a class then its instance do not have an associated __dict__, and hence its not possible to add attributes at runtime

>>> a.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__dict__'

Alternatively you can redefine the __setattr__ method to have finer control about what happens when you set an attribute.

Upvotes: 1

RemcoGerlich
RemcoGerlich

Reputation: 31260

For the same reason that you can set myobject.listl -- you can set any attribute on any instance from anywhere*, attributes set in __init__ aren't special in any way. The only thing special about __init__ is that it's called automatically when a new instance is made.

That doesn't mean you have to make use of that, it's usually better not to set such attributes from outside the class, I'd prefer to have a method like

def setvalues(self):
    self.listl = self.getvalues()

or so. In general you need to be careful because you have a mutable attribute there, what if some outside code still holds a reference to the old self.listl? Then that old list won't be changed by that code. This way it would be:

def setvalues(self):
    self.listl[:] = self.getvalues()

Doesn't re-set the listl attribute, but merely the values inside it. But that's a whole different discussion.

*: there are a few exceptions, like with classes implemented in C, or classes with __slots__ defined, but those are rare exceptions.

Upvotes: 1

Related Questions