Skip to content

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

При обращении с классу через точку:

img.png

Можно обратить внимание, что подсказки по возможным модификациям созданных атрибутов не выводятся, т.к. атрибуты относятся к экземпляру, а не ко всему классу.

Сравнение свойства класса и свойства объекта

Рассмотрим код, в котором класс 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_classproperty_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__ и принадлежат только конкретному объекту.
  • Изменение атрибута класса отражается на всех экземплярах, у которых нет собственного атрибута с таким именем.
  • Изменение атрибута экземпляра затрагивает только один объект.

Упражнения

  1. Создайте оглавление книги. Для задания названий и страницы используйте класс. Для визуализации отступа пробелами, точками или подчеркиваниями используйте свойство класса.
  2. Создайте чек супермаркета в котором будет класс продукта со свойствами: название, количество, цена, валюта. Реализуйте вывод чека в рублях, а затем после изменения валюты в юанях.
  3. Используя библиотеку tkinter и Canvas. Нарисовать коробки на холсте с помощью задания координат и размеров через класс. Добавьте отображение размеров коробки внутри фигуры вдоль осей. Выведите размерность в мм, затем измените на см и заново отрисуйте коробки с указанными параметрами.
  4. Создайте часы, с разными часовыми поясами, например: Лондон UTC+0, Москва UTC+3, Астана UTC+6, Пекин UTC+8. Цвет фона часов сделать светлый отрисовать, а затем поменять на темный. По примеру реализации темной/светлой темы.