<MyRusakov.ru />

Профессиональная Web-разработка. Дизайн, код и автоматизация

Профессиональная Web-разработка. Дизайн, код и автоматизация

Это очень подробный курс из разряда "всё включено". Разбираются следующие темы: HTML, CSS, SCSS, JavaScript, PHP, SQL, Laravel, Nginx, PostCSS, npm, Vite, Vitest, Composer, PHPUnit, Prettier, Stylelint, ESLint, Pint, Larastan, Git, Agile, Scrum, Docker, Supervisord, Figma, Stitch AI, Confluence, Jira.

Рассчитан и на новичков, и на тех, кто уже знаком с основами, но хочет освоить полный цикл разработки.

Помимо самой теории, Вы увидите пример создания Web-проекта на 20 000 строк кода: от идеи и документации на Confluence через планирование на Jira, fullstack-разработку до деплоя на VPS.

Помимо уроков, курс содержит упражнения для закрепления знаний и финальное тестирование. А ещё Вы получите 5 полноценных Бонусных курсов: «GitLab под ключ», «Вёрстка сайта с нуля 2.0», «JavaScript с Нуля до Гуру 2.0», «PHP и MySQL с Нуля до Гуру 3.0» и «Laravel от А до Я».

Подробнее
Подписка

Подпишитесь на мой канал на YouTube, где я регулярно публикую новые видео.

YouTube Подписаться

Подписавшись по E-mail, Вы будете получать уведомления о новых статьях.

Подписка Подписаться

Добавляйтесь ко мне в друзья ВКонтакте! Отзывы о сайте и обо мне оставляйте в моей группе.

Мой аккаунт Мой аккаунт Моя группа
Опрос

Какая тема Вас интересует больше?

Изменяемые аргументы по умолчанию в Python: почему это ошибка и как делать правильно

Изменяемые аргументы по умолчанию в Python: почему это ошибка и как делать правильно

Запрос «изменяемые аргументы по умолчанию в Python» регулярно появляется в поиске — и не зря. Это одна из самых коварных ошибок: вы пишете безобидную функцию со списком по умолчанию, а потом внезапно данные «накапливаются» между вызовами. Разберёмся на примерах и закрепим правильные паттерны.

Почему это происходит: значения по умолчанию вычисляются один раз

В Python значения по умолчанию вычисляются в момент определения функции, а не при каждом вызове. Поэтому если вы используете изменяемый объект (list, dict, set), он будет общий для всех последующих вызовов без явной передачи параметра.

def append_item(item, bucket=[]):
    bucket.append(item)
    return bucket

print(append_item(1))   # [1]
print(append_item(2))   # [1, 2]  — <!> неожиданно!
print(append_item(3, []))  # [3] — если передать новый список, всё ок

# Покажем, что дефолт «живёт» в функции
print(append_item.__defaults__)  # (['1', '2'],) — здесь хранится тот самый список

Аналогичный эффект проявляется и в конструкторах классов:

class User:
    def __init__(self, name, tags=[]):
        self.name = name
        self.tags = tags

u1 = User("Ann")
u2 = User("Bob")
u1.tags.append("admin")
print(u2.tags)  # ['admin'] — второй пользователь «унаследовал» тег первого

Правильный паттерн: используем None как «сентинел»

Стоимость проверки None ничтожна, а читаемость и надёжность — на высоте. Это канонический способ.

def append_item(item, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(item)
    return bucket

print(append_item(1))   # [1]
print(append_item(2))   # [2] — новый список при каждом вызове
print(append_item(3, [10]))  # [10, 3] — используем свой список

То же для словарей и множеств:

def add_flag(key, flags=None):
    if flags is None:
        flags = {}
    flags[key] = True
    return flags

print(add_flag("x"))      # {'x': True}
print(add_flag("y"))      # {'y': True}

Dataclasses: используйте default_factory

В dataclass нельзя ставить изменяемые значения напрямую: используйте field(default_factory=...). Это создаёт новый объект при каждом создании экземпляра.

from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    tags: list[str] = field(default_factory=list)

u1 = User("Ann")
u2 = User("Bob")
u1.tags.append("admin")
print(u2.tags)  # [] — теги независимы

Аннотации типов и None

Если используете аннотации, помните про None в типах. Для Python 3.10+ подойдёт оператор |.

def process(items: list[int] | None = None) -> list[int]:
    if items is None:
        return []
    return [x * 2 for x in items]

В более ранних версиях:

from typing import Optional, List

def process(items: Optional[List[int]] = None) -> List[int]:
    return [] if items is None else [x * 2 for x in items]

Не только списки: вызовы в дефолтах тоже опасны

Любая функция в дефолте выполнится при определении, а не при вызове. Пример с датой:

from datetime import datetime

def log(ts=datetime.now()):  # <!> ВСЕ вызовы получат один и тот же момент времени
    return ts.isoformat()

Правильно так:

from datetime import datetime

def log(ts=None):
    if ts is None:
        ts = datetime.now()
    return ts.isoformat()

Когда «шэрить» дефолт можно осознанно

Иногда вам действительно нужен общий «кеш» между вызовами. Но делайте это явно — через замыкание, глобальную переменную или декоратор кеширования, чтобы код было легче читать и тестировать.

# Ясный и управляемый стейт через замыкание

def make_counter(start=0):
    value = start
    def inc():
        nonlocal value
        value += 1
        return value
    return inc

c = make_counter()
print(c())  # 1
print(c())  # 2

Если нужен мемо-кеш по аргументам — лучше используйте functools.lru_cache.

from functools import lru_cache

@lru_cache(maxsize=1024)
def fib(n: int) -> int:
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

Как ловить проблему заранее

  • Линтеры: Pylint (W0102: dangerous default value), flake8-bugbear (B006 для изменяемых дефолтов, B008 для вызовов в дефолтах).
  • Код-ревью: проверяйте сигнатуры на list/dict/set/{} или вызовы функций.
  • Тесты: делайте два последовательных вызова без передачи аргумента и сравнивайте результаты.
def test_append_item_is_fresh():
    assert append_item(1) == [1]
    assert append_item(2) == [2]

Чеклист перед коммитом

  • Нет ли в параметрах list/dict/set как значений по умолчанию?
  • Нет ли вызовов функций в дефолтах (datetime.now(), uuid4(), Path.cwd())?
  • В dataclass для изменяемых полей стоит field(default_factory=...)?
  • С None-сентинелем покрыты все ветки тестами?

Итоги

Главное правило: не используйте изменяемые аргументы по умолчанию в Python. Ставьте None и инициализируйте внутри функции, а в dataclass применяйте default_factory. Исключения бывают, но должны быть намеренными и явно оформленными. Следуя этим простым принципам, вы избежите множества неприятных и трудноуловимых багов.

Хотите системно прокачать навыки и научиться писать надёжный продакшен-код? Рекомендую пройти практический курс «Python с Нуля до Гуру» — много реальных задач, разбор типичных ошибок и менторская поддержка.

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (https://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: https://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: https://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

  1. Кнопка:

    Она выглядит вот так: Как создать свой сайт

  2. Текстовая ссылка:

    Она выглядит вот так: Как создать свой сайт

  3. BB-код ссылки для форумов (например, можете поставить её в подписи):

Комментарии (0):

Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.