Перейти к содержимому
Теория · Классы · урок 5 из 11

Атрибуты класса

Вернёмся к чертежу на стене. Поля карточки у каждого игрока свои: имя, очки. Но на самом чертеже тоже можно что-то написать — например, «игра: шахматы». Такая надпись одна, и её видят все карточки, сделанные по чертежу.

Так и в классе. Одни данные у каждого экземпляра свои, как кличка собаки. Другие одинаковы для всех: все собаки говорят woof. Записывать такое значение в каждый экземпляр через __init__ незачем — его хранят в самом классе:

Python
class Dog:
    sound = "woof"

    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name}: {self.sound}"


rex = Dog("Rex")
print(rex.speak())  # Rex: woof
print(Dog.sound, rex.sound)  # woof woof

Давайте разберём пример:

  1. sound = "woof" стоит в теле класса, вне методов, — это надпись на чертеже.
  2. self.name = name в __init__ пишет кличку на карточку rex.
  3. rex.speak() читает и свою кличку, и общий звук: Rex: woof.
  4. Dog.sound и rex.sound дают одно и то же значение.

Атрибут класса

Переменная, которой присвоили значение в теле класса вне методов, — атрибут класса. Он один на весь класс, и его читают и через имя класса, и через любой экземпляр. Атрибуты, созданные через self, называют атрибутами экземпляра: у каждого объекта они свои.

В атрибуте класса держат то, что одинаково для всех экземпляров:

  • звук, который издают все собаки;
  • единицу измерения;
  • предел, например наибольшее число игроков.

Где Python ищет атрибут

Когда программа читает rex.sound, Python ищет имя в два шага:

  1. Сначала среди атрибутов самого rex — на карточке.
  2. Не найдя, он берёт атрибут класса — надпись на чертеже.

Так же находятся и методы: speak тоже хранится в классе, один для всех экземпляров. Класс без __init__ тоже можно вызвать с пустыми скобками: получится экземпляр без атрибутов экземпляра.

Присваивание через экземпляр

Присваивание Dog.sound = "bark" меняет атрибут класса — надпись на чертеже. Новое значение видят все экземпляры, у которых нет своего sound. Присваивание через экземпляр класс не трогает: оно создаёт атрибут этого экземпляра.

Python
a = Dog("Ace")
b = Dog("Bo")
Dog.sound = "bark"
print(a.sound, b.sound)  # bark bark
a.sound = "grr"
print(a.sound, b.sound, Dog.sound)  # grr bark bark

Разберём по строкам:

  1. Dog.sound = "bark" переписывает надпись на чертеже, и a, и b читают bark.
  2. a.sound = "grr" пишет sound на карточку a, а чертёж остаётся прежним.
  3. Python находит sound у a раньше атрибута класса, поэтому печатает grr, а b и Dog — bark.

Своё значение закрывает атрибут класса и при следующих изменениях класса: a.sound останется grr, что бы ни присвоили Dog.sound. После del a.sound свой атрибут у a пропадёт, и a.sound снова покажет атрибут класса.

Здесь аналогия с чертежом заканчивается. Настоящий чертёж не меняется после того, как карточки заполнены, а атрибут класса можно переписать в любой момент. Все экземпляры без своего значения сразу увидят новое.

Частая ошибка

Список, который у каждого экземпляра должен быть свой, делают атрибутом класса:

Python
class Team:
    members = []  # ✗

    def add(self, name):
        self.members.append(name)


red = Team()
blue = Team()
red.add("Ann")
print(blue.members)  # ['Ann']

self.members.append(name) ничего не присваивает: он находит в классе единственный список и меняет его. Этот список общий для всех команд. Создавайте такой список в __init__: self.members = [], и у каждого экземпляра он будет свой.

Эту ловушку часто показывают на собеседовании: «Почему игрок одной команды попал в другую?» Ответ: изменяемый атрибут класса один на все экземпляры.

С числом такой ошибки нет. Если в классе стоит count = 0, то self.count += 1 читает 0 из класса и записывает 1 в новый атрибут экземпляра. Атрибут класса при этом не меняется.

Главное

  • Атрибут класса задают в теле класса вне методов; он один на все экземпляры.
  • Python ищет атрибут сначала в экземпляре, потом в классе.
  • Присваивание через экземпляр создаёт его собственный атрибут, а класс не меняет.
  • Изменяемый список в атрибуте класса общий для всех экземпляров — создавайте его в __init__.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом