Атрибуты класса
Вернёмся к чертежу на стене. Поля карточки у каждого игрока свои: имя, очки. Но на самом чертеже тоже можно что-то написать — например, «игра: шахматы». Такая надпись одна, и её видят все карточки, сделанные по чертежу.
Так и в классе. Одни данные у каждого экземпляра свои, как кличка собаки. Другие одинаковы для всех: все собаки говорят woof. Записывать такое значение в каждый экземпляр через __init__ незачем — его хранят в самом классе:
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Давайте разберём пример:
sound = "woof"стоит в теле класса, вне методов, — это надпись на чертеже.self.name = nameв__init__пишет кличку на карточкуrex.rex.speak()читает и свою кличку, и общий звук:Rex: woof.Dog.soundиrex.soundдают одно и то же значение.
Атрибут класса
Переменная, которой присвоили значение в теле класса вне методов, — атрибут класса. Он один на весь класс, и его читают и через имя класса, и через любой экземпляр. Атрибуты, созданные через self, называют атрибутами экземпляра: у каждого объекта они свои.
В атрибуте класса держат то, что одинаково для всех экземпляров:
- звук, который издают все собаки;
- единицу измерения;
- предел, например наибольшее число игроков.
Где Python ищет атрибут
Когда программа читает rex.sound, Python ищет имя в два шага:
- Сначала среди атрибутов самого
rex— на карточке. - Не найдя, он берёт атрибут класса — надпись на чертеже.
Так же находятся и методы: speak тоже хранится в классе, один для всех экземпляров. Класс без __init__ тоже можно вызвать с пустыми скобками: получится экземпляр без атрибутов экземпляра.
Присваивание через экземпляр
Присваивание Dog.sound = "bark" меняет атрибут класса — надпись на чертеже. Новое значение видят все экземпляры, у которых нет своего sound. Присваивание через экземпляр класс не трогает: оно создаёт атрибут этого экземпляра.
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Разберём по строкам:
Dog.sound = "bark"переписывает надпись на чертеже, иa, иbчитаютbark.a.sound = "grr"пишетsoundна карточкуa, а чертёж остаётся прежним.- Python находит
soundуaраньше атрибута класса, поэтому печатаетgrr, аbиDog—bark.
Своё значение закрывает атрибут класса и при следующих изменениях класса: a.sound останется grr, что бы ни присвоили Dog.sound. После del a.sound свой атрибут у a пропадёт, и a.sound снова покажет атрибут класса.
Здесь аналогия с чертежом заканчивается. Настоящий чертёж не меняется после того, как карточки заполнены, а атрибут класса можно переписать в любой момент. Все экземпляры без своего значения сразу увидят новое.
Частая ошибка
Список, который у каждого экземпляра должен быть свой, делают атрибутом класса:
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 и подпишись на канал — откроются все уроки.