5.7. OOP Staticmethod
Method which don't use
self
in its body should not be in a classIf method takes
self
and use it (it requires instances to work) it should be in classIf a method don't use
self
but uses class as a namespace use@staticmethod
decoratorUsing 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.7.1. Problem: Instance Method
Instance methods require
self
as a first argumentCalling instance method on a class will result in
TypeError
Calling 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.7.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.7.3. Solution: Static Method
Calling static method on a class will work
Calling static method on an instance will work
Use
@staticmethod
decorator 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.7.4. Case Study
import json
from dataclasses import dataclass
@dataclass
class User:
firstname: str
lastname: str
DATA = '{"firstname": "Mark", "lastname": "Watney"}'
result = User(**json.loads(DATA))
result
# User(firstname='Mark', lastname='Watney')
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": "Mark", "lastname": "Watney"}'
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='Mark', lastname='Watney')
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": "Mark", "lastname": "Watney"}'
result = User.from_json(string=DATA)
result
# User(firstname='Mark', lastname='Watney')
5.7.5. Case Study 1
>>> from dataclasses import dataclass
>>> import json
Assume we received DATA
from the REST API endpoint:
>>> DATA = '{"firstname": "Mark", "lastname": "Watney"}'
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='Mark', lastname='Watney')
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='Mark', lastname='Watney')
5.7.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.7.7. When to Use Staticmethod
Some functions in a class do not require instance (
self
) to workHence, they can be
@staticmethod
... but should they?@staticmethod
is 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.7.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.7.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': 'Mark', 'lastname': 'Watney'})
5.7.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')