67

Maybe this is more of a style question than a technical one but I have a class with several member variables and I want to have it work so that some of the member variables are initialized when the user first creates an instance of the class (i.e. in the __init__ function) and I want the other member variables to be defined from arguments of member functions that will be called later on. So my question is should I initialize all member variables in the __init__ function (and set the ones that will be defined later on to dummy values) or initialize some in the __init__ function and some in later functions. I realize this might be difficult to understand so here are a couple of examples.

This example has var3 set to 0 initially in the __init__ function, then set to the desired value later on in the my_funct function.

class myClass(object):
   def __init__(self,var1,var2):
        self.var1=var1
        self.var2=var2
        self.var3=0

  def my_funct(self,var3):
       self.var3=var3

and in this example, var3 is not defined at all in the __init__ function

class myClass(object):
   def __init__(self,var1,var2):
        self.var1=var1
        self.var2=var2

  def my_funct(self,var3):
       self.var3=var3

I don't think either way would make a big difference (maybe a slight difference in memory usage). But I was wondering if one of these is preferred over the other for some reason.

Georgy
  • 12,464
  • 7
  • 65
  • 73
user1893354
  • 5,778
  • 12
  • 46
  • 83

3 Answers3

45

In object-oriented programming it's up to the developer to ensure an object is always in a consistent state after instantiation and after a method finishes. Other than that you're free to develop the class as you wish (keeping in mind certain principles with subclassing / overriding and so on).

A tool such as Pylint will warn when you're setting instance variables outside __init__. It can be argued that setting all instance variables in the __init__ is cleaner but it's not a rule that must be abided by at all times.

Simeon Visser
  • 118,920
  • 18
  • 185
  • 180
  • 2
    Agreed. Consistency is key - you don't want outsiders using your API or class in an invalid state. – C.B. Dec 18 '13 at 15:02
  • 2
    Can you briefly define what 'consistent state' means. Does this mean no new member variables should be added after instantiation? – user1893354 Dec 18 '13 at 15:10
  • 4
    @user1893354 you can add variables after instantiation but it should not be possible to create an object, call one or more methods and end up with a messy object. The behaviour of methods and their return values should always be consistent. For example, we can't have a `Car` class that reports being both "broken" and "functioning properly". – Simeon Visser Dec 18 '13 at 15:12
  • 1
    I agree with that. I'm just trying to think of an example of how that kind of mistake could be made. If you only have one `self.car_health` variable, it can only take on one value at a time ('broken' or 'functioning properly'). – user1893354 Dec 18 '13 at 15:20
  • 5
    Yes, but the state of an object is usually made up with multiple variables. For example, the `Car` may have variables such as: `self.wheel_damage`, `self.exterior_damage`, `self.engine_damage` (etc) if you're creating a video game. In that case it shouldn't be possible to have a 100% healthy car but also 75% wheel damage. – Simeon Visser Dec 18 '13 at 15:24
10

I would actually discourage initializing variables you don't always need in __init__ to an arbitrary default value.

I do question your use of OO if this is the case, but I'm sure there is a valid and understandable case where __init__ will not do everything, and the class will want to further modify itself by adding additional attributes with other methods.

The proper way in my opinion to test if a variable was set while running a method that may want to use it would be to use hasattr. This is in the case that this is a valid way to use the method and the test just switches behavior in a sensible way.

Another way would be to try and use it and handle the exception and provide some user friendly information about what the user of your class is doing wrong. This is in the case the method needs the attribute to be set before running.

i.e. Hey man, you did initialize the class, but you need to make sure the z attribute exists by calling the z_init method before running the z_run method.

Another, arguably the more pythonic way, would be to just document how to use the method in the docstring and then let the exception fly when it is used improperly. This is good enough for the first implementation of something and you can then focus on the next task. This is in the same situation as above, the method needs the attribute to be set.

The reason I do not like the idea of initializing variables to arbitrary defaults is this can be confusing (because it is arbitrary) and is line noise.

If the value is not arbitrary and simply a default value that can be changed you should be using a default value in the __init__ method that can be overridden. It can also actually be a valid initial state, which is also not arbitrary and you should set it in the __init__ method.

So the real answer is it depends, and you should probably avoid it and question your use of OO if you are doing this either by adding attributes in other methods or initializing attributes to arbitrary values.

While Simeon Visser is saying to keep your object in a consistent state, he has no basis for what consistency is based on your abstract example. While Pylint warns on this kind of thing, warnings from lint programs are simply so a high level reviewer can be alerted of things that usually indicate code smell. I say high level reviewer because a real reviewer should be reading and understanding all of your code, and thus not really need Pylint.

An example that breaks the rule of thumb:

class Mutant(object):
    """A mutant!"""

    def __init__(self):
        """A mutant is born with only 1 eye and 1 mouth"""

        self.eyes = 1
        self.mouth = 1
        self.location = 'Montana'

    def roll_to(self, location):
        """If they have limbs, running is less dangerous"""

        if hasattr(self, 'limbs'):
             print 'Your mutant broke its limbs off!!'
             del self.limbs

        self.location = location

    def run_to(self, location):
        """If they don't have limbs, running is not effective"""

        if not hasattr(self, 'limbs'):
             print 'Your mutant tries to run but he has no limbs.'
        else:
             self.location = location

    def grow_limbs(self, number_of_limbs):
         """Ah, evolution!"""

         assert number_of_limbs > 0, 'Cannot grow 0 or less limbs...'

         if hasattr(self, 'limbs'):
             self.limbs += number_of_limbs
         else:
             self.limbs = number_of_limbs
Derek Litz
  • 10,529
  • 7
  • 43
  • 53
  • Good points. I was thinking of the case where one of the member variables is the output of a member function that you would want to save within the object. In this case, the variable can only be instantiated with a real value after the member function is called. – user1893354 Dec 18 '13 at 16:11
  • So, let me make sure I understand you correctly... For example, an `obj` has `output_complex_calculation`, and you want to `obj.set_result_of_complex_calculation(obj.output_of_complex_calculation())` – Derek Litz Dec 18 '13 at 16:15
  • for example, if you have a class that is a prediction model where the user inputs the parameters of the model at instantiation. Then the user calls the .predict() function which creates a list of predictions. But you would want to save these predictions for this object as a member variable. Then maybe some other member function will do something with this new prediction member variable. So in this case, the real values for the prediction member variable can only be instantiated after .predict() is called. – user1893354 Dec 18 '13 at 16:29
  • 1
    Seems ok, but it's really a matter of usability. Code it up, try it out, show it to people. Purity would be to keep things explicit, but practicality might dictate doing more for the user in `__init__`, `__setattr__` and `__getattr__`, for example. You can always change it later, most of my code I've written over a year ago, I would not be happy to have written today. I was sure happy when I wrote it though ;) The important thing for your first attempt is that it does the job and you can demonstrate understanding the requirements with code. After that improvement is easier. – Derek Litz Dec 18 '13 at 16:36
  • Short version `import this` ;) – Derek Litz Dec 18 '13 at 16:43
  • 1
    personally, i would prefer setting limbs to `None`, rather than deleting it – Erik Aronesty May 17 '22 at 21:54
2

Here is an excerpt from sololearn.com (a free site to learn python)

"Properties provide a way of customizing access to instance attributes. They are created by putting the property decorator above a method, which means when the instance attribute with the same name as the method is accessed, the method will be called instead.

One common use of a property is to make an attribute read-only."

Example (also from sololearn.com):

class Pizza:
    def __init__(self, toppings):
    self.toppings = toppings

    @property
    def pineapple_allowed(self):
       return False

   pizza = Pizza(["cheese", "tomato"])
   print(pizza.pineapple_allowed)
   pizza.pineapple_allowed = True

Result:

  >>>
 False
 AttributeError: can't set attribute
 >>>

If var3 depends on var1 and var2 you could do

class myClass:
    def __init__(self,var1,var2):
        self.var1=var1
        self.var2=var2
    @property
    def var3(self):
        return(self.var1+self.var2)  #var3 depends on var1 and var2
 m1=myClass(1,2)
 print(m1.var3)   # var3 is 3

var3 can also be set to whatever you want using a setter function. Note that you can avoid setting var3 to an arbitrary value by using None.

class myClass2(object):
    def __init__(self,var1,var2):
        self.var1=var1
        self.var2=var2
        self._var3=None     # None or an initial value that makes sense
        @property
        def var3(self):
            return(self._var3)
        @var3.setter
        def var3(self,value):
            self._var3=value
   m2=myClass(1,2)
   print(m2.var3)        # var3 is none
   print(m2.var3(10))    # var3 is set to 10 
Cody Aldaz
  • 160
  • 7