DevGang
Авторизоваться

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, и о том, как вы можете их понять.

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

#Начинающим #Python
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу