5.6. OOP Staticmethod
Method which don't use
selfin its body should not be in a classIf method takes
selfand use it (it requires instances to work) it should be in classIf a method don't use
selfbut uses class as a namespace use@staticmethoddecoratorUsing class as namespace
No need to create a class instance
Will not pass instance (
self) as a first method argument
>>> class User:
... def say_hello():
... return 'hello'
>>> class User:
... @staticmethod
... def say_hello():
... return 'hello'
5.6.1. Problem: Instance Method
Instance methods require
selfas a first argumentCalling instance method on a class will result in
TypeErrorCalling instance method on an instance will work
>>> class User:
... def login(self):
... print('ok')
Call method on a class:
>>> User.login()
Traceback (most recent call last):
TypeError: User.login() missing 1 required positional argument: 'self'
Call method on an instance:
>>> User().login()
ok
5.6.2. Problem: Class Function
Calling class method on a class will work
Calling class method on an instance will result in
TypeError
>>> class User:
... def login():
... print('ok')
Call method on a class:
>>> User.login()
ok
Call method on an instance:
>>> User().login()
Traceback (most recent call last):
TypeError: User.login() takes 0 positional arguments but 1 was given
5.6.3. Solution: Static Method
Calling static method on a class will work
Calling static method on an instance will work
Use
@staticmethoddecorator to create static method
>>> class User:
... @staticmethod
... def login():
... print('ok')
Call method on a class:
>>> User.login()
ok
Call method on an instance:
>>> User().login()
ok
5.6.4. Case Study
import json
from dataclasses import dataclass
@dataclass
class User:
firstname: str
lastname: str
DATA = '{"firstname": "Alice", "lastname": "Apricot"}'
result = User(**json.loads(DATA))
result
# User(firstname='Alice', lastname='Apricot')
import json
from dataclasses import dataclass
@dataclass
class User:
firstname: str
lastname: str
def from_json(self, string):
data = json.loads(string)
return User(**data)
DATA = '{"firstname":"Alice", "lastname":"Apricot"}'
result = User.from_json(string=DATA)
# TypeError: User.from_json() missing 1 required positional argument: 'self'
result = User().from_json(string=DATA)
# TypeError: User.__init__() missing 2 required positional arguments: 'firstname' and 'lastname'
result = User('', '').from_json(string=DATA)
# User(firstname='Alice', lastname='Apricot')
import json
from dataclasses import dataclass
@dataclass
class User:
firstname: str
lastname: str
@staticmethod
def from_json(string):
data = json.loads(string)
return User(**data)
DATA = '{"firstname":"Alice", "lastname":"Apricot"}'
result = User.from_json(string=DATA)
result
# User(firstname='Alice', lastname='Apricot')
5.6.5. Case Study 1
>>> from dataclasses import dataclass
>>> import json
Assume we received DATA from the REST API endpoint:
>>> DATA = '{"firstname":"Alice", "lastname":"Apricot"}'
Let's define a User class:
>>> @dataclass
... class User:
... firstname: str
... lastname: str
...
... def from_json(self, string):
... data = json.loads(string)
... return User(**data)
Let's use .from_json() to create an instance:
>>> User.from_json(string=DATA)
Traceback (most recent call last):
TypeError: User.from_json() missing 1 required positional argument: 'self'
The string is unfilled. This is due to the fact, that we are running a method
on a class, not on an instance. Typically while running on an instance,
Python will pass it as self argument and we will fill the other one.
Running this on a class, turns off this behavior, and therefore this is why
the string parameter is unfilled.
We can create an instance and then run .from_json() method.
>>> User().from_json(string=DATA)
Traceback (most recent call last):
TypeError: User.__init__() missing 2 required positional arguments: 'firstname' and 'lastname'
Nope, we cannot. In order to create an instance we need to pass firstname
and lastname. We can pass None objects instead. We can also make a class
to always assume a default value for firstname and lastname as None, but
this will remove those arguments from required list and allow to create a
User object without passing those values. In both cases this will work,
but it is not good:
>>> User(None, None).from_json(DATA)
User(firstname='Alice', lastname='Apricot')
We can define a static method .from_json() which will not require
creating instance in order to use it:
>>> @dataclass
... class User:
... firstname: str
... lastname: str
...
... @staticmethod
... def from_json(data):
... data = json.loads(data)
... return User(**data)
Now, we can use this without creating an instance first:
>>> User.from_json(DATA)
User(firstname='Alice', lastname='Apricot')
5.6.6. Namespace
Functions on a high level of a module lack namespace:
>>> def login():
... print('User login')
>>>
>>> def logout():
... print('User logout')
>>>
>>>
>>> login()
User login
>>>
>>> logout()
User logout
When login and logout are in User class (namespace) they get
instance (self) as a first argument. Instantiating Calculator is not
needed, as of functions do not read or write to instance variables:
>>> class User:
... def login(self):
... print('User login')
...
... def logout(self):
... print('User logout')
>>>
>>>
>>> User().login()
User login
>>>
>>> User().logout()
User logout
Class User is a namespace for functions. @staticmethod remove
instance (self) argument to method:
>>> class User:
... @staticmethod
... def login():
... print('User login')
...
... @staticmethod
... def logout():
... print('User logout')
>>>
>>>
>>> User.login()
User login
>>>
>>> User.logout()
User logout
5.6.7. When to Use Staticmethod
Some functions in a class do not require instance (
self) to workHence, they can be
@staticmethod... but should they?@staticmethodis a hint for a developer that method does not use instance
SetUp:
>>> from random import randint, seed
>>> seed(0)
Let's create a Hero class with a method make_damage():
>>> class Hero:
... def make_damage(self):
... return randint(5,20)
Calling this method on an instance will work as expected:
>>> hero = Hero()
>>> hero.make_damage()
17
And calling this method on a class will fail, as expected:
>>> Hero.make_damage()
Traceback (most recent call last):
TypeError: Hero.make_damage() missing 1 required positional argument: 'self'
This is reasonable, as we need an instance to call this method. Hero needs to be alive (instance created), before it can make damage.
But your IDE may suggest to use @staticmethod, because this method
does not use instance (self) to work. It does not modify instance
variables, it does not read from them. It is a pure function.
>>> class Hero:
... @staticmethod
... def make_damage():
... return randint(5,20)
Now, calling this method on an instance will work as expected:
>>> hero = Hero()
>>> hero.make_damage()
18
But calling this method on a class will work as well, which is unexpected:
>>> Hero.make_damage()
6
The meaning of this, is that Hero does not need to be alive
(instance created) to make damage. And this is not true.
In this example, using @staticmethod is not a good idea.
Despite the fact that this method does not use instance (self),
5.6.8. Use Case - 1
Singleton
>>> class DatabaseConnection:
... _instance = None
...
... @staticmethod
... def get_instance():
... if not DatabaseConnection._instance:
... DatabaseConnection._instance = object.__new__(DatabaseConnection)
... return DatabaseConnection._instance
>>> a = DatabaseConnection.get_instance()
>>> print(a)
<__main__.DatabaseConnection object at 0x102453ee0>
>>> b = DatabaseConnection.get_instance()
>>> print(b)
<__main__.DatabaseConnection object at 0x102453ee0>
5.6.9. Use Case - 2
Http Client
>>> class http:
... @staticmethod
... def get(url):
... ...
...
... @staticmethod
... def post(url, data):
... ...
>>>
>>> http.get('https://python3.info')
>>> http.post('https://python3.info', data={'firstname': 'Alice', 'lastname': 'Apricot'})
5.6.10. Use Case - 3
The user_create() function is a helper function to create a user.
This clearly does belong to the User class, even the prefix user_
in the name suggest it. But it does not use instance (self) to work.
It does not read or write to instance variables. It is a pure function.
>>> class User:
... pass
...
...
>>> def user_create():
... print('Create User')
We can move this function to the User class, but it will require
an instance to work, or we can use @staticmethod decorator:
>>> class User:
... @staticmethod
... def create():
... print('Create User')