Instance, static and class methods

The most common use of a class is as a recipe for creating objects, each of which is a specific instance of the class. The methods that we’ve defined inside a class so far have all had the self parameter as their first argument. This indicates that the method will work only if called by an instance of the class.

There are actually 3 different types of methods that can be defined within a class: instance methods (that we’ve already seen), static methods and class methods. As usual, it’s easiest to see how these work by looking at a specific example in code.

Consider the class (in ISCTest.py):

class ISCTest(object):
    """For testing instance, static & class methods"""
    counter = 0

    def __init__(self):
        ISCTest.counter += 1
        self.instanceVar = 42

    def iMethod(self):
        print('iMethod', self, f'Counter = {self.counter}; instanceVar = {self.instanceVar}')

    def sMethod():
        print('sMethod', f'Counter = {ISCTest.counter}')
    sMethod = staticmethod(sMethod)

    def cMethod(c):
        print('cMethod', c, f'Counter = {c.counter}')
    cMethod = classmethod(cMethod)

The class contains a class variable, counter, which exists independently of any instances of the class. This will serve as a counter for the number of instances of the class that have been created. The constructor increments counter by 1 each time it is called, so that each new instance will add 1 to counter. We also define an instance variable called instanceVar.

The iMethod() method on line 9 is an ordinary instance method. Its first argument is self, so it will work only if called with a specific instance of ISCTest. This method prints out the self object, and the values of self.counter and self.instanceVar. More on this in a minute.

The sMethod() method on line 12 takes no arguments and thus cannot be an instance method. As such it has no access to any instance variables and cannot print out instanceVar, since instanceVar is defined only as an instance variable. It can access the class variable counter, however, although to do so it must prefix it with the class name, as in ISCTest.counter (otherwise counter is assumed to be a local variable within the method, which doesn’t exist). Line 14 explicitly converts sMethod() to be a static method, using the built-in staticmethod() function.

Finally, line 16 defines the cMethod() method, which does take one argument. Although this argument is given the name c (and not self), Python will interpret the first argument of any class method as a reference to the object calling the method, unless it is told otherwise. In other words, lines 16 and 17 on their own would define an ordinary instance method, and c would refer to the object calling the method.

On line 18, we explicitly convert cMethod() to be a class method, again using a built-in function classmethod(). This means that the c argument is now the class of the object calling cMethod() and not any particular instance of that class. As such, cMethod() has no access to instance variables, and cannot print out instanceVar, for example.

Now let’s see how these 3 methods can be used. We have the following code in main.py:

from ISCTest import *

ISCobj1 = ISCTest()
ISCobj2 = ISCTest()

ISCobj1.iMethod()
ISCobj1.sMethod()
ISCobj1.cMethod()

ISCTest.iMethod(ISCobj1)
ISCTest.sMethod()
ISCTest.cMethod()

We create 2 objects on lines 3 and 4. On lines 6 through 8, we use one of these objects to call each of the 3 methods. The output from these 3 calls is:

iMethod <ISCTest.ISCTest object at 0x0000016118CEAF48> Counter = 2; instanceVar = 42
sMethod Counter = 2
cMethod <class 'ISCTest.ISCTest'> Counter = 2

We can see that all 3 calls work. The iMethod() call prints out the ISCobj1 object’s information, and shows counter as 2 (for the 2 objects we created) and the value of instanceVar.

The sMethod() call is still able to access counter, even though we called it with an instance of the class rather than the class itself. A static method identifies the class from which the calling instance is defined and uses that class to call the method. Since counter is referred to as ISCTest.counter in the code for sMethod(), its value can be accessed.

Finally, the call to cMethod() on line 8 also works and, again, it is the class of the calling object that is passed into cMethod() as its argument c, so c has the value shown in the printout: it is a class rather than an object. As such, we can use it to refer to counter by typing c.counter, as was done in the code for cMethod().

The calls on lines 10 to 12 use the class name (rather than an object) to call each of the three methods. The output is:

iMethod <ISCTest.ISCTest object at 0x000001E8DF94AF48> Counter = 2; instanceVar = 42
sMethod Counter = 2
cMethod <class 'ISCTest.ISCTest'> Counter = 2

This is the same as before. We can still get a call to iMethod() to work this way provided that we pass the object explicitly as an argument, as we do here. ISCobj1 is passed as self into iMethod() and is used to access that particular instance’s properties.

The call to sMethod() works in much the same way as before, with the class type being used to look up the location of sMethod().

The call to cMethod() includes no arguments. As with the call using an object above, the class is passed into cMethod() as its argument c, and is used to access class variables.

At this point, it’s useful to try out some variations on these calls, and to try to predict what will happen (usually an error) with each of them.

Exercises

Try to predict what will happen with each of the following modifications:

  1. Modify iMethod() above so that it looks like this:
def iMethod(self):
    print('iMethod', self, f'Counter = {ISCTest.counter}; instanceVar = {self.instanceVar}')

That is, we try to access counter as ISCTest.counter.

See answer

This will still work, since counter can be accessed either via the class name or by an instance of the class.

  1. Omit the call to staticmethod() on line 14 in the above definition of ISCTest. What happens when you try to run the test code above?
See answer

The call to sMethod() on line 9 fails with the error: “TypeError: sMethod() takes 0 positional arguments but 1 was given”. Without being explicitly declared as a static method, Python assumes it’s an instance method, and if sMethod() is called from an instance (as opposed to a class), it tries to pass the calling object as a self argument into the method. This is the argument it claims was given.

Note that the call to sMethod() on line 13 will still work, however, as Python assumes that any method with no arguments called from a class name is a static method, and thus can be called using the class name.

  1. Omit the call to classmethod() on line 18 in the above definition of ISCTest. What happens when you try to run the test code above?
See answer

The call to cMethod() on line 14 fails with the error: “TypeError: cMethod() missing 1 required positional argument: ‘c'”. As cMethod() wasn’t explicitly defined as a class method, the call using the class name on line 14 is assumed to be a call to a static method and thus will pass no arguments to it. A class method will pass the name of the calling class in as an implicit first argument.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.