3.1. 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'
3.1.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
3.1.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
3.1.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
3.1.4. 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')
3.1.5. 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
3.1.6. 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
),
3.1.7. 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>
3.1.8. 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'})
3.1.9. 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')