classmethod, staticmethod и альтернативные конструкторы
Три вида методов
| Вид | Декоратор | Первый аргумент | Доступ |
|---|---|---|---|
| Метод экземпляра | — | self — объект |
к данным объекта и класса |
| Метод класса | @classmethod |
cls — класс |
к данным класса, может создавать объекты |
| Статический метод | @staticmethod |
— | ни к чему, просто функция в пространстве имён класса |
class Pizza:
sizes = {"S": 25, "M": 30, "L": 35}
def __init__(self, toppings, size="M"):
self.toppings = toppings
self.size = size
def describe(self): # метод экземпляра
return f"Пицца {self.sizes[self.size]} см: {', '.join(self.toppings)}"
@classmethod
def margherita(cls, size="M"): # метод класса
return cls(["моцарелла", "томаты"], size)
@staticmethod
def area(diameter): # статический метод
return round(3.14159 * (diameter / 2) ** 2)
print(Pizza(["грибы"]).describe())
print(Pizza.margherita("L").describe())
print(Pizza.area(30), Pizza(["сыр"]).area(25)) # статический — и через класс, и через объект
Как Python передаёт первый аргумент
class Demo:
def instance_m(self):
return f"self = {type(self).__name__} объект"
@classmethod
def class_m(cls):
return f"cls = {cls.__name__}"
@staticmethod
def static_m():
return "ничего не получил"
d = Demo()
print(d.instance_m())
print(d.class_m(), "|", Demo.class_m()) # через объект и класс — cls одинаковый
print(d.static_m(), "|", Demo.static_m())
try:
Demo.instance_m() # self некому подставить
except TypeError as e:
print("TypeError:", e)
classmethod и staticmethod — это дескрипторы: при обращении obj.m они решают, что подставить первым аргументом. Механизм разберём в уроке «Дескрипторы и slots».
Альтернативные конструкторы
__init__ в классе один. Если объект нужно создавать из разных источников — строки, словаря, файла, — заводят методы класса from_...:
import json
class User:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_dict(cls, data):
return cls(data["name"], int(data["age"]))
@classmethod
def from_json(cls, text):
return cls.from_dict(json.loads(text))
@classmethod
def from_csv_line(cls, line):
name, age = line.split(",")
return cls(name.strip(), int(age))
def __repr__(self):
return f"{type(self).__name__}({self.name!r}, {self.age})"
print(User.from_dict({"name": "Анна", "age": "28"}))
print(User.from_json('{"name": "Боря", "age": 31}'))
print(User.from_csv_line("Вера, 25"))
Так устроены и встроенные типы:
from datetime import datetime, date
print(dict.fromkeys(["a", "b"], 0))
print(datetime.fromisoformat("2026-10-25T12:30"))
print(date.fromtimestamp(0))
print(int.from_bytes(b"\x01\x00", "big"))
Почему cls, а не имя класса
class Shape:
def __init__(self, size):
self.size = size
@classmethod
def unit(cls):
return cls(1) # правильно: создаст объект того класса, через который вызвали
@classmethod
def unit_bad(cls):
return Shape(1) # жёстко прописан базовый класс
class Square(Shape):
def area(self):
return self.size ** 2
print(type(Square.unit()).__name__, Square.unit().area())
print(type(Square.unit_bad()).__name__) # Shape — у него нет area()
Метод класса для состояния класса
class Connection:
_registry = []
max_connections = 2
def __init__(self, host):
self.host = host
type(self)._registry.append(self)
@classmethod
def open(cls, host):
if len(cls._registry) >= cls.max_connections:
raise RuntimeError(f"лимит {cls.max_connections} соединений")
return cls(host)
@classmethod
def count(cls):
return len(cls._registry)
Connection.open("db1")
Connection.open("db2")
print(Connection.count())
try:
Connection.open("db3")
except RuntimeError as e:
print("RuntimeError:", e)
staticmethod или функция модуля?
Статический метод — это обычная функция, положенная в класс ради группировки. Ставьте её в класс, если она логически связана с ним (валидатор, конвертер единиц) и её удобно вызывать как Temperature.c_to_f(...). Если функция полезна сама по себе — оставьте её функцией модуля: в Python не нужно всё складывать в классы.
Правило выбора
Нужен объект → обычный метод. Нужен класс (создать объект, прочитать настройки класса) → @classmethod. Не нужно ни то, ни другое → @staticmethod или функция модуля.
Вопрос на собеседовании
«Чем classmethod отличается от staticmethod?» — classmethod получает класс cls (с учётом наследования) и используется для альтернативных конструкторов и работы с состоянием класса. staticmethod ничего не получает — это функция в пространстве имён класса.
Итог
- Обычный метод получает
self,@classmethod—cls,@staticmethod— ничего. - Альтернативные конструкторы
from_...— классический случай для@classmethod; внутри пишитеcls(...). - Статический метод — функция, сгруппированная с классом по смыслу.
Практика решено 0 из 5
Добавьте в класс Date альтернативный конструктор from_string(cls, text),
который принимает строку "25.10.2026" и возвращает объект класса cls.
Используйте именно cls, чтобы наследник MyDate.from_string(...) создавал MyDate.
Какие тесты проверяют код (2)
d = Date.from_string("25.10.2026"); assert (d.day, d.month, d.year) == (25, 10, 2026)
class MyDate(Date):
pass
assert type(MyDate.from_string("1.1.2000")) is MyDate, "Наследник должен создавать объект своего класса — используйте cls"
class Date:
def __init__(self, day, month, year):
self.day, self.month, self.year = day, month, year
@classmethod
def from_string(cls, text):
day, month, year = map(int, text.split("."))
return cls(day, month, year)
Добавьте в класс Password статический метод is_strong(text):
пароль сильный, если в нём не меньше 8 символов, есть хотя бы одна цифра и хотя бы одна заглавная буква.
Метод должен вызываться и через класс, и через экземпляр.
Какие тесты проверяют код (3)
assert Password.is_strong("Secret123") is True
assert not Password.is_strong("secret123") and not Password.is_strong("Sec1")
assert Password("Qwerty12345").text == "Qwerty12345"
class Password:
def __init__(self, text):
if not self.is_strong(text):
raise ValueError("Слабый пароль")
self.text = text
@staticmethod
def is_strong(text):
return (len(text) >= 8
and any(c.isdigit() for c in text)
and any(c.isupper() for c in text))
Что получает первым аргументом каждый метод?
| экземпляр (self) | класс (cls) | ничего | |
|---|---|---|---|
обычный метод def m(self) |
|||
@classmethod |
|||
@staticmethod |
|||
@classmethod, вызванный через экземпляр obj.m() |
Почему в альтернативном конструкторе пишут return cls(...), а не return Date(...)?
Где в стандартной библиотеке используется classmethod как альтернативный конструктор?
Войдите, чтобы отмечать пройденные уроки.