Что такое метод класса в Python и зачем нужен
Альтернативный конструктор (несколько конструкторов) класса в Python
Сразу начнем с примера простого класса, который содержит обычный метод и метод класса:
class MyClass: def method(self): return 'instance method called', self @classmethod def classmethod(cls): return 'class method called', cls
Как работают методы класса в Python?
В Python, методы класса отмечаются декоратором @classmethod , следовательно в приведенном выше примере, метод класса будет определен в функции classmethod() .
Как можно заметить, метод класса вместо того, чтобы принимать аргумент self , принимает аргумент cls . При вызове метода этот аргумент указывает на сам класс, а не на экземпляр класса.
Поскольку метод класса имеет доступ только к аргументу cls , он не может изменять состояние экземпляра объекта. Для этого потребуется доступ к аргументу self . НО все же методы класса могут изменять состояние класса, которое применяется ко всем экземплярам класса.
MyClass() настроен таким образом, что реализация каждого метода возвращает кортеж для отслеживания, что происходит, и к каким частям класса или объекта метод может получить доступ.
Вот что происходит, когда мы вызываем метод экземпляра:
>>> obj = MyClass() >>> obj.method() # ('instance method called', ) # Можно передать объект `obj` экземпляра вручную, # для получения того же результата >>> MyClass.method(obj) # ('instance method called', )
Этот кусок кода подтвердил, что метод экземпляра класса имеет доступ к экземпляру объекта, напечатанному как через аргумент self .
Кстати, методы экземпляра также могут получить доступ к самому классу через атрибут self.__class__ . Это делает методы экземпляра мощными с точки зрения ограничений доступа — они могут изменять состояние как экземпляра объекта, так и самого класса.
Попробуем вызвать метод класса:
>>> obj.classmethod() # ('class method called', )
Вызов метода класса obj.classmethod() показал, что он не имеет доступа к объекту , а только к объекту , представляющему сам класс (в Python все является объектом, даже сами классы).
Обратите внимание, как Python автоматически передает класс в качестве первого аргумента функции, когда вызывается MyClass.classmethod() . Вызов метода в Python через точечную нотацию запускает это поведение. Параметр self в методах экземпляра работает точно так же.
Теперь посмотрим, что происходит, когда попытаться вызвать эти методы в самом классе — без предварительного создания экземпляра объекта:
>>> MyClass.classmethod() # ('class method called', ) >>> MyClass.method() # Traceback (most recent call last): # File "", line 1, in # TypeError: method() missing 1 required positional argument: 'self'
Из примера видно, что можно нормально вызвать метод класса MyClass.classmethod() , но попытка вызвать метод экземпляра MyClass.method() завершилась ошибкой TypeError . Этого следовало ожидать, ведь экземпляр объекта не создан, что означает, что Python не может заполнить аргумент self и следовательно, вызов не выполняется.
Для чего нужны методы класса в Python?
Следующие примеры кода должны сделать понимание метода класса более ясным. Далее рассмотрим пример класса, имеющего дело с информацией о дате (это будет шаблон):
class Date: def __init__(self, day=0, month=0, year=0): self.day = day self.month = month self.year = year def string_to_db(self): return f'self.year>-self.month>-self.day>'
Этот класс, очевидно, можно использовать для хранения информации об определенных датах, без информации о часовом поясе (предположим, что все даты представлены в формате UTC).
Здесь есть конструктор __init__ , типичный инициализатор экземпляров классов Python, который получает аргументы как типичный метод экземпляра, имея первый необязательный аргумент self , который содержит ссылку на вновь созданный экземпляр.
Например есть несколько задач, которые можно решить при помощи будущих методов этого класса, не только определенного для примера метода, банального перевода числовых значений в формат строки с датой для баз данных.
Предположим, что мы хотим создать много экземпляров класса Date() с информацией о дате, поступающей из внешнего источника, в виде строки с форматом dd.mm.yyyy . Предположим, нужно сделать это в разных местах исходного кода проекта.
Итак, что для этого необходимо сделать:
- Выполнить синтаксический анализ строки, получить день, месяц и год как три целочисленные переменные или кортеж из трех элементов, включающий эти переменные.
- Создать экземпляр Date , передав эти значения в конструктор класса при его создании.
Это будет выглядеть так:
>>> string_date = '10.10.2020' >>> day, month, year = map(int, string_date.split('.')) >>> date = Date(day, month, year) >>> date.string_to_db() # '2020-10-10'
Для выполнения этой задачи, например C++ или Java может реализовать такую возможность с перегрузкой метода __init__ , но в Python перегрузка методов отсутствует. Вместо нее необходимо использовать метод класса (декоратор @classmethod).
Создадим еще один «конструктор».
class Date(object): def __init__(self, day=0, month=0, year=0): self.day = day self.month = month self.year = year @classmethod def from_string(cls, date_as_string): day, month, year = map(int, date_as_string.split('.')) date1 = cls(day, month, year) return date1 def string_to_db(self): return f'self.year>-self.month>-self.day>'
Более подробно о перегрузке смотрите в материале «Перегрузка методов в Python».
Обратите внимание, как используется аргумент cls в методе класса from_string() вместо прямого вызова конструктора класса Date() .
Теперь создать новый экземпляр Date , так же, можно следующим образом:
>>> date1 = Date.from_string('30.12.2020') >>> date1.string_to_db() # '2020-12-30' >>> date2 = Date.from_string('01.01.2021') >>> date2.string_to_db() # '2021-1-1'
Рассмотрим приведенную выше реализацию чтобы понять, какие преимущества здесь есть:
- Реализован синтаксический анализ строки даты в одном месте, и теперь его можно использовать повторно.
- Инкапсуляция здесь отлично работает. Если вы думаете, что можете реализовать синтаксический анализ строк как единственную функцию в другом месте, это решение намного лучше соответствует парадигме ООП.
- cls — это объект, который содержит сам класс, а не его экземпляр. Это довольно круто, потому что, если мы наследуем класс Date , для всех дочерних элементов также будет определен метод класса from_string() .
Используя методы класса, можно добавить столько альтернативных конструкторов, сколько необходимо. Такое поведение может сделать интерфейс создаваемых классов самодокументированным (до определенной степени конечно) и упростить их использование.
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Пространство имен и область видимости в классах
- Определение классов
- Объект класса и конструктор класса
- Создание экземпляра класса
- Метод экземпляра класса
- Что такое метод класса и зачем нужен
- Что такое статический метод в классах Python и зачем нужен
- Атрибуты класса и переменные экземпляра класса
- Кэширование методов экземпляра декоратором lru_cache
- Закрытые/приватные методы и переменные класса Python
- Наследование классов
- Множественное наследование классов
- Абстрактные классы
- Перегрузка методов в классе Python
- Что такое миксины и как их использовать
- Класс Python как структура данных, подобная языку C
- Создание пользовательских типов данных
- Специальные (магические) методы класса Python
- Базовая настройка классов Python магическими методами
- Настройка доступа к атрибутам класса Python
- Дескриптор класса для чайников
- Протокол дескриптора класса
- Практический пример дескриптора
- Использование метода .__new__() в классах Python
- Специальный атрибут __slots__ класса Python
- Специальный метод __init_subclass__ класса Python
- Определение метаклассов metaclass
- Эмуляция контейнерных типов в классах Python
- Другие специальные методы класса
- Как Python ищет специальные методы в классах
- Шаблон проектирования Фабрика и его реализация
Python: статические методы, методы класса и экземпляра класса
Согласно модели данных Python, язык предлагает три вида методов: статические, класса и экземпляра класса. Давайте посмотрим, что же происходит за кулисами каждого из видов методов. Понимание принципов их работы поможет в создании красивого и эффективного кода. Начнём с самого простого примера, в котором демонстрируются все три вида методов.
class ToyClass:
def instancemethod(self):
return 'instance method called', self
@classmethod
def classmethod(cls):
return 'class method called', cls @staticmethod
def staticmethod():
return 'static method called'
Методы экземпляра класса
Это наиболее часто используемый вид методов. Методы экземпляра класса принимают объект класса как первый аргумент, который принято называть self и который указывает на сам экземпляр. Количество параметров метода не ограничено.
Используя параметр self , мы можем менять состояние объекта и обращаться к другим его методам и параметрам. К тому же, используя атрибут self.__class__ , мы получаем доступ к атрибутам класса и возможности менять состояние самого класса. То есть методы экземпляров класса позволяют менять как состояние определённого объекта, так и класса.
Встроенный пример метода экземпляра — str.upper() :
>>> "welcome".upper() # 'WELCOME'
Методы класса
Методы класса принимают класс в качестве параметра, который принято обозначать как cls . Он указывает на класс ToyClass, а не на объект этого класса. При декларации методов этого вида используется декоратор classmethod .
Методы класса привязаны к самому классу, а не его экземпляру. Они могут менять состояние класса, что отразится на всех объектах этого класса, но не могут менять конкретный объект.
Встроенный пример метода класса — dict.fromkeys() — возвращает новый словарь с переданными элементами в качестве ключей.
dict.fromkeys('AEIOU') #
Статические методы
Статические методы декларируются при помощи декоратора staticmethod . Им не нужен определённый первый аргумент (ни self , ни cls ).
Их можно воспринимать как методы, которые “не знают, к какому классу относятся”.
Таким образом, статические методы прикреплены к классу лишь для удобства и не могут менять состояние ни класса, ни его экземпляра.
С теорией достаточно. Давайте разберёмся с работой методов, создав объект нашего класса и вызвав поочерёдно каждый из методов: instancemethod, classmethod and staticmethod.
>>> obj = ToyClass()
>>> obj.instancemethod()
('instance method called', ToyClass instance at 0x10f47e7a0>)>>> ToyClass.instancemethod(obj)
('instance method called', ToyClass instance at 0x10f47e7a0>)
Пример выше подтверждает то, что метод instancemethod имеет доступ к объекту класса ToyClass через аргумент self . Кстати, вызов функции obj.instancemethod() используется лишь для удобства, то есть можно использовать и ToyClass.instancemethod(obj) .
Теперь давайте вызовем метод класса:
>>> obj.classmethod()
('class method called', )
Мы видим, что метод класса classmethod() имеет доступ к самому классу ToyClass, но не к его конкретному экземпляру объекта. Запомните, в Python всё является объектом. Класс тоже объект, который мы можем передать функции в качестве аргумента.
Заметьте, что self и cls — не обязательные названия и эти параметры можно называть иначе.
def instancemethod(self, . )
def classmethod(cls, . )-------то же самое, что и----------def instancemethod(my_object, . )
def classmethod(my_class, . )
Это лишь общепринятые обозначения, которым следуют все. Тем не менее они должны находиться первыми в списке параметров.
Вызовем статический метод:
>>> obj.staticmethod()
static method called
Да, это может вас удивить, но статические методы можно вызывать через объект класса. Вызов через точку нужен лишь для удобства. На самом же деле в случае статического метода никакие аргументы ( self или cls ) методу не передаются.
То есть статические методы не могут получить доступ к параметрам класса или объекта. Они работают только с теми данными, которые им передаются в качестве аргументов.
Теперь давайте вызовем те же самые методы, но на самом классе.
>>> ToyClass.classmethod()
('class method called', )>>> ToyClass.staticmethod()
'static method called'>>> ToyClass.instancemethod()
TypeError: unbound method instancemethod()
must be called with ToyClass instance as
first argument (got nothing instead)
Метод класса и статический метод работают, как нужно. Однако вызов метода экземпляра класса выдаёт TypeError, так как метод не может получить на вход экземпляр класса.
Теперь, когда вы знаете разницу между тремя видами методов, давайте рассмотрим реальный пример для понимания того, когда и какой метод стоит использовать. Пример взят отсюда.
from datetime import dateclass Person:
def __init__(self, name, age):
self.name = name
self.age = age@classmethod
def from_birth_year(cls, name, year):
return cls(name, date.today().year - year)@staticmethod
def is_adult(age):
return age > 18person1 = Person('Sarah', 25)
person2 = Person.from_birth_year('Roark', 1994)>>> person1.name, person1.age
Sarah 25>>> person2.name, person2.age
Roark 24>>> Person.is_adult(25)
True
Когда использовать каждый из методов?
Выбор того, какой из методов использовать, может показаться достаточно сложным. Тем не менее с опытом этот выбор делать гораздо проще.
Чаще всего метод класса используется тогда, когда нужен генерирующий метод, возвращающий объект класса. Как видим, метод класса from_birth_year используется для создания объекта класса Person по году рождения, а не возрасту.
Статические методы в основном используются как вспомогательные функции и работают с данными, которые им передаются.
Запомнить
- Методы экземпляра класса получают доступ к объекту класса через параметр self и к классу через self.__class__ .
- Методы класса не могут получить доступ к определённому объекту класса, но имеют доступ к самому классу через cls .
- Статические методы работают как обычные функции, но принадлежат области имён класса. Они не имеют доступа ни к самому классу, ни к его экземплярам.
- Даже если вы программируете только ради интереса, изучение ООП в Python поможет писать код так, чтобы в дальнейшем было легче искать ошибки и использовать его повторно.
Объектно-ориентированное программирование¶

Питон является развитым объектно-ориентированным языком. Всё, с чем он работает, является объектами — целые числа, строки, словари, функции и т.д. Каждый объект принадлежит определённому типу (или классу, что одно и то же). Класс тоже является объектом. Классы наследуют друг от друга. Класс object является корнем дерева классов — каждый класс наследует от него прямо или через какие-то промежуточные классы.
object,type(object)
(object, type)
Функция dir возвращает список атрибутов класса.
dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Атрибуты, имена которых начинаются и кончаются двойным подчерком, используются интерпретатором для особых целей. Например, атрибут __doc__ содержит док-строку.
object.__doc__
'The most base type'
help(object)
Help on class object in module builtins: class object | The most base type
Ниже мы рассмотрим цели некоторых других специальных атрибутов.
Вот простейший класс. Поскольку не указано, от чего он наследует, он наследует от object .
class A: pass
A,type(A)
(__main__.A, type)
Создать объект какого-то класса можно, вызвав имя класса как функцию (возможно, с какими-нибудь аргументами). Мы уже это видели: имена классов int , str , list и т.д. создают объекты этих классов.
o=A() o,type(o)
(, __main__.A)
Узнать, какому классу принадлежит объект, можно при помощи функции type или атрибута __class__ .
type(o),o.__class__
(__main__.A, __main__.A)
У только что созданного объекта o нет атрибутов. Их можно создавать (и удалять) налету.
o.x=1 o.y=2 o.x,o.y
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) in () ----> 1 o.z AttributeError: 'A' object has no attribute 'z'
del o.y o.y
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) in () 1 del o.y ----> 2 o.y AttributeError: 'A' object has no attribute 'y'
Такой объект похож на словарь, ключами которого являются имена атрибутов: можно узнать значение атрибута, изменить его, добавить новый или удалить старый. Это и неудивительно: для реализации атрибутов объекта используется именно словарь.
o.__dict__
Класс вводит пространство имён. В описании класса мы определяем его атрибуты (атрибуты, являющиеся функциями, называются методами). Потом эти атрибуты можно использовать как Class.attribute . Принято, чтобы имена классов начинались с заглавной буквы.
Вот более полный пример класса. В нём есть док-строка, метод f , статический атрибут x (атрибут класса, а не конкретного объекта) и статический метод getx (опять же принадлежащий классу, а не конкретному объекту).
class S: 'Простой класс' x=1 def f(self): print(self) @staticmethod def getx(): return S.x
Заклинание тёмной магии, начинающееся с @ , называется декоратором. Запись
@dec def fun(x): .
def fun(x): . fun=dec(fun)
То есть dec — это функция, параметр которой — функция, и он возвращает эту функцию, преобразованную некоторым образом. Мы не будем обсуждать, как самим сочинять такие заклинания — за этим обращайтесь в Дурмстранг.
Функция dir возвращает список атрибутов класса. Чтобы не смотреть снова на атрибуты, унаследованные от object , мы их вычтем.
set(dir(S))-set(dir(object))
dict(S.__dict__)
, '__doc__': 'Простой класс', '__module__': '__main__', '__weakref__': , 'f': , 'getx': , 'x': 1>
S.x=2 S.x
S.f,S.getx
S.getx()
Теперь создадим объект этого класса.
o=S() o,type(o)
(, __main__.S)
Метод класса можно вызвать и через объект.
o.getx()
Следующее присваивание создаёт атрибут объекта o с именем x . Когда мы запрашиваем o.x , атрибут x ищется сначала в объекте o , а если он там не найден — в его классе. В данном случае он найдётся в объекте o . На атрибут класса S.x это присваивание не влияет.
o.x=5 o.x,S.x
Как мы уже обсуждали, можно вызвать метод класса S.f с каким-нибудь аргументом, например, o .
Следующий вызов означает в точности то же самое. Интерпретатор питон фактически преобразует его в предыдущий.
То есть текущий объект передаётся методу в качестве первого аргумента. Этот первый аргумент любого метода принято называть self . В принципе, Вы можете назвать его как угодно, но это затруднит понимание Вашего класса читателями, воспитанными в этой традиции.
Отличие метода класса ( @staticmethod ) от метода объекта состоит в том, что такое автоматическое вставление первого аргумента не производится.
o.f — это связанный метод: S.f связанный с объектом o .
Док-строка доступна как атрибут __doc__ и используется функцией help .
S.__doc__
'Простой класс'
help(S)
Help on class S in module __main__: class S(builtins.object) | Простой класс | | Methods defined here: | | f(self) | | ---------------------------------------------------------------------- | Static methods defined here: | | getx() | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | x = 2
Классу можно добавить новый атрибут налету (равно как и удалить имеющийся).
S.y=2 S.y
Можно добавить и атрибут, являющийся функцией, т.е. метод. Сначала опишем (вне тела класса!) какую-нибудь функцию, а потом добавим её к классу в качестве нового метода.
def g(self): print(self.y) S.g=g o.g()
Менять класс налету таким образом — плохая идея. Когда в каком-то месте программы Вы видете, что используется какой-то объект некоторого класса, первое, что Вы сделаете — это посмотрите определение этого класса. И если текущее его состояние отлично от его определения, это сильно затрудняет понимание программы.
Класс S , который мы рассмотрели в качестве примера — отнюдь не пример для подражания. В нормальном объектно-ориентированном подходе объект класса должен создаваться в допустимом (пригодном к использованию) состоянии, со всеми необходимыми атрибутами. В других языках за это твечает конструктор. В питоне аналогичную роль играет метод инициализации __init__ . Вот пример такого класса.
class C: def __init__(self,x): self.x=x def getx(self): return self.x def setx(self,x): self.x=x
Теперь для создания объекта мы должны вызвать C с одним аргументом x (первый аргумент метода __init__ , self , это свежесозданный объект, в котором ещё ничего нет и который надо инициализировать).
o=C(1) o.getx()
o.setx(2) o.getx()
Этот класс — тоже не пример для подражания. В некоторых объектно-ориентированных языках считается некошерным напрямую читать и писать атрибуты; считается, что вся работа должна производиться через вызов методов. В питоне этот предрассудок не разделяют. Так что писать методы типа getx и setx абсолютно излишне. Они не добавляют никакой полезной функциональности — всё можно сделать, просто используя атрибут x .
Любой объектно-ориентированный язык, заслуживающий такого названия, поддерживает наследование. Класс C2 наследует от C . Его объекты являются вполне законными для класса C (имеют атрибут x ), но в добавок к этому имеют ещё и атрибут y . Метод __init__ теперь должен иметь 2 параметра x и y (не считая обязательного self ). К методам getx и setx , унаследованным от C , добавляются методы gety и sety .
Чтобы инициализировать атрибут x , который был в родительском классе, мы могли бы, конечно, скопировать код из метода __init__ класса C . В данном случае он столь прост, что это не преступление. Но, вообще говоря, копировать куски кода из одного места в другое категорически не рекомендуется. Допустим, в скопированном куске найден и исправлен баг. А в копии он остался. Поэтому для инициализации нового объекта, рассматриваемого как объект родительского класса C , нам следует вызвать метод __init__ класса C , а после этого довавить инициализацию атрибута y , специфичного для дочернего класса C2 . Первую часть задачи можно выполнить, вызвав C.__init__(self,x) (мы ведь только что написали строчку class , в которой указали, что класс-предок называется C ). Но есть более универсальный метод, не требующий второй раз писать имя родительского класса. Функция super() возвращает текущий объект self , рассматриваемый как объект родительского класса C . Поэтому мы можем написать super().__init__(x) .
Конечно, не только __init__ , но и другие методы дочернего класса могут захотеть вызвать методы родительского класса. Для этого используется либо вызов через имя родительского класса, либо super() .
class C2(C): def __init__(self,x,y): super().__init__(x) self.y=y def gety(self): return self.y def sety(self,y): self.y=y
Как найти методы объекта в Python
Часто в процессе работы с Python возникает необходимость узнать, какие методы доступны для какого-то конкретного объекта. Это может быть полезно, если вы работаете с новым или незнакомым объектом, и вам нужно узнать, какие функции вы можете использовать.
Возможен и другой сценарий: например, вы знаете имя метода, но не уверены, существует ли он для данного объекта. В этом случае вы захотите проверить наличие метода, прежде чем вызывать его, чтобы избежать возможных ошибок.
Для обоих этих задач в Python существуют простые и эффективные решения.
Нахождение всех методов объекта
В Python есть встроенная функция dir() , которая возвращает список атрибутов и методов для любого объекта. Вот пример ее использования:
# Создание объекта класса list my_object = list() # Получение списка всех методов и атрибутов этого объекта methods = dir(my_object) # Вывод списка print(methods)
В результате выполнения этого кода будет выведен список всех методов и атрибутов объекта list .
Проверка наличия метода у объекта
Если вам нужно проверить, есть ли у объекта конкретный метод, вы можете воспользоваться функцией hasattr() . Она принимает два аргумента: объект и строку, содержащую имя атрибута или метода, который нужно проверить.
Вот пример использования этой функции:
# Создание объекта класса list my_object = list() # Проверка наличия метода append у объекта has_method = hasattr(my_object, 'append') # Вывод результата print(has_method)
Если метод append существует у объекта my_object , то функция hasattr() вернет True , иначе — False .
Таким образом, с помощью функций dir() и hasattr() можно легко найти все методы объекта или проверить наличие конкретного метода.