- Статические методы и их особенности
- Классовые атрибуты и их использование
- Работа с наследованием и методы класса
- Выводы
В объектно-ориентированном программировании встречаются различные типы методов и атрибутов, включая статические методы, методы класса и классовые атрибуты. Хотя эти элементы являются фундаментальными, они часто могут сбивать с толку, особенно, когда дело доходит до наследования и переопределения.
Как использовать статические методы, не нарушая при этом иерархию наследования? Как правильно применять атрибуты класса, и в чем их отличие от атрибутов экземпляра? В этом уроке мы разберем эти и другие вопросы, а также рассмотрим сложные моменты работы с методами и атрибутами.
Статические методы и их особенности
В отличие от экземпляров классов обращение к статическим методам в Python работает немного иначе. Когда мы объявляем метод как статический с помощью декоратора @staticmethod
, он ведет себя как обычная функция, но она находится в пространстве имен класса:
class A:
def simple_func():
print("Simple Function")
@staticmethod
def who():
print('A')
@staticmethod
def test():
A.who()
class B(A):
@staticmethod
def who():
print('B')
obj = B()
obj.simple_func() # Это вызовет ошибку, потому что Python пытается передать self в simple_func
B.test() # 'A'
В этом примере статический метод test()
вызывает A.who()
, который находится внутри класса A
. Поэтому A.who()
указывает на сам A
. Никакая иерархия наследования не может изменить это поведение.
Подобное поведение можно рассматривать как ограничение. Фактически оно игнорирует факт наследования. Но перед тем, как вдаваться в детали, нужно немного разобраться с тем, какую роль играют статические методы в классах.
class A:
@staticmethod
def who():
print('A')
A.who() # 'A'
В этом примере мы просто объявляем класс A
со статическим методом who()
, который выводит 'A'. Когда мы вызываем A.who()
, он корректно выводит 'A' и показывает, что статические методы можно вызывать напрямую от класса. Но если нам нужно хранить информацию, специфичную для класса, мы можем использовать классовые атрибуты.
Классовые атрибуты и их использование
Данные в классовых атрибутах относятся к классу в целом. Они «описывают» его. В Python самый распространенный пример — это связь сущности с таблицей в базе данных, где она хранится:
class User:
# Очень важно делать их неизменяемыми!
_table = 'users'
Почему эту информацию нужно хранить в классовом атрибуте? Потому что она не принадлежит конкретному объекту.
Представим, что мы хотим узнать из кода, в какую таблицу будут сохраняться пользователи. Если бы эта информация была связана с конкретным объектом, пришлось бы создавать объект только ради того, чтобы узнать ответ на наш вопрос. Это бессмысленно.
Для сохранения сущности в базу данных обычно используются библиотеки ORM (Object-Relationship Mapping), которые знают, как сохранять сущности в базу и как извлечь их. Большинство из них построено на наследовании. Любая сущность должна наследоваться от специального базового класса, который содержит в себе общую логику для работы с базой данных:
class BaseEntity:
# Код
class User(BaseEntity):
pass
user = User()
# Сохранение в базу
# user.save()
Метод save()
закомментирован, потому что в Python нет встроенной функции сохранения объектов в базу данных. Вместо этого мы используем библиотеки ORM, такие, как SQLAlchemy или Django ORM.
Для сохранения пользователя в базу недостаточно знать, что сохранять. Еще нужно понимать, куда сохранять. И эта информация записана в классовом атрибуте:
class User(BaseEntity):
_table = 'users'
В данном случае класс User
, который наследуется от BaseEntity
, содержит классовый атрибут _table
на 'users'. Это означает, что когда мы сохраняем объект User
, мы знаем, что его следует сохранить в таблице 'users'. Но что делать, если мы хотим обратиться к этой информации из базового класса? Рассмотрим этот нюанс подробнее.
Работа с наследованием и методы класса
Возникает вопрос, можно ли добраться до нее из базового класса BaseEntity
?
Рассмотрим следующий код:
class BaseEntity:
def get_table():
return _table
class User(BaseEntity):
_table = 'users'
print(User.get_table()) # NameError: name '_table' is not defined
В этом примере мы пытаемся получить доступ к атрибуту _table
через метод get_table()
. Проблема в том, что такой код не сработает. User.get_table()
ссылается на BaseEntity
, и вызов get_table()
завершится с ошибкой, так как в этом классе нет классового атрибута _table
.
Из этой ситуации есть выход: использовать @classmethod
:
class BaseEntity:
@classmethod
def get_table(cls):
return cls._table
class User(BaseEntity):
_table = 'users'
print(User.get_table()) # 'users'
Изменив get_table()
на метод класса с помощью декоратора @classmethod
, мы можем вызвать метод для экземпляра класса и получить доступ к атрибуту _table
текущего класса. Теперь вызов get_table()
вернет значение классового атрибута _table
, определенного в том классе, с объектом которого идет работа прямо сейчас.
Обратите внимание, что мы используем cls
вместо self
. Ключевое слово cls
используется в Python для обозначения текущего класса в контексте метода класса.
В Python метод класса, определенный с использованием декоратора @classmethod
, автоматически получает ссылку на класс, а не на экземпляр. Это позволяет нам обращаться к атрибутам класса внутри метода класса, даже если этот атрибут переопределен в подклассе.
Например, определим метод get_table
в базовом классе BaseEntity
и переопределим атрибут _table
в классе User
:
class BaseEntity:
_table = 'base_entity'
@classmethod
def get_table(cls):
return cls._table
class User(BaseEntity):
_table = 'users'
print(User.get_table()) # 'users'
Можно заметить, что вызов get_table
для класса User
возвращает значение атрибута _table
, определенное в классе User
, а не в BaseEntity
. Это показывает, что метод класса get_table
работает с текущим классом (User
), а не с классом, в котором он был определен (BaseEntity
).
Выводы
Использование методов класса с помощью декоратора @classmethod
позволяет нам работать с атрибутами текущего класса, а не базового класса. Это особенно полезно при работе с наследованием, когда подклассы могут переопределить некоторые атрибуты класса.
Ключевое слово cls
используется для обозначения текущего класса в контексте метода класса. Помимо этого мы увидели, как классовые атрибуты могут быть использованы для хранения информации, специфичной для класса, такой как имя таблицы в базе данных для сохранения объектов этого класса.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.