Appearance
Class or object attributes - Атрибуты класса или объекта
Python позволяет определять класс как с явным конструктором, так и без него. Рассмотрим оба подхода, их отличия и ситуации, когда имеет смысл прописывать __init__ даже без входных параметров.
Термины
Термины атрибут = свойство = поле = attribute = property = field - это слова означающие одно и тоже.
Например, коробка имеет свойства: длина, ширина, высота - эти значения в классе будут свойствами или могут называться другими синонимами.
Класс без конструктора
python
class Box:
width_class = 1
length_class = 3
height_class = 5
box_1 = Box()
box_1.length_class = 15
print(box_1, box_1.width_class, box_1.length_class, box_1.height_class)
box_2 = Box()
print(box_2, box_2.width_class, box_2.length_class, box_2.height_class)
box_3 = Box()
print(box_3, box_3.width_class, box_3.length_class, box_3.height_class)
Box.width_class = 77
box_3.height_class = 33
print(box_1, box_1.width_class, box_1.length_class, box_1.height_class)
print(box_2, box_2.width_class, box_2.length_class, box_2.height_class)
print(box_3, box_3.width_class, box_3.length_class, box_3.height_class)
box_4 = Box()
print(box_4, box_4.width_class, box_4.length_class, box_4.height_class)- Атрибуты
length_class,width_class,height_classв этом примере являются атрибутами класса. - Все экземпляры
Boxпо умолчанию будут иметь одинаковые свойства. - Изменение
Box.width_classповлияет на все объекты, которые не переопределили это значение на уровне экземпляра, а также изменения коснуться новых объектов. - Изменения
box_1.length_class = 15иbox_3.height_class = 33будут применены только к соответствующим объектам класса.
Результат выполнения
python
<__main__.Box object at 0x0000020678777CB0> 1 15 5
<__main__.Box object at 0x00000206789F7110> 1 3 5
<__main__.Box object at 0x00000206789F7250> 1 3 5
<__main__.Box object at 0x0000020678777CB0> 77 15 5
<__main__.Box object at 0x00000206789F7110> 77 3 5
<__main__.Box object at 0x00000206789F7250> 77 3 33
<__main__.Box object at 0x00000206787E9E00> 77 3 5Конструктор __init__ без параметров
В питоне имя конструктора зарезервировано под названием __init__ - это функция объявляемая внутри класса.
python
class Box:
def __init__(self):
self.width_object = 1
self.length_object = 3
self.height_object = 5
box_1 = Box()
box_1.length_object = 15
print(box_1, box_1.width_object, box_1.length_object, box_1.height_object)
box_2 = Box()
print(box_2, box_2.width_object, box_2.length_object, box_2.height_object)- Метод
__init__создаёт атрибуты экземпляра, уникальные для каждого объекта. - Даже без параметров внешнего вызова, внутри конструктора можно задать любые начальные значения.
- При создании двух разных объектов они получат свои собственные копии атрибутов.
Результат выполнения
python
<__main__.Box object at 0x0000017529087CB0> 1 15 5
<__main__.Box object at 0x0000017529317110> 1 3 5При обращении с классу через точку:

Можно обратить внимание, что подсказки по возможным модификациям созданных атрибутов не выводятся, т.к. атрибуты относятся к экземпляру, а не ко всему классу.
Сравнение свойства класса и свойства объекта
Рассмотрим код, в котором класс Box определяет и свойство класса, и свойство экземпляра:
python
class Box:
property_class = 'class_box'
def __init__(self):
self.property_object = 'object_box'
box_1 = Box()
print(box_1, box_1.property_class, box_1.property_object)
box_2 = Box()
print(box_2, box_2.property_class, box_2.property_object)
box_1.property_object = 'object_box_1'
Box.property_class = "class_box_1"
print(box_1, box_1.property_class, box_1.property_object)
print(box_2, box_2.property_class, box_2.property_object)Что происходит при создании экземпляров:
- При объявлении
property_class = 'class_box'создаётся атрибут класса. - Внутри
__init__устанавливаетсяself.property_object = 'object_box'– это атрибут экземпляра. - Когда выполняется
box_1 = Box(), дляbox_1создаётся своё хранилище атрибутов, в него попадаетproperty_object. - Атрибут
property_classне копируется в экземпляр: его Python будет искать на уровне класса.
Результат выполнения
python
<__main__.Box object at 0x00000205EEA07CB0> class_box object_box
<__main__.Box object at 0x00000205EEC87110> class_box object_box
<__main__.Box object at 0x00000205EEA07CB0> class_box_1 object_box_1
<__main__.Box object at 0x00000205EEC87110> class_box_1 object_boxproperty_objectвсегда хранится в каждом экземпляре.property_classлежит в классе и общий для всех экземпляров до тех пор, пока его не изменить на уровне экземпляра.
Сравнение обращения к классу и создание экземпляра
python
class Box:
property_class = 'class_box'
def __init__(self):
self.property_object = 'object_box'
box_1 = Box
print(box_1, box_1.property_class)
box_2 = Box()
print(box_2, box_2.property_class, box_2.property_object)
Box.property_class = 'property_changed_from_Box_class'
print(box_1, box_1.property_class)
print(box_2, box_2.property_class, box_2.property_object)
box_3 = Box()
print(box_3, box_3.property_class, box_3.property_object)
box_1.property_class = 'property_changed_from_box_1_object'
print(box_1, box_1.property_class)
print(box_2, box_2.property_class, box_2.property_object)
print(box_3, box_3.property_class, box_3.property_object)Результат выполнения
python
<class '__main__.Box'> class_box
<__main__.Box object at 0x0000023DA6257CB0> class_box object_box
<class '__main__.Box'> property_changed_from_Box_class
<__main__.Box object at 0x0000023DA6257CB0> property_changed_from_Box_class object_box
<__main__.Box object at 0x0000023DA6597110> property_changed_from_Box_class object_box
<class '__main__.Box'> property_changed_from_box_1_object
<__main__.Box object at 0x0000023DA6257CB0> property_changed_from_box_1_object object_box
<__main__.Box object at 0x0000023DA6597110> property_changed_from_box_1_object object_boxbox_1 = Box без скобочек
- При
box_1 = Boxмы не создаём экземпляр, а сохраняем вbox_1ссылку на сам класс. - Поле
property_classпри этом получается из атрибутов класса. - Атрибут
property_objectнедоступен, потому что__init__не вызывался.
box_2 = Box() и box_3 = Box()
- Конструкция
Box()порождает новый объект, вызывает__init__, и в его записываетсяproperty_object. - Поэтому оба экземпляра до изменений видят первоначальное значение
'class_box'.
Изменение через Box.property_class
python
Box.property_class = 'property_changed_from_Box_class'- Меняет атрибут класса сразу для всех ссылок на класс и его экземпляров (если у экземпляра нет своего
property_class). box_1.property_classиbox_2.property_classначинают возвращать'property_changed_from_Box_class'.
Изменение через box_1.property_class
python
box_1.property_class = 'property_changed_from_box_1_object'- Поскольку
box_1— это класс, эта строка равносильнаBox.property_class = .... - На класс записывается новое значение, и оно снова видно всем экземплярам (
box_2,box_3) и самой переменной-алиасуbox_1. - Если бы мы написали
some_instance.property_class = …, то создался бы локальный атрибут экземпляра, не затрагивая класс.
Итоговое поведение
| Объект | Тип | property_class | property_object |
|---|---|---|---|
| box_1 | класс | property_changed_from_box_1_object (class) | — |
| box_2 | экземпляр | property_changed_from_box_1_object (inherited) | object_box |
| box_3 | экземпляр | property_changed_from_box_1_object (inherited) | object_box |
- Присвоение без скобок (
box_1 = Box) не вызывает__init__и не создаёт объект. - Любая запись в
box_1.property_classпри таком случае изменяет атрибут класса. - Экземпляры наследуют текущее значение
property_class, пока не перекроют его собственной переменной.
Ключевые выводы
- Свойства класса хранятся один раз и разделяются между всеми объектами.
- Свойства экземпляра создаются в
__init__и принадлежат только конкретному объекту. - Изменение атрибута класса отражается на всех экземплярах, у которых нет собственного атрибута с таким именем.
- Изменение атрибута экземпляра затрагивает только один объект.
Упражнения
- Создайте оглавление книги. Для задания названий и страницы используйте класс. Для визуализации отступа пробелами, точками или подчеркиваниями используйте свойство класса.
- Создайте чек супермаркета в котором будет класс продукта со свойствами: название, количество, цена, валюта. Реализуйте вывод чека в рублях, а затем после изменения валюты в юанях.
- Используя библиотеку tkinter и Canvas. Нарисовать коробки на холсте с помощью задания координат и размеров через класс. Добавьте отображение размеров коробки внутри фигуры вдоль осей. Выведите размерность в
мм, затем измените насми заново отрисуйте коробки с указанными параметрами. - Создайте часы, с разными часовыми поясами, например: Лондон UTC+0, Москва UTC+3, Астана UTC+6, Пекин UTC+8. Цвет фона часов сделать светлый отрисовать, а затем поменять на темный. По примеру реализации темной/светлой темы.