Snippets Python: Static, Class and Instance Methods.

@staticmethod and @classmethod

By Marcelo Fernandes Jul 11, 2017

A Walk Tru about class methods and static methods

Python has 2 major decorators that supports the creation of some object-like functionalities on your code (besides the standard instance method that consumes self.
The first is @staticmethod and the second @classmethod, their definition is pretty literal and
straight forward, but there is much more than that:

Authorizations Instance Method Class Method Static Method
Can modify object instance state
Can modify class state

But... What does it really mean?


class MyClass:
    def instance_method(self):
        return 'called instance_method', self
    @classmethod
    def class_method(cls):
        return 'called class_method', cls
    @staticmethod
    def static_method():
        return 'called static_method'

obj = MyClass()

# An instance method has an object pointing to an instance of a class.

obj.instance_method()
#('called instance_method', <MyClass object at 0x7f5419a18ac8>)

# A class method only has access to the class, not the instance.

obj.class_method()
# '('called class_method', <class 'MyClass'>)'

# Static methods "just be":

obj.static_method()
# 'called static_method'

Ok, but what if we call the object without actually creating an instance?

# For Class methods it works just fine!

MyClass.class_method()
# '('called class_method', <class 'MyClass'>)'

# The same for static methods...

MyClass.static_method()
# 'called static_method'

# What if... we call the instance method?
MyClass.instance_method()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: instance_method() missing 1 required positional argument: 'self'

When should I use @classmethod?

1- Factories:
Imagine that you have an object, and in order to create an specific instance of this object you have to pass a lot of complicated parameters:

my_instance = object.method('param_1', 'param_2', ... , 'param_N')

If anybody is ever going to use your API, they won't like to be typing-in thousands of arguments just to get an specific instance.
so we can have a @classmethod that does this for us.

# ... Somewhere in the middle of a MyClass (...)

@classmethod
def specialized_instance(cls):
    return cls('param_1', 'param_2', ... , 'param_N')

# instead of: my_instance = object.method('param_1', 'param_2', ... , 'param_N')
MyClass.specialized_instance()

It's a trade off, we make the users of our factory happy that they won't be passing thousands of parameters to get an specific instance that is requested all the time, at the cost of typing it in the middle of our class only one time.

2 - Decoupling
Class methods are able to be carried away by children of the parent class. Which means that if I make the request myobj.foo(), I don't have to worry whether or not foo() was implemented inside of myobj or in its parent class, I just trust that it's gonna solve my problem and therefore I will turn its declaration and implementation the problem of someone else.

3 - Calculating things that precedes the creation of an instance.
Sometimes you just want to set-up some computations that are gonna take place before creating the instance, if you are interested in creating some connections to your database and send some triggers or signals prior instantiating the object that is gonna be sent, you might use some class methods, because if the instance does not exist yet, you can't use any instance methods.

Why and When should I use @staticmethod?

1 - Be explicit.
Do you know the statement "Explicit is Better than Implicit?". Imagine that you are debugging someone else function and you are interested in knowing when your instance was modified. Let's say that this guy that wrote the code did not keep good practices, and for him, everything was an instance method no matter what. So... How do you know which methods you should look for? You have to literally check method per method until you finally realize which one was the responsible for changing your instance. This situation might be a big issue when you don't know where to start debugging.

2 - Testing is gonna be easier.
Remember that you don't have to create instances to test a staticmethod, so you can just use myobj.staticmethod(parameters) and go ahead with the testing. You don't even have to pass a "cls" parameter and carry it arround when you are not using it. At some point during your development you might increase the testing performance of your application, if you are carrying tons of instances that computes a lot of stuffs on their __init__ or __call__ you might wanna reconsider reviewing your classes and separating which one is static and which one is not. It will increase the performance of your tests, if this is something that bothers you. Okay, but what is the problem about creating instances if I have a small app? I will probably have to create then anyway. Well... It's partially true and might be a consideration for some applications, but at least keep in mind that it is 'ideally' better when it comes to the big picture.

Performance Comparative


from timeit import timeit


setup = """
class MyClass:

    @classmethod
    def class_method(cls):
        pass

    @staticmethod
    def static_method():
        pass

    def instance_method(self):
    	pass

obj = MyClass()
"""


class_method_operation = """obj.class_method"""
static_method_operation = """obj.static_method"""
instance_method_operation = """obj.instance_method"""

class_method_time = timeit(stmt=class_method_operation, setup=setup, number=1000000)
static_method_time = timeit(stmt=static_method_operation, setup=setup, number=1000000)
instance_method_time = timeit(stmt=instance_method_operation, setup=setup, number=1000000)

instance_method_time, class_method_time, static_method_time
# (0.06076693534851074, 0.051759958267211914, 0.03341794013977051)

Staticmethod is always faster, as the compiler does not have to pass extra arguments to the function.


Notes


References:

link