Настройка дефолтных значений аргументов

Раздел: Основы Python -> Функции/Аргументы

Параметры функций со значениями по умолчанию

Значение по умолчанию для аргумента функции задается в определении после знака равенства. Этот подход работает для неизменяемых типов (числа, строки, кортежи, None) и позволяет вызывать функцию без указания соответствующего аргумента.

def greet(name='Guest'):
    print(f'Hello, {name}')

Python значение по умолчанию (значение по умолчанию в python)

greet()            # Hello, Guest
greet('Alice')    # Hello, Alice

В данном примере параметр name получает строку 'Guest' при отсутствии аргумента. Такое значение вычисляется один раз в момент определения функции и сохраняется в атрибуте __defaults__.

Как обработать изменяемый объект в качестве значения по умолчанию?

Если значением по умолчанию выступает изменяемый объект (список, словарь, множество), он создаётся один раз и разделяется между всеми вызовами функции. Это приводит к накоплению данных между вызовами, что часто становится неожиданным поведением.

def add_item(item, lst=[]):
    lst.append(item)
    return lst
print(add_item(1))   # [1]
print(add_item(2))   # [1, 2]  (ожидалось [2])

Такая ошибка возникает из-за того, что список lst создаётся при определении функции и используется во всех вызовах.

Рекомендуемый приём - использовать None как sentinel и создавать новый изменяемый объект внутри функции.

def add_item(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst
print(add_item(1))   # [1]
print(add_item(2))   # [2]

Теперь каждый вызов без аргумента lst создаёт новый список. Аналогом может быть использование фабричной функции, например lambda, но она недопустима непосредственно в сигнатуре - применяется в декораторах или dataclasses.field.

Как отличить отсутствие аргумента от явной передачи None?

Иногда требуется различать две ситуации: аргумент не передан, либо передан со значением None. Для этого применяют специальный объект-синглтон:

_MISSING = object()

def process(data=_MISSING):
    if data is _MISSING:
        print('Аргумент не передан')
    else:
        print(f'Получено: {data}')
process()          # Аргумент не передан
process(None)      # Получено: None
process(42)        # Получено: 42

Этот подход позволяет обрабатывать None как корректное значение, не путая его с пропущенным аргументом.

Как задать значение по умолчанию, вычисляемое при каждом вызове?

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

import datetime

def log_event(message, timestamp=None):
    if timestamp is None:
        timestamp = datetime.datetime.now()
    print(f'[{timestamp}] {message}')
log_event('start')   # [2025-03-19 12:34:56] start

Для более элегантного синтаксиса (например, в классах данных) применяется dataclasses.field с default_factory.

from dataclasses import dataclass, field
import datetime

@dataclass
class Event:
    message: str
    timestamp: datetime.datetime = field(default_factory=datetime.datetime.now)

При создании экземпляра без timestamp вызывается datetime.now() для каждого объекта отдельно.

Как зафиксировать часть аргументов с помощью functools.partial?

Библиотека functools предоставляет partial для создания новой функции с предустановленными значениями некоторых аргументов.

from functools import partial

def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5))   # 25
print(cube(2))     # 8

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

Как использовать значения по умолчанию в декораторах?

Декораторы могут добавлять аргументы со значениями по умолчанию к оборачиваемой функции. Например, таймер с опциональным единицами измерения:

import time
from functools import wraps

def timer(unit='seconds'):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            elapsed = time.time() - start
            if unit == 'milliseconds':
                elapsed *= 1000
            print(f'{func.__name__} заняла {elapsed:.4f} {unit}')
            return result
        return wrapper
    return decorator

Параметр unit имеет значение по умолчанию 'seconds', которое можно переопределить при применении декоратора.

Расширенные примеры со значениями по умолчанию

Вложенные изменяемые объекты и deepcopy

Если значение по умолчанию - сложная структура (список списков), копирование через None может дать неверный результат, если требуется полная независимость. Используйте copy.deepcopy внутри тела.

Пример
from copy import deepcopy

def add_matrix(row, matrix=None):
    if matrix is None:
        matrix = [[0,0],[0,0]]
    else:
        matrix = deepcopy(matrix)
    matrix.append(row)
    return matrix
m1 = add_matrix([1,2])
m2 = add_matrix([3,4])
print(m1)  # [[0,0],[0,0],[1,2]]
print(m2)  # [[0,0],[0,0],[3,4]]

Без deepcopy второй вызов модифицировал бы общие внутренние списки.

Значения по умолчанию и аннотации типов

Совместное использование аннотаций и значений по умолчанию улучшает читаемость и помогает статическим анализаторам.

Пример
def fetch_data(url: str, timeout: int = 30, headers: dict | None = None) -> dict:
    if headers is None:
        headers = {}
    # ... логика запроса

Здесь dict | None (Python 3.10+) указывает, что headers может быть словарём или None, а значение по умолчанию None обрабатывается внутри.

Использование inspect.signature для анализа значений по умолчанию

Модуль inspect позволяет программно получать значения по умолчанию и их параметры.

Пример
import inspect

def example(a, b=10, c=None):
    pass

sig = inspect.signature(example)
for name, param in sig.parameters.items():
    if param.default is not inspect.Parameter.empty:
        print(f'{name}: default = {param.default!r}')
b: default = 10
c: default = None

Это полезно для метапрограммирования, создания документации или валидации.

Задание значений по умолчанию через __kwdefaults__ для функций с **kwargs

Если функция использует **kwargs, значения по умолчанию для отдельных ключей можно задать через словарь внутри функции.

Пример
def render_template(path, **kwargs):
    defaults = {'lang': 'ru', 'cache': True}
    config = {**defaults, **kwargs}
    # ... использование config
render_template('index.html')
render_template('page.html', lang='en', cache=False)

Такой способ гибок, но не даёт подсказок IDE о доступных параметрах.

Классы с настраиваемыми значениями по умолчанию через __init_subclass__

При наследовании можно изменить значения по умолчанию для методов базового класса.

Пример
class Base:
    def process(self, limit=100):
        print(f'Processing with limit {limit}')

class Child(Base):
    def process(self, limit=500):
        super().process(limit=limit)
c = Child()
c.process()   # Processing with limit 500

Здесь дочерний класс переопределяет значение по умолчанию для аргумента limit.

Значения по умолчанию в лямбда-выражениях

Лямбда-функции также могут иметь параметры с умолчаниями.

Пример
add = lambda x, delta=1: x + delta
print(add(5))     # 6
print(add(5, 3))  # 8

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

Значение по умолчанию в Python - comments

En
Python значение по умолчанию (python)