Instance variables in methods outside the constructor (Python) -- why and how?
Asked Answered
C

7

32

My questions concern instance variables that are initialized in methods outside the class constructor. This is for Python.

I'll first state what I understand:

  1. Classes may define a constructor, and it may also define other methods.
  2. Instance variables are generally defined/initialized within the constructor.
  3. But instance variables can also be defined/initialized outside the constructor, e.g. in the other methods of the same class.
  4. An example of (2) and (3) -- see self.meow and self.roar in the Cat class below:

    class Cat():
    
        def __init__(self):
            self.meow = "Meow!"
        def meow_bigger(self):
            self.roar = "Roar!"
    

My questions:

  1. Why is it best practice to initialize the instance variable within the constructor?

  2. What general/specific mess could arise if instance variables are regularly initialized in methods other than the constructor? (E.g. Having read Mark Lutz's Tkinter guide in his Programming Python, which I thought was excellent, I noticed that the instance variable used to hold the PhotoImage objects/references were initialized in the further methods, not in the constructor. It seemed to work without issue there, but could that practice cause issues in the long run?)

  3. In what scenarios would it be better to initialize instance variables in the other methods, rather than in the constructor?


  1. To my knowledge, instance variables exist not when the class object is created, but after the class object is instantiated. Proceeding upon my code above, I demonstrate this:

    >> c = Cat() 
    >> c.meow
    'Meow!'
    >> c.roar
    Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
    AttributeError: 'Cat' object has no attribute 'roar'
    >>> c.meow_bigger()
    >>> c.roar
    'Roar!'
    

    As it were:

    • I cannot access the instance variable (c.roar) at first.
    • However, after I have called the instance method c.meow_bigger() once, I am suddenly able to access the instance variable c.roar.
    • Why is the above behaviour so?

Thank you for helping out with my understanding.

Concordant answered 14/7, 2016 at 14:44 Comment(2)
As far as what "general mess" could occur if you are initializing instance variables in other functions-- you'd have no access to those variables from instances which hadn't called the functions. Having several instances, all with different existing variables, could get quite messy.Aerobiology
these are in depth concepts. good one.Lohr
L
30

Why is it best practice to initialize the instance variable within the constructor?

Clarity.

Because it makes it easy to see at a glance all of the attributes of the class. If you initialize the variables in multiple methods, it becomes difficult to understand the complete data structure without reading every line of code.

Initializing within the __init__ also makes documentation easier. With your example, you can't write "an instance of Cat has a roar attribute". Instead, you have to add a paragraph explaining that an instance of Cat might have a "roar" attribute, but only after calling the "meow_louder" method.

Clarity is king. One of the smartest programmers I ever met once told me "show me your data structures, and I can tell you how your code works without seeing any of your code". While that's a tiny bit hyperbolic, there's definitely a ring of truth to it. One of the biggest hurdles to learning a code base is understanding the data that it manipulates.

What general/specific mess could arise if instance variables are regularly initialized in methods other than the constructor?

The most obvious one is that an object may not have an attribute available during all parts of the program, leading to having to add a lot of extra code to handle the case where the attribute is undefined.

In what scenarios would it be better to initialize instance variables in the other methods, rather than in the constructor?

I don't think there are any.

Note: you don't necessarily have to initialize an attribute with it's final value. In your case it's acceptable to initialize roar to None. The mere fact that it has been initialized to something shows that it's a piece of data that the class maintains. It's fine if the value changes later.

Linehan answered 14/7, 2016 at 15:49 Comment(1)
This is the best way to avoid code smell, and ensure readability and clean code.Guenna
B
14

Remember that class members in "pure" Python are just a dictionary. Members aren't added to an instance's dictionary until you run the function in which they are defined. Ideally this is the constructor, because that then guarantees that your members will all exist regardless of the order that your functions are called.

I believe your example above could be translated to:

class Cat():
    def __init__(self):
        self.__dict__['meow'] = "Meow!"
    def meow_bigger(self):
        self.__dict__['roar'] = "Roar!"

>>> c = Cat()        # c.__dict__ = { 'meow': "Meow!" }
>>> c.meow_bigger()  # c.__dict__ = { 'meow': "Meow!", 'roar': "Roar!" }
Buster answered 14/7, 2016 at 15:0 Comment(2)
I learned something new. Did not know that class members were a dictionary. Thank you!Concordant
hi , I left a question here, maybe you can answer it, thanks #66937561Dextrose
R
4

To initialize instance variables within the constructor, is - as you already pointed out - only recommended in python.

First of all, defining all instance variables within the constructor is a good way to document a class. Everybody, seeing the code, knows what kind of internal state an instance has.

Secondly, order matters. if one defines an instance variable V in a function A and there is another function B also accessing V, it is important to call A before B. Otherwise B will fail since V was never defined. Maybe, A has to be invoked before B, but then it should be ensured by an internal state, which would be an instance variable.

There are many more examples. Generally it is just a good idea to define everything in the __init__ method, and set it to None if it can not / should not be initialized at initialization.

Of course, one could use hasattr method to derive some information of the state. But, also one could check if some instance variable V is for example None, which can imply the same then. So in my opinion, it is never a good idea to define an instance variable anywhere else as in the constructor.

Your examples state some basic properties of python. An object in Python is basically just a dictionary. Lets use a dictionary: One can add functions and values to that dictionary and construct some kind of OOP. Using the class statement just brings everything into a clean syntax and provides extra stuff like magic methods.

In other languages all information about instance variables and functions are present before the object was initialized. Python does that at runtime. You can also add new methods to any object outside the class definition: Adding a Method to an Existing Object Instance

Reis answered 14/7, 2016 at 15:14 Comment(0)
H
2

3.) But instance variables can also be defined/initialized outside the constructor, e.g. in the other methods of the same class.

I'd recommend providing a default state in initialization, just so its clear what the class should expect. In statically typed languages, you'd have to do this, and it's good practice in python.

Let's convey this by replacing the variable roar with a more meaningful variable like has_roared.

In this case, your meow_bigger() method now has a reason to set has_roar. You'd initialize it to false in __init__, as the cat has not roared yet upon instantiation.

class Cat():
    def __init__(self):
        self.meow = "Meow!"
        self.has_roared = False

    def meow_bigger(self):
         print self.meow + "!!!"
         self.has_roared = True

Now do you see why it often makes sense to initialize attributes with default values?

All that being said, why does python not enforce that we HAVE to define our variables in the __init__ method? Well, being a dynamic language, we can now do things like this.

>>> cat1 = Cat()
>>> cat2 = Cat()
>>> cat1.name = "steve"
>>> cat2.name = "sarah"
>>> print cat1.name
... "steve"

The name attribute was not defined in the __init__ method, but we're able to add it anyway. This is a more realistic use case of setting variables that aren't defaulted in __init__.

Hitch answered 14/7, 2016 at 15:27 Comment(0)
C
1

I try to provide a case where you would do so for:

3.) But instance variables can also be defined/initialized outside the constructor, e.g. in the other methods of the same class.

I agree it would be clear and organized to include instance field in the constructor, but sometimes you are inherit other class, which is created by some other people and has many instance fields and api.

But if you inherit it only for certain apis and you want to have your own instance field for your own apis, in this case, it is easier for you to just declare extra instance field in the method instead override the other's constructor without bothering to deep into the source code. This also support Adam Hughes's answer, because in this case, you will always have your defined instance because you will guarantee to call you own api first.

For instance, suppose you inherit a package's handler class for web development, you want to include a new instance field called user for handler, you would probability just declare it directly in the method--initialize without override the constructor, I saw it is more common to do so.

class BlogHandler(webapp2.RequestHandler):
     def initialize(self, *a, **kw):
         webapp2.RequestHandler.initialize(self, *a, **kw)
         uid = self.read_cookie('user_id') #get user_id by read cookie in the browser
         self.user = User.by_id(int(uid)) #run query in data base find the user and return user
Catamaran answered 23/5, 2017 at 16:38 Comment(0)
J
1

These are very open questions.

Python is a very "free" language in the sense that it tries to never restrict you from doing anything, even if it looks silly. This is why you can do completely useless things such as replacing a class with a boolean (Yes you can).

The behaviour that you mention follows that same logic: if you wish to add an attribute to an object (or to a function - yes you can, too) dynamically, anywhere, not necessarily in the constructor, well... you can.

But it is not because you can that you should. The main reason for initializing attributes in the constructor is readability, which is a prerequisite for maintenance. As Bryan Oakley explains in his answer, class fields are key to understand the code as their names and types often reveal the intent better than the methods.

That being said, there is now a way to separate attribute definition from constructor initialization: pyfields. I wrote this library to be able to define the "contract" of a class in terms of attributes, while not requiring initialization in the constructor. This allows you in particular to create "mix-in classes" where attributes and methods relying on these attributes are defined, but no constructor is provided.

See this other answer for an example and details.

Jocasta answered 30/3, 2020 at 13:21 Comment(0)
L
0

i think to keep it simple and understandable, better to initialize the class variables in the class constructor, so they can be directly called without the necessity of compiling of a specific class method.

class Cat():

        def __init__(self,Meow,Roar):
            self.meow = Meow
            self.roar = Roar
        def meow_bigger(self):
            return self.roar
        def mix(self):
            return self.meow+self.roar

    
c=Cat("Meow!","Roar!")
print(c.meow_bigger())
print(c.mix())

Output

Roar!

Roar!

Meow!Roar!

Lohr answered 26/10, 2018 at 20:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.