Классические ловушки: аргументы по умолчанию, позднее связывание и другие

⏱ 35 мин · 🧩 заданий: 6

Эти ошибки делают все — поэтому о них так любят спрашивать. Для каждой: запустите пример, посмотрите 👣 Пошагово и запомните исправление.

1. Изменяемое значение по умолчанию

def append_to(item, target=[]):
    target.append(item)
    return target

print(append_to(1))
print(append_to(2))       # [1, 2] — список «помнит» прошлый вызов
print(append_to.__defaults__)

Почему: значения по умолчанию вычисляются один раз — при выполнении def, и хранятся в func.__defaults__. Все вызовы получают один и тот же список.

def append_to(item, target=None):
    if target is None:
        target = []           # новый список на каждый вызов
    target.append(item)
    return target

print(append_to(1), append_to(2))

То же касается {}, set(), объектов и даже datetime.now(): def log(msg, ts=datetime.now()) запомнит время определения функции.

2. Позднее связывание в замыканиях

callbacks = [lambda: i for i in range(3)]
print([cb() for cb in callbacks])              # [2, 2, 2]

callbacks = [lambda i=i: i for i in range(3)]
print([cb() for cb in callbacks])              # [0, 1, 2]

Почему: замыкание хранит ссылку на переменную, значение читается при вызове. Исправление: аргумент по умолчанию или functools.partial.

3. Умножение списка со вложенными списками

board = [[" "] * 3] * 3
board[0][0] = "X"
print(board)                                    # X во всех строках

board = [[" "] * 3 for _ in range(3)]
board[0][0] = "X"
print(board)

Почему: * копирует ссылки — три ссылки на один внутренний список.

4. Изменение коллекции во время перебора

nums = [1, 2, 3, 4, 5, 6]
for n in nums:
    if n % 2 == 0:
        nums.remove(n)
print(nums)                         # кажется, работает…

nums = [2, 4, 6, 8]
for n in nums:
    if n % 2 == 0:
        nums.remove(n)
print(nums)                         # [4, 8] — элементы пропущены!

nums = [2, 4, 6, 8]
nums = [n for n in nums if n % 2]   # правильно
print(nums)

Для словаря и множества изменение размера во время перебора — сразу RuntimeError.

5. Изменяемый атрибут класса

class Cart:
    items = []                  # общий для ВСЕХ экземпляров!
    def add(self, x):
        self.items.append(x)

a, b = Cart(), Cart()
a.add("яблоко")
print(b.items)                  # ['яблоко']

class Cart:
    def __init__(self):
        self.items = []         # у каждого экземпляра свой
    def add(self, x):
        self.items.append(x)

a, b = Cart(), Cart()
a.add("яблоко")
print(b.items)

6. is вместо ==

a = 256; b = 256
print(a is b)                   # True — кеш малых чисел
x = int("257"); y = int("257")
print(x is y, x == y)           # False True
s1 = "hello world!"; s2 = " ".join(["hello", "world!"])
print(s1 is s2, s1 == s2)       # False True

is — только для None, True, False.

7. or для значений по умолчанию

def connect(timeout=None):
    timeout = timeout or 30
    return timeout

print(connect(0))               # 30, хотя просили 0!

def connect(timeout=None):
    return 30 if timeout is None else timeout

print(connect(0))

8. Сравнение float

import math
print(0.1 * 3 == 0.3, math.isclose(0.1 * 3, 0.3))
print(sum([0.1] * 10) == 1.0)

9. Одноразовые итераторы

squares = map(lambda x: x * x, [1, 2, 3])
print(list(squares), list(squares))     # второй раз — пусто

data = (x for x in range(3))
print(2 in data, list(data))            # проверка «съела» элементы

10. Затенение встроенных имён и модулей

list = [1, 2, 3]
sum = 10
print(list(range(3)))       # TypeError: 'list' object is not callable

Такая же беда — файл random.py или json.py в своём проекте: import random импортирует ваш файл вместо стандартного модуля.

11. «Голый» except и проглатывание ошибок

def parse(value):
    try:
        return int(valeu)       # опечатка → NameError
    except:                     # перехватывает и её тоже!
        return 0

print(parse("42"))              # 0 — ошибка в коде незаметна

def parse(value):
    try:
        return int(value)
    except ValueError:          # только ожидаемое исключение
        return 0

print(parse("42"), parse("x"))

12. Распаковка и += с кортежем

t = ([1],)
try:
    t[0] += [2]
except TypeError as e:
    print(e)
print(t)            # ([1, 2],) — список изменился, хотя была ошибка

13. Целочисленное деление и округление

print(-7 // 2, int(-7 / 2), round(2.5), round(3.5))

// округляет вниз, int() — к нулю, round() — к чётному.

14. Порядок вычисления значений по умолчанию и время

import time

def stamp(ts=time.time()):
    return ts

a = stamp()
time.sleep(0.01)
print(stamp() == a)        # True — время зафиксировано при def

Как отвечать на собеседовании

Не просто называйте результат, а объясняйте механизм: «значения по умолчанию вычисляются один раз при определении функции», «замыкание захватывает переменную, а не значение», «умножение списка копирует ссылки». Затем — исправление.

Итог

Ловушка Исправление
def f(x=[]) x=None + создать внутри
lambda: i в цикле lambda i=i: i
[[0] * n] * m [[0] * n for _ in range(m)]
remove во время for новый список / перебор копии
изменяемый атрибут класса создавать в __init__
is для значений ==
x or default при допустимом 0 default if x is None else x
a == b для float math.isclose
повторный проход генератора list(...) или создать заново
except: конкретное исключение

Практика решено 0 из 6

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Один вариант ответа

Что выведет код?

def add(item, items=[]):
    items.append(item)
    return items

print(add(1))
print(add(2))
Задание 2 Программа на Python

Исправьте функцию add_tag, чтобы каждый вызов без аргумента tags работал с новым списком.

Какие тесты проверяют код (3)
assert add_tag("a") == ["a"]
assert add_tag("b") == ["b"], "Второй вызов не должен видеть тег из первого"
my = ["x"]; assert add_tag("y", my) == ["x", "y"] and my == ["x", "y"]
Задание 3 Программа на Python

Исправьте класс Team, чтобы у каждой команды был свой список участников.

Какие тесты проверяют код (2)
a = Team("A"); b = Team("B"); a.add("Аня"); assert b.members == [], "Участники команды A попали в команду B"
c = Team("C"); c.add("Боря"); assert c.members == ["Боря"]
Задание 4 Один вариант ответа

Что выведет код?

nums = (x * x for x in range(3))
print(sum(nums))
print(sum(nums))
Задание 5 Один вариант ответа

Чем плох «голый» except: (без указания исключения)?

Задание 6 Заполнить таблицу

Есть ли в строке ловушка?

ЛовушкаВсё нормально
def f(x, cache={}): ... (кеш специально общий)
if x is 1000:
timeout = value or 30 (0 — допустимое значение)
if 0.1 + 0.2 == 0.3:
grid = [[0] * 3 for _ in range(3)]
for k in list(d): del d[k]
Отметьте ячейку — «+», пустая — «−».

Войдите, чтобы отмечать пройденные уроки.