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_box
property_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_box
box_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. Цвет фона часов сделать светлый отрисовать, а затем поменять на темный. По примеру реализации темной/светлой темы.