Deep Engineering

MEASUREMENT

bench/oop/practice.py

The script that produced the numbers in the article, and the record of the run. The file is read from the repository at build time — this is the code that was run, not a copy of it.

Cited in
/en/interview/python/classes-and-oop
How to run it
python3.11 bench/oop/basics.py
python3.12 bench/oop/basics.py
python3.13 bench/oop/basics.py
python3.14 bench/oop/basics.py

The run below is recorded in Russian. It is a lab record, kept in the language it was written in; the numbers, the tables and the code read the same either way.

Record of the run

Замеры для урока «Классы и ООП»

Скрипт Что делает
basics.py наблюдения без времени: self и связанный метод, атрибут класса против атрибута экземпляра, три вида методов и фабрики стандартной библиотеки через наследника, super().__init__(), _x и __x, __new__/__init__, __eq__ без __hash__, хеш у датаклассов, __slots__ и словарь экземпляра (раздел 7)
practice.py источник ответов двух практических задач урока
python3.11 bench/oop/basics.py
python3.12 bench/oop/basics.py
python3.13 bench/oop/basics.py
python3.14 bench/oop/basics.py

Записи в runs/: без суффикса — CPython 3.13.7, остальные версии — с суффиксом (3.11.15, 3.12.3, 3.14.7); linux/amd64. Перезапущены 14.10.2026 после добавления раздела 7 — разделы 1–6 не изменились.

Цена вызова метода здесь не меряется: она снята в bench/attr-lookup/ и разобрана в статье о поиске атрибута. Урок ссылается туда, а не повторяет числа.

Что получилось

self — обычный первый аргумент. a.hello() и Greeter.hello(a) дали одно и то же. a.hello — объект типа method, и каждый раз новый: a.hello is a.hello — False, тысяча обращений — тысяча разных объектов. Метод, объявленный без self, падает при вызове через экземпляр: takes 0 positional arguments but 1 was given.

Изменяемый атрибут класса — общий. Три экземпляра добавили по метке в tags, объявленный в теле класса, — каждый видит все три, а в словаре экземпляра (vars(c2)) пусто. Присваивание через экземпляр (c1.total = 5) создаёт свой атрибут и затеняет классовый, класс не меняется.

Три вида методов. @classmethod получает класс, через который вызван: Dog.create() вернул Dog, а не Animal; так же dict.fromkeys и datetime.fromtimestamp, вызванные через наследников, вернули наследников. @staticmethod не получает ничего лишнего.

Забытый super().__init__() — объект создан, но атрибута родителя нет; AttributeError возникает при первом чтении, а не в конструкторе.

__x переименовывается в _Класс__x. Доступ по исходному имени — AttributeError, по переименованному — пожалуйста. Наследник с тем же __pin получает своё имя и родительское не задевает.

__eq__ без __hash__ делает объект нехешируемым: Point.__hash__ — None, ни элементом множества, ни ключом словаря он быть не может. У @dataclass хеш по умолчанию тоже None; с frozen=True он генерируется. Текст исключения в 3.14 изменился и называет роль объекта: cannot use 'Point' as a set element (…) и cannot use 'Point' as a dict key (…) вместо unhashable type: 'Point' на 3.11–3.13. Остальной вывод на четырёх версиях совпадает.

__slots__ убирает словарь экземпляра не всегда (раздел 7, добавлен 14.10.2026 по внешнему аудиту, замечание PY-OOP-01). OnlySlots со __slots__ = ("x",) и базой object — hasattr(o, '__dict__') даёт False, присвоить o.extra нельзя. Но Child(Base) со __slots__ = (), где Base — обычный класс, принимает c.extra = 1: vars(c) — {'extra': 1}. И "__dict__", перечисленный в slots, словарь возвращает. Текст AttributeError на 3.13 и 3.14 длиннее: … has no attribute 'extra' and no __dict__ for setting new attributes; на 3.11 и 3.12 — без хвоста.

Script

46 lines
"""Практика урока «Классы и ООП»: источник ответов двух задач.

Ответ задачи не сочиняется редакцией — он берётся из записи прогона этого
скрипта (`runs/practice.txt`), а сборка сверяет одно с другим
(`scripts/validate-practice.mjs`).

ЧТО ПРОВЕРЯЮТ ЗАДАЧИ. Первая — что изменяемый атрибут, объявленный в теле
класса, один на всех: две корзины пишут в один список. Вторая — что
`obj.method` при каждом обращении даёт НОВЫЙ объект связанного метода:
тысяча обращений, сложенных в список, — тысяча разных объектов.

Запускать: python3.11 / 3.12 / 3.13 / 3.14.
"""
import sys

print("PY", sys.version.split()[0])
print()
print("вывод задачи:")


class Cart:
    items = []

    def add(self, item):
        self.items.append(item)


a = Cart()
b = Cart()
a.add("apple")
b.add("pear")
print(len(a.items), len(b.items), a.items is b.items)

print()
print("оценка:")


class User:
    def greet(self):
        return "привет"


u = User()
methods = [u.greet for _ in range(1000)]
print("разных объектов связанного метода:", len({id(m) for m in methods}))