Python: *args, **kwargs и декораторы для начинающих
Python имеет много конструкций, которые достаточно просты для изучения и использования в нашем коде. Тогда как есть некоторые конструкции, которые всегда смущают нас, когда мы сталкиваемся с ними в нашем коде.
Есть некоторые, которые даже опытные программисты не могут понять. *args
, **kwargs
и декораторы - это некоторые конструкции, которые попадают в эту категорию.
Я думаю, что многие из моих друзей по Data Scientists сталкивались с ними тоже.
Большинство функций seaborn так или иначе используют *args и **kwargs.
Или как насчет декораторов?
Каждый раз, когда вы видите предупреждение о том, что какая-то функция будет устаревшей в следующей версии. Для этого пакет sklearn
использует декораторы. Вы можете увидеть в исходном коде конструкцию @deprecated
. Это функция декоратора.
В этой серии статей под названием « 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?
Проще говоря, вы можете использовать, **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
все равно будет работать.
Что такое декораторы?
Проще говоря: декораторы - это функции, которые обертывают другую функцию, изменяя ее поведение.
Простой пример:
Допустим, мы хотим добавить пользовательские функции к некоторым из наших функций. Функциональность заключается в том, что всякий раз, когда вызывается функция, печатается «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
к каждой нашей функции, для которой мы хотим, чтобы печаталось время.
Соединяя все части
Что если наша функция принимает три аргумента? Или много аргументов?
Именно здесь соединяется все, что мы узнали до сих пор. Мы используем *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, и о том, как вы можете их понять.
Не обязательно, чтобы вы в конечном итоге использовали их в своем коде. Но я предполагаю, что понимание того, как эти вещи работают, помогает смягчить некоторую путаницу и панику, с которой сталкиваются каждый раз, когда появляются эти конструкции.