Python: задачки и вопросы
Подробное объяснение:
1️⃣ Python использует банковское округление (round half to even), а не привычное школьное «в большую сторону».
2️⃣ Когда число ровно посередине между двумя целыми, округляется к ближайшему чётному.
3️⃣ round(2.5) → 2 (ближайшее чётное).
4️⃣ round(3.5) → 4 (ближайшее чётное).
5️⃣ round(4.5) → 4 (ближайшее чётное).
Почему это важно:
В финансовых расчётах и при подсчёте статистики это может привести к расхождениям с ожидаемыми результатами. Стандарт IEEE 754 так округляет, чтобы уменьшить систематическую ошибку при большом количестве операций. Для точных десятичных вычислений (деньги, налоги) используйте Decimal из модуля decimal.
Почему это важно:
В финансовых расчётах и при подсчёте статистики это может привести к расхождениям с ожидаемыми результатами. Стандарт IEEE 754 так округляет, чтобы уменьшить систематическую ошибку при большом количестве операций. Для точных десятичных вычислений (деньги, налоги) используйте Decimal из модуля decimal.
Please open Telegram to view this post
VIEW IN TELEGRAM
✍8
Подробное объяснение:
1️⃣ Цикл for i in range(3) запускается: i принимает значения 0, 1, 2.
2️⃣ На итерации i = 0 условие if i == 1 ложно — цикл продолжается.
3️⃣ На итерации i = 1 условие истинно — выполняется break.
4️⃣ break немедленно прерывает цикл.
5️⃣ Блок else цикла проверяется только при нормальном завершении (без break).
6️⃣ Поскольку был break, блок else пропускается.
7️⃣ Выполняется print("done") → на экране одно слово: done.
Почему это важно:
Многие разработчики (даже опытные) думают, что else в цикле — это "если условие в цикле не сработало". На самом деле это "если цикл дошёл до конца". Путаница приводит к багам в поиске и валидации данных. for/else — полезный паттерн, но с контринтуитивным синтаксисом.
2️⃣ На итерации i = 0 условие if i == 1 ложно — цикл продолжается.
3️⃣ На итерации i = 1 условие истинно — выполняется break.
4️⃣ break немедленно прерывает цикл.
5️⃣ Блок else цикла проверяется только при нормальном завершении (без break).
6️⃣ Поскольку был break, блок else пропускается.
7️⃣ Выполняется print("done") → на экране одно слово: done.
Почему это важно:
Многие разработчики (даже опытные) думают, что else в цикле — это "если условие в цикле не сработало". На самом деле это "если цикл дошёл до конца". Путаница приводит к багам в поиске и валидации данных. for/else — полезный паттерн, но с контринтуитивным синтаксисом.
✍2
Подробное объяснение
1️⃣ Выражение [0] * 3 создаёт список [0, 0, 0] — один объект в памяти.
2️⃣ Выражение [[0, 0, 0]] * 3 создаёт три ссылки на этот же объект, а не три независимых списка.
3️⃣ Переменная m содержит [[0,0,0] ref, [0,0,0] ref, [0,0,0] ref]. Все ref → один объект.
4️⃣ m[0][0] = 1 обращается к первой ссылке, находит тот же список и меняет его первый элемент.
5️⃣ Поскольку все три строки ссылаются на один список, изменение видно во всех строках.
6️⃣ Вывод: [[1, 0, 0], [1, 0, 0], [1, 0, 0]].
Почему это важно:
Классический баг при создании игровых полей, таблиц и матриц. Чтобы сделать независимые строки, используйте list comprehension: [[0]*3 for _ in range(3)] — тогда каждая строка будет своим объектом.
2️⃣ Выражение [[0, 0, 0]] * 3 создаёт три ссылки на этот же объект, а не три независимых списка.
3️⃣ Переменная m содержит [[0,0,0] ref, [0,0,0] ref, [0,0,0] ref]. Все ref → один объект.
4️⃣ m[0][0] = 1 обращается к первой ссылке, находит тот же список и меняет его первый элемент.
5️⃣ Поскольку все три строки ссылаются на один список, изменение видно во всех строках.
6️⃣ Вывод: [[1, 0, 0], [1, 0, 0], [1, 0, 0]].
Почему это важно:
Классический баг при создании игровых полей, таблиц и матриц. Чтобы сделать независимые строки, используйте list comprehension: [[0]*3 for _ in range(3)] — тогда каждая строка будет своим объектом.
✍3
Подробное объяснение
1️⃣ a = Singleton(): _inst равен None → создаётся объект → _inst заполнен.
2️⃣ b = Sub(): Sub не переопределяет __new__, использует родительский.
3️⃣ Внутри __new__ проверяется cls._inst. Python ищет _inst в MRO: Sub.__dict__ → Singleton.__dict__.
4️⃣ Находит _inst в Singleton (уже не None) → возвращает тот же объект.
5️⃣ a is b → True. Тип объекта — Singleton (создан через super().__new__(cls) в первый раз).
6️⃣ Вывод: True Singleton Singleton.
Почему это важно?
Наследование ломает изоляцию Singleton. Каждый подкласс должен иметь свой _inst, иначе все клонируют объект родителя.
2️⃣ b = Sub(): Sub не переопределяет __new__, использует родительский.
3️⃣ Внутри __new__ проверяется cls._inst. Python ищет _inst в MRO: Sub.__dict__ → Singleton.__dict__.
4️⃣ Находит _inst в Singleton (уже не None) → возвращает тот же объект.
5️⃣ a is b → True. Тип объекта — Singleton (создан через super().__new__(cls) в первый раз).
6️⃣ Вывод: True Singleton Singleton.
Почему это важно?
Наследование ломает изоляцию Singleton. Каждый подкласс должен иметь свой _inst, иначе все клонируют объект родителя.
✍2
Подробное объяснение
1️⃣ hash(1) == hash(1.0) == hash(True) == 1.
2️⃣ 1 == 1.0 == True → все дубликаты в set → len → 1.
3️⃣ a is b → 1 is 1.0 → разные типы, разные объекты → False.
4️⃣ a is c → 1 is True → False.
5️⃣ Вывод: 1 False False.
Почему это важно
is для чисел — антипаттерн. Работает случайно для малых чисел, ломается в production. Всегда используйте ==.
1️⃣ hash(1) == hash(1.0) == hash(True) == 1.
2️⃣ 1 == 1.0 == True → все дубликаты в set → len → 1.
3️⃣ a is b → 1 is 1.0 → разные типы, разные объекты → False.
4️⃣ a is c → 1 is True → False.
5️⃣ Вывод: 1 False False.
Почему это важно
is для чисел — антипаттерн. Работает случайно для малых чисел, ломается в production. Всегда используйте ==.
✍3
Подробное объяснение
1️⃣@dataclass создаёт __init__(self, tags=[]) — пустой список вычисляется один раз при определении класса.
2️⃣ u1 = User() и u2 = User() получают ссылку на один и тот же список.
3️⃣ u1.tags.append("admin") мутирует этот список.
4️⃣ u2.tags видит ту же мутацию — выводит ['admin'].
5️⃣ Правильно: tags: list = field(default_factory=list).
Почему это важно
Тот же баг, что и в обычной функции, но скрыт за декоратором. default_factory создаёт новый объект для каждого экземпляра.
1️⃣
2️⃣ u1 = User() и u2 = User() получают ссылку на один и тот же список.
3️⃣ u1.tags.append("admin") мутирует этот список.
4️⃣ u2.tags видит ту же мутацию — выводит ['admin'].
5️⃣ Правильно: tags: list = field(default_factory=list).
Почему это важно
Тот же баг, что и в обычной функции, но скрыт за декоратором. default_factory создаёт новый объект для каждого экземпляра.
✍3
Подробное объяснение
1️⃣ case Animal() проверяет isinstance(d, Animal).
2️⃣ Dog — подкласс Animal, поэтому проверка возвращает True.
3️⃣ Выполняется print("A"), блок case Dog() пропускается.
4️⃣ Match не падает сквозь — только первый подходящий case.
5️⃣ print("done") выполняется в любом случае.
Почему это важно
Более общий паттерн сверху перехватывает частные. Чтобы Dog сработал — поменяйте порядок: Dog выше Animal.
2️⃣ Dog — подкласс Animal, поэтому проверка возвращает True.
3️⃣ Выполняется print("A"), блок case Dog() пропускается.
4️⃣ Match не падает сквозь — только первый подходящий case.
5️⃣ print("done") выполняется в любом случае.
Почему это важно
Более общий паттерн сверху перехватывает частные. Чтобы Dog сработал — поменяйте порядок: Dog выше Animal.
✍3
Подробное объяснение
1️⃣ next(g) → yield 1 → возвращает 1.
2️⃣ next(g) → yield 2 → возвращает 2, останавливается на x = yield 2.
3️⃣ g.send(10) → отправляет 10 в левую часть yield → x = 10.
4️⃣ Генератор продолжается до yield x → возвращает 10.
5️⃣ Итог: 1 2 10.
Почему это важно
send() — основа корутин в asyncio. Понимание двунаправленной коммуникации с генератором критично для асинхронного кода.
1️⃣ next(g) → yield 1 → возвращает 1.
2️⃣ next(g) → yield 2 → возвращает 2, останавливается на x = yield 2.
3️⃣ g.send(10) → отправляет 10 в левую часть yield → x = 10.
4️⃣ Генератор продолжается до yield x → возвращает 10.
5️⃣ Итог: 1 2 10.
Почему это важно
send() — основа корутин в asyncio. Понимание двунаправленной коммуникации с генератором критично для асинхронного кода.
✍1
Подробное объяснение
1️⃣ Python 3 компилирует super() без аргументов в super(__class__, <first_arg>).
2️⃣ Компилятор создаёт __classcell__ — ячейку замыкания с классом B.
3️⃣ Внутри inner() Python ищет __class__ в замыканиях — находит B.
4️⃣ self берётся из параметра f(self) через цепочку замыканий.
5️⃣ super().f() → A.f(self) → возвращает "A".
Почему это важно
В Python 2 super(B, self) было обязательно. В Python 3 zero-argument super() работает через магию компилятора — даже во вложенных функциях.
1️⃣ Python 3 компилирует super() без аргументов в super(__class__, <first_arg>).
2️⃣ Компилятор создаёт __classcell__ — ячейку замыкания с классом B.
3️⃣ Внутри inner() Python ищет __class__ в замыканиях — находит B.
4️⃣ self берётся из параметра f(self) через цепочку замыканий.
5️⃣ super().f() → A.f(self) → возвращает "A".
Почему это важно
В Python 2 super(B, self) было обязательно. В Python 3 zero-argument super() работает через магию компилятора — даже во вложенных функциях.
✍2
Выражение a + [3] порождает новый список, который затем присваивается переменной a, не затрагивая исходный объект, на который ссылается b. Если бы использовалось a += [3], вызвался бы метод __iadd__, изменяющий список на месте, и тогда b тоже бы изменился. Таким образом, оператор присваивания с + не модифицирует исходный список.
✍3