На этом шаге мы рассмотрим, что еще можно делать с этими параметрами.
Существует возможность передавать необязательные или именованные параметры из одной функции в другую. Это можно делать при помощи операторов распаковки аргументов * и ** во время вызова функции, в которую вы хотите переадресовать аргументы.
Это также дает вам возможность модифицировать аргументы перед тем, как вы передадите их дальше. Вот пример:
>>> 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 теперь имеет довольно бесполезную сигнатуру, - мы не узнаем, какие аргументы он ожидает, не заглянув в родительский класс.
Как правило, вы не будете использовать этот прием со своими собственными иерархиями классов. Более вероятный сценарий будет такой, что вы захотите изменить или переопределить поведение в некотором внешнем классе, которым не управляете.
Но это всегда опасная территория, поэтому лучше соблюдать осторожность.
Еще один сценарий, где этот прием будет потенциально полезен, - написание оберточных функций, таких как декораторы. Там вы также захотите принимать произвольные аргументы, которые будут переправляться в обернутую функцию.
И если мы можем сделать это без необходимости копипастить сигнатуру оригинальной функции, то, возможно, сопровождение станет удобнее:
>>> 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('Привет', 'Боб') <function greet at 0x000001AD88A7DF70> ('Привет', 'Боб') {} Привет, Боб!
Такого рода приемами иногда трудно уравновесить идею сделать ваш программный код достаточно четким и при этом остаться в рамках принципа DRY.
DRY (от англ. Don't Repeat Yourself, то есть "не повторяйся") - это принцип разработки программного обеспечения, нацеленный
на снижение повторения информации различного рода. См.
https://ru.wikipedia.org/wiki/Don't_repeat_yourself.
Это всегда будет нелегким выбором.
На следующем шаге мы подведем некоторые итоги.