Атрибуты класса и атрибуты экземпляра
Атрибут класса один на всех, атрибут экземпляра — личный. Разбираем порядок поиска атрибутов и классическую ловушку общего списка — с исправлением.
Редакция Питоники
Атрибуты из прошлого урока принадлежали конкретному объекту: у каждого игрока своё имя и здоровье. Но у сущностей есть и общее: у всех заказов одна валюта, у всех роботов — версия прошивки, у всех домов — материал по умолчанию. Дублировать общее в каждом объекте расточительно — для этого у класса есть собственные атрибуты.
Атрибут класса: один на всех
Атрибут класса записывается в теле класса, вне методов. Читать его можно и через экземпляр, и через сам класс. А вот записывать через экземпляр опасно: присваивание first.material = "дерево" создаёт атрибут экземпляра, который заслоняет общий, — сам атрибут класса при этом не меняется. Присваивание через экземпляр создаёт атрибут экземпляра и заслоняет атрибут класса, но не меняет его.
class House:
material = "кирпич"
def __init__(self, address):
self.address = address
first = House("ул. Липовая, 7")
second = House("пр. Мира, 1")
first.material = "дерево"
print(first.material)
print(second.material)
print(House.material)
дерево кирпич кирпич
class House:
material = "кирпич"
h = House()
print(h.material)
h.material = "дерево"
print(h.material)
del h.material
print(h.material)
кирпич дерево кирпич
Поведение объясняется порядком поиска атрибута. Читая h.material, Python смотрит сначала в атрибутах экземпляра, затем в атрибутах класса, затем в родителях класса — о порядке в сложных иерархиях расскажет урок про MRO. Нашлось «дерево» в экземпляре — классовое «кирпич» осталось нетронутым. Удалили del-ом — поиск провалился ниже и снова достал классовое значение. Чтобы сменить значение для всех сразу, присваивайте через класс: House.material = "бетон". Но правило работает лишь для неизменяемых значений — числа и строки переприсваиваются целиком. Списки и словари не переприсваивают, а изменяют на месте — и здесь начинается главная ловушка.
Ловушка изменяемого атрибута класса
class Robot:
parts = []
def __init__(self, name):
self.name = name
def add_part(self, part):
self.parts.append(part)
roby = Robot("Роби")
wally = Robot("Валли")
roby.add_part("манипулятор")
print(roby.parts)
print(wally.parts)
['манипулятор'] ['манипулятор']
- Метод add_part выполняет self.parts.append(part): Python ищет parts у экземпляра, не находит и берёт общий список класса;
- append изменяет список на месте — присваивания нет, а значит, нет и шанса создать личный атрибут;
- у Роби и Валли один и тот же список, поэтому деталь «появилась» у обоих — код ошибок не печатал, данные просто общие.
class Robot:
def __init__(self, name):
self.name = name
self.parts = []
def add_part(self, part):
self.parts.append(part)
roby = Robot("Роби")
wally = Robot("Валли")
roby.add_part("манипулятор")
print(roby.parts)
print(wally.parts)
['манипулятор'] []
Сначала предскажи ответ в голове — это главный навык программиста.
class Order:
created = 0
def __init__(self):
Order.created += 1
a = Order()
b = Order()
c = Order()
print(Order.created)
class Order:
created = 0
def __init__(self):
self.created += 1
a = Order()
b = Order()
print(a.created, Order.created)
class Team:
members = []
a = Team()
b = Team()
a.members.append("Анна")
b.members.append("Борис")
print(b.members)
1. Где создаётся атрибут экземпляра?
2. Что вернёт first.material после first.material = "дерево", если у класса material = "кирпич"?
3. Что произойдёт с House.material после first.material = "дерево"?
4. Почему add_part через self.parts.append(...) меняет список у всех роботов?
5. Как исправить ловушку общего списка?
6. Для чего атрибут класса уместен?
Создайте класс GameServer с атрибутом класса status = "онлайн" (общий для всех серверов) и __init__(self, region), сохраняющим регион в атрибуте экземпляра. Создайте два сервера — европа и азия, затем переключите статус глобально через класс на «техработы» и выведите две строки: статус азиатского сервера и статус класса.
Чем атрибут класса отличается от атрибута экземпляра?
Атрибут класса записан в теле класса и один на всех: его видят все экземпляры и сам класс (House.material). Атрибут экземпляра создаётся через self и у каждого объекта свой (self.address). При чтении Python сначала ищет атрибут в экземпляре, затем в классе.
Урок 2: __init__ и selfПочему список, объявленный в теле класса, общий для всех объектов?
Потому что он создаётся один раз при выполнении тела класса, а экземпляры получают не копию, а ссылку на тот же список. Методы вроде append меняют его на месте, и все экземпляры видят изменения. Решение — создавать список в __init__ через self.parts = [].
Как поменять атрибут класса для всех экземпляров?
Через сам класс: House.material = "бетон". Присваивание через экземпляр (first.material = "дерево") создаст личный атрибут экземпляра, который лишь заслонит общий, — класс останется прежним.
Куда Python ищет атрибут, если его нет ни в экземпляре, ни в классе?
Дальше по цепочке родителей: если атрибута нет ни у экземпляра, ни у класса, интерпретатор поднимается по базовым классам. Порядок обхода этой цепочки называется MRO и подробно разбирается в уроке про super() и MRO.
Урок 8: super() и MROПонравился урок? Сошлитесь на него
«Присваивание через экземпляр создаёт атрибут экземпляра и заслоняет атрибут класса, но не меняет его.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
ООП · Урок 2
__init__ и self: как рождается объект
__init__ задаёт объекту стартовое состояние, а self — это сам объект внутри его методов. Разбираем механику вызова hero.heal(30) до последней детали.
ООП · Урок 4
Методы: обычные, classmethod и staticmethod
У методов Python три лика: обычный с self, classmethod с cls и staticmethod без ничего. Строим альтернативный конструктор from_string и расставляем границы.
ООП · Урок 8
super() и порядок MRO: как Python ищет методы
super() вызывает не «метод родителя», а следующий метод в порядке MRO — порядке, в котором Python ищет атрибуты по иерархии. Разбираем __mro__, расширение методов и кооперативные цепочки вызовов.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
BeautifulSoup / Scrapy · Урок 3
Поиск элементов: find, find_all и CSS-селекторы в BeautifulSoup
Два метода find и find_all плюс семейство select закрывают большинство задач поиска. Разбираем их отличия, ловушки с class_ и attrs и применяем к каталогу товаров.
find и find_all в beautifulsoup разницапоиск элементов html python
BeautifulSoup / Scrapy · Урок 4
Извлечение данных: text, get_text, атрибуты и навигация по дереву
Найти элемент — полдела: данные лежат в тексте и атрибутах. Разбираем text против string, безопасное чтение атрибутов и ходьбу по дереву от родителя к соседям.
beautifulsoup получить атрибут href ссылкиатрибуты html python
ООП · Урок 6
@property: вычисляемые атрибуты
@property превращает метод в атрибут без скобок: площадь пересчитывается сама, сеттер проверяет данные. Разбираем, когда свойство излишне.
свойства класса pythonpython атрибут метод без скобок