Методы класса в Python: синтаксис, варианты и практические сценарии
Методы класса в Python: @classmethod
Основным и наиболее распространённым способом объявления метода класса в Python является использование декоратора @classmethod. Такой метод принимает первым аргументом ссылку на сам класс (обычно обозначается cls), а не на экземпляр. Это позволяет методу работать с атрибутами класса и вызывать другие методы класса, оставаясь независимым от конкретного объекта.
class MyClass:
class_attr = 10
@classmethod
def class_method(cls, value):
cls.class_attr += value
return cls.class_attr
print(MyClass.class_method(5)) # 15атрибуты класса python (атрибуты классов и объектов в python)
15
библиотека классов python (библиотека классов в python)
В этом примере class_method изменяет атрибут класса class_attr. Вызов метода возможен как через класс, так и через экземпляр.
Как объявить метод класса без использования синтаксиса декоратора?
Можно воспользоваться встроенной функцией classmethod(). Она принимает обычную функцию и возвращает объект метода класса.
class MyClass:
class_attr = 0
def _class_method(cls):
cls.class_attr += 1
return cls.class_attr
alt_method = classmethod(_class_method)
print(MyClass.alt_method()) # 1метод объекта python (методы объектов в python)
Типичная ошибка: забыть передать функцию, а не её результат. Если написать alt_method = classmethod(_class_method()), то будет вызвана функция с неправильными аргументами и метод не заработает. Нужно передавать саму функцию без скобок.
Как использовать методы класса для создания альтернативных конструкторов?
Это одна из ключевых областей применения @classmethod. Фабричные методы позволяют создавать объекты класса разными способами.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_tuple(cls, coords):
return cls(*coords)
@classmethod
def from_polar(cls, radius, angle):
import math
return cls(radius * math.cos(angle), radius * math.sin(angle))
p1 = Point.from_tuple((3, 4))
p2 = Point.from_polar(5, 0.9273)
print(p1.x, p1.y) # 3 4
print(p2.x, p2.y) # 3.000... 4.000...Python структура объекта (структура объекта в python)
Проблема: если в подклассе переопределён конструктор, фабричные методы должны корректно работать. Использование cls вместо жёсткой ссылки на имя класса позволяет подклассам автоматически возвращать правильный тип.
Как с помощью методов класса изменять атрибуты, общие для всех экземпляров?
Методы класса идеально подходят для модификации состояния, которое хранится на уровне класса (например, счётчики, конфигурации).
class Counter:
count = 0
@classmethod
def increment(cls, delta=1):
cls.count += delta
return cls.count
@classmethod
def reset(cls):
cls.count = 0
Counter.increment()
Counter.increment(5)
print(Counter.count) # 6
Counter.reset()
print(Counter.count) # 0Python создание объектов (создание объектов в python)
Важно не путать атрибуты класса и экземпляра. Если изменить self.count внутри метода класса, это создаст локальный атрибут экземпляра и не повлияет на класс. Метод класса должен работать только с cls.
Чем метод класса отличается от статического метода и обычного метода?
Обычный метод (экземпляра) принимает self и имеет доступ к атрибутам конкретного объекта. Статический метод (@staticmethod) не принимает ни self, ни cls и работает как обычная функция внутри класса. Метод класса (@classmethod) принимает cls и может обращаться к атрибутам класса и вызывать другие методы класса.
class Demo:
@classmethod
def cm(cls):
return "classmethod"
@staticmethod
def sm():
return "staticmethod"
def im(self):
return "instance method"
print(Demo.cm()) # classmethod
print(Demo.sm()) # staticmethod
print(Demo().im()) # instance method
Ошибка: вызов обычного метода от класса (без экземпляра) приводит к ошибке TypeError, так как self не передан. Метод класса и статический метод можно вызывать как от класса, так и от экземпляра.
Расширенные примеры использования методов класса
# Пример 1: Паттерн 'Одиночка' с использованием classmethod
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
@classmethod
def get_instance(cls):
if cls._instance is None:
cls() # вызовет __new__
return cls._instance
s1 = Singleton.get_instance()
s2 = Singleton.get_instance()
print(s1 is s2) # True
True
# Пример 2: Подсчёт созданных экземпляров через методы класса
class Task:
total_tasks = 0
def __init__(self, title):
self.title = title
Task.total_tasks += 1 # можно и cls, но проще через Task
@classmethod
def task_count(cls):
return cls.total_tasks
@classmethod
def create_empty(cls):
return cls("Untitled")
t1 = Task("First")
t2 = Task("Second")
t3 = Task.create_empty()
print(Task.task_count()) # 3
3
# Пример 3: Наследование и проверка принадлежности к подклассу
class Animal:
species = "Unknown"
@classmethod
def get_species(cls):
return cls.species
@classmethod
def is_animal(cls, obj):
return isinstance(obj, cls)
class Dog(Animal):
species = "Canis lupus familiaris"
class Cat(Animal):
species = "Felis catus"
print(Animal.get_species()) # Unknown
print(Dog.get_species()) # Canis lupus familiaris
print(Cat.get_species()) # Felis catus
pet = Dog()
print(Animal.is_animal(pet)) # True (проверка по базовому классу)
print(Dog.is_animal(pet)) # True
print(Cat.is_animal(pet)) # False
Unknown Canis lupus familiaris Felis catus True True False
# Пример 4: Кэширование или общий реестр объектов
class Registered:
registry = {}
def __init__(self, name):
self.name = name
Registered.registry[name] = self
@classmethod
def get_by_name(cls, name):
return cls.registry.get(name, None)
@classmethod
def list_registered(cls):
return list(cls.registry.keys())
obj_a = Registered("alpha")
obj_b = Registered("beta")
print(Registered.list_registered()) # ['alpha', 'beta']
print(Registered.get_by_name("alpha")) # <__main__.Registered object ...>
['alpha', 'beta'] <__main__.Registered object at 0x...>
# Пример 5: Использование classmethod для парсинга данных из разных форматов
class Config:
def __init__(self, db_host, db_port):
self.db_host = db_host
self.db_port = db_port
@classmethod
def from_json(cls, json_str):
import json
data = json.loads(json_str)
return cls(data['host'], data['port'])
@classmethod
def from_dict(cls, d):
return cls(d['host'], d['port'])
cfg1 = Config.from_json('{"host":"localhost","port":5432}')
cfg2 = Config.from_dict({'host':'example.com','port':3306})
print(cfg1.db_host) # localhost
print(cfg2.db_port) # 3306
localhost 3306