Шаг 52.
Python: тонкости программирования. Эффективные функции. Параметры *args и **kwargs. Переадресация необязательных или именованных аргументов

    На этом шаге мы рассмотрим, что еще можно делать с этими параметрами.

    Существует возможность передавать необязательные или именованные параметры из одной функции в другую. Это можно делать при помощи операторов распаковки аргументов * и ** во время вызова функции, в которую вы хотите переадресовать аргументы.

    Это также дает вам возможность модифицировать аргументы перед тем, как вы передадите их дальше. Вот пример:

>>> 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.

    Это всегда будет нелегким выбором.

    На следующем шаге мы подведем некоторые итоги.




Предыдущий шаг Содержание