Python: *args, **kwargs и декораторы для начинающих
![](/static/storage/175269366159785207888996487635525899732.jpeg)
Python имеет много конструкций, которые достаточно просты для изучения и использования в нашем коде. Тогда как есть некоторые конструкции, которые всегда смущают нас, когда мы сталкиваемся с ними в нашем коде.
Есть некоторые, которые даже опытные программисты не могут понять. *args
, **kwargs
и декораторы - это некоторые конструкции, которые попадают в эту категорию.
Я думаю, что многие из моих друзей по Data Scientists сталкивались с ними тоже.
Большинство функций seaborn так или иначе используют *args и **kwargs.
![](/static/storage/168930417613555315415866316641195716032.png)
Или как насчет декораторов?
Каждый раз, когда вы видите предупреждение о том, что какая-то функция будет устаревшей в следующей версии. Для этого пакет sklearn
использует декораторы. Вы можете увидеть в исходном коде конструкцию @deprecated
. Это функция декоратора.
![](/static/storage/189955936019165948662277528868062134417.png)
В этой серии статей под названием « Python Shorts » я объясню некоторые простые конструкции, предоставляемые Python, некоторые важные советы и некоторые примеры использования, которые я регулярно придумываю в своей работе.
Этот пост посвящен объяснению некоторых сложных концепций в доступной для понимания форме.
Что такое *args?
Проще говоря, вы можете использовать, *args
чтобы дать произвольное количество входных аргументов для вашей функции.
Простой пример:
Допустим, мы должны создать функцию, которая добавляет два числа. Мы можем сделать это легко в Python следующим образом.
def adder(x, y): return x + y
Что если мы хотим создать функцию для добавления трех переменных?
def adder(x, y, z): return x + y + z
Что если мы хотим, чтобы одна и та же функция добавила неизвестное количество переменных? Обратите внимание, что мы можем использовать *args
или *argv
или *anyOtherName
чтобы сделать это.
def adder(*args): result = 0 for arg in args: result += arg return result
Конструкция *args
, означает что он принимает все переданные вами аргументы и предоставляет список аргументов переменной длины для функции, которую вы можете использовать по своему усмотрению.
Теперь вы можете использовать ту же функцию следующим образом:
adder(1, 2) adder(1, 2, 3) adder(1, 2, 5, 7, 8, 9, 100)
Что такое **kwargs?
![](/static/storage/53166506690810101239698996996261709667.png)
Проще говоря, вы можете использовать, **kwargs
чтобы дать произвольное количество ключевых слов для вашей функции и получить к ним доступ, используя словарь.
Простой пример:
Допустим, вы хотите создать функцию печати, которая может принимать имя и возраст в качестве входных данных и печатать их.
def myprint(name, age): print(f'{name} is {age} years old')
Давайте теперь скажем, что вы хотите, чтобы одна и та же функция принимала два имени и два возраста.
def myprint(name1,age1,name2,age2): print(f'{name1} is {age1} years old') print(f'{name2} is {age2} years old')
Вы правильно догадались, что мой следующий вопрос: что если я не знаю, сколько аргументов мне понадобится?
Могу ли я использовать *args
? Не думаю, так как имя и возрастной порядок важен.
Давайте используем **kwargs
.
def myprint(**kwargs): for k, v in kwargs.items(): print(f'{k} is {v} years old')
Вы можете вызвать эту функцию используя:
myprint(Sansa=20, Tyrion=40, Arya=17) # Output: # ----------------------------------- # Sansa is 20 years old # Tyrion is 40 years old # Arya is 17 years old
Помните, что мы никогда не определяли Sansa, Arya или Tyrion в качестве аргументов наших методов.
Это довольно мощная концепция. И многие программисты используют это довольно умно, когда пишут библиотеки-обертки.
Например, функция seaborn.scatterplot
оборачивает функцию plt.scatter
из Matplotlib. По сути, используя *args
и **kwargs
мы можем предоставить все аргументы, которые могут принять plt.scatter
и seaborn.Scatterplot
.
Это может сэкономить много усилий при написании кода, а также делает код на будущее. Если в какой-либо момент в будущем plt.scatter
будет принимать какие-либо новые аргументы, функция seaborn.Scatterplot
все равно будет работать.
Что такое декораторы?
![](/static/storage/61882478311302045413194368407455566669.jpeg)
Проще говоря: декораторы - это функции, которые обертывают другую функцию, изменяя ее поведение.
Простой пример:
Допустим, мы хотим добавить пользовательские функции к некоторым из наших функций. Функциональность заключается в том, что всякий раз, когда вызывается функция, печатается «function name
begins», а всякий раз, когда функция заканчивается, печатаются « function name
ends» и время, затраченное функцией.
Давайте предположим, что наша функция:
def somefunc(a, b): output = a + b return output
Мы можем добавить несколько строк с print для всех наших функций, чтобы достичь этого.
import time def somefunc(a, b): print("somefunc begins") start_time = time.time() output = a + b print("somefunc ends in ",time.time()-start_time, "secs") return output out = somefunc(4,5) # OUTPUT: # ------------------------------------------- # somefunc begins # somefunc ends in 9.5367431640625e-07 secs
Вот где декораторы нам пригодятся. Мы можем использовать декораторы, чтобы обернуть любую функцию.
from functools import wraps def timer(func): @wraps(func) def wrapper(a, b): print(f"{func.__name__!r} begins") start_time = time.time() func(a, b) print(f"{func.__name__!r} ends in {time.time()-start_time} secs") return wrapper
Так мы можем определить любой декоратор. functools
помогает нам создавать декораторы с помощью wraps
. По сути, мы делаем что-то перед вызовом любой функции и делаем что-то после вызова функции в описанном выше декораторе.
Теперь мы можем использовать этот декоратор timer
для украшения нашей функции somefunc
@timer def somefunc(a, b): output = a+b return output
Теперь, вызывая эту функцию, мы получаем:
a = somefunc(4,5) # Output # --------------------------------------------- # 'somefunc' begins # 'somefunc' ends in 2.86102294921875e-06 secs
Теперь мы можем добавить @timer
к каждой нашей функции, для которой мы хотим, чтобы печаталось время.
Соединяя все части
![](/static/storage/179073166829788283235981275293446754770.jpeg)
Что если наша функция принимает три аргумента? Или много аргументов?
Именно здесь соединяется все, что мы узнали до сих пор. Мы используем *args
и **kwargs
Мы меняем нашу функцию декоратора так:
from functools import wraps def timer(func): @wraps(func) def wrapper(*args, **kwargs): print(f"{func.__name__!r} begins") start_time = time.time() func(*args, **kwargs) print(f"{func.__name__!r} ends in {time.time()-start_time} secs") return wrapper
Теперь наша функция может принимать любое количество аргументов, и наш декоратор все равно будет работать.
На мой взгляд, декораторы могут быть очень полезными. Я предоставил только один вариант использования декораторов, но есть несколько способов их использования.
Вы можете использовать декоратор для отладки кода, проверяя, какие аргументы входят в функцию. Или декоратор может быть использован для подсчета количества вызовов конкретной функции. Это может помочь с подсчетом рекурсивных вызовов.
Заключение
В этом посте я рассказал о некоторых конструкциях, которые вы можете найти в исходном коде Python, и о том, как вы можете их понять.
Не обязательно, чтобы вы в конечном итоге использовали их в своем коде. Но я предполагаю, что понимание того, как эти вещи работают, помогает смягчить некоторую путаницу и панику, с которой сталкиваются каждый раз, когда появляются эти конструкции.