TypeError:
"foo() missing 1 required positional arg: 'required'"
>>> foo('привет')
привет
>>> foo('привет', 1, 2, 3)
привет
(1, 2, 3)
>>> foo('привет', 1, 2, 3, key1='значение', key2=999)
привет
(1, 2, 3)
{'key1': 'значение', 'key2': 999}
Сразу хочу прояснить. Название параметров
args
и
kwargs
принято по
договоренности, как согласованное правило именования. Приведенный
выше пример будет работать точно так же, если вы назовете их
*parms
и
**argv
. Фактическим синтаксисом является, соответственно, просто
звездочка (
*
) или двойная звездочка (
**
).
Однако чтобы избежать недоразумений, я рекомендую придерживаться
общеринятого согласованного правила именования. (И иметь возмож-
ность время от времени рычать «арррг!» и «кваррррг!».)
Переадресация необязательных
или именованных аргументов
Существует возможность передавать необязательные или именованные
параметры из одной функции в другую. Это можно делать при помощи
операторов распаковки аргументов
*
и
**
во время вызова функции, в ко-
торую вы хотите переадресовать аргументы
1
.
1
См. раздел 3.5 «Распаковка аргументов функции».
94 Глава 3 • Эффективные функции
Это также дает вам возможность модифицировать аргументы перед тем,
как вы передадите их дальше. Вот пример:
def foo(x, *args, **kwargs):
kwargs['имя'] = 'Алиса'
new_args = args + ('дополнительный', )
bar(x, *new_args, **kwargs)
Данный прием может быть полезен для создания производных классов
и написания оберточных функций. Например, он может применяться
для расширения поведения родительского класса без необходимости
повторять полную сигнатуру его конструктора в дочернем классе. Это
может быть довольно удобно, если вы работаете с API, который может
измениться за пределами вашего контроля:
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
class AlwaysBlueCar(Car):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.color = 'синий'
>>> AlwaysBlueCar('зеленый', 48392).color
'синий'
Конструктор класса
AlwaysBlueCar
просто передает все аргументы в свой
родительский класс и затем переопределяет внутренний атрибут. Это оз-
начает, что если конструктор родительского класса изменится, то велика
вероятность того, что
AlwaysBlueCar
будет по-прежнему функционировать
как было задумано.
Оборотной стороной здесь является то, что конструктор
AlwaysBlueCar
теперь имеет довольно бесполезную сигнатуру, — мы не узнаем, какие
аргументы он ожидает, не заглянув в родительский класс.
Как правило, вы не будете использовать этот прием со своими собствен-
ными иерархиями классов. Более вероятный сценарий будет такой, что вы
захотите изменить или переопределить поведение в некотором внешнем
классе, которым не управляете.
3 .4 . Веселье с *args и **kwargs 95
Но это всегда опасная территория, поэтому лучше соблюдать осторож-
ность (иначе вскоре у вас, возможно, появится еще одна причина вос-
кликнуть «аррррг!»).
Еще один сценарий, где этот прием будет потенциально полезен, — на-
писание оберточных функций, таких как декораторы. Там вы также захо-
тите принимать произвольные аргументы, которые будут переправляться
в обернутую функцию.
И если мы можем сделать это без необходимости копипастить сигнатуру
оригинальной функции, то, возможно, сопровождение станет удобнее:
def trace(f):
@functools.wraps(f)
def decorated_function(*args, **kwargs):
print(f, args, kwargs)
result = f(*args, **kwargs)
print(result)
return decorated_function
@trace
def greet(greeting, name):
return '{}, {}!'.format(greeting, name)
>>> greet('Привет', 'Боб')
('Привет', 'Боб') {}
Do'stlaringiz bilan baham: |