Шорты Python

Праймеры по * args, ** kwargs, декораторам для Data Scientists

Понимание жизненно важно, когда дело доходит до кодирования

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

Есть такие, которые не могут понять даже опытные программисты. *args, **kwargs и декораторы - некоторые конструкции, которые попадают в эту категорию.

Думаю, многие мои друзья из области науки о данных тоже сталкивались с ними.

Большинство функций seaborn так или иначе используют * args и ** kwargs.

А как насчет декораторов?

Каждый раз, когда вы видите предупреждение о том, что какая-то функция будет устаревшей в следующей версии. В пакете sklearn для этого используются декораторы. Вы можете увидеть @deprecated в исходном коде. Это функция декоратора.

В этой серии публикаций под названием Python Shorts я объясню некоторые простые конструкции, предоставляемые Python, некоторые важные советы и некоторые варианты использования, которые я регулярно придумываю в своей работе с Data Science.

Этот пост посвящен простому для понимания объяснению некоторых сложных концепций.

Что такое * 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)

и так далее.

А вы когда-нибудь задумывались, как функция печати в Python может принимать столько аргументов? *args

Что такое ** 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? Думаю, нет, потому что имя и возраст важны. Мы не хотим писать «Михаилу 28 лет».

Приходите **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

Помните, что мы никогда не определяли Сансу, Арью или Тирион в качестве аргументов наших методов.

Это довольно мощная концепция. И многие программисты довольно умно используют ее при написании библиотек-оболочек.

Например, функция seaborn.scatterplot является оболочкой для функции plt.scatter из Matplotlib. По сути, используя *args и **kwargs, мы можем предоставить все аргументы, которые plt.scatter может принимать и seaborn.Scatterplot.

Это может сэкономить много усилий при написании кода, а также делает код надежным в будущем. Если когда-нибудь в будущемplt.scatter начнет принимать какие-либо новые аргументы, seaborn.Scatterplot функция все равно будет работать.

Что такое декораторы?

Проще говоря: Декораторы - это функции, которые обертывают другую функцию, изменяя ее поведение.

Простой пример:

Допустим, мы хотим добавить пользовательские функции к некоторым из наших функций. Функциональность заключается в том, что всякий раз, когда функция вызывается, печатается «function name начинается», а всякий раз, когда функция завершается, печатается «function name заканчивается» и время, затраченное функцией.

Предположим, наша функция:

def somefunc(a,b):
    output = a+b
    return output

Для этого мы можем добавить несколько строк печати ко всем нашим функциям.

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()
        result = func(a,b)
        print(f"{func.__name__!r} ends in {time.time()-start_time}  secs")
        return result
    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()
        result = func(*args,**kwargs)
        print(f"{func.__name__!r} ends in {time.time()-start_time}  secs")
        return result
    return wrapper

Теперь наша функция может принимать любое количество аргументов, и наш декоратор по-прежнему будет работать.

Разве Python не прекрасен?

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

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

Заключение

В этом посте я рассказал о некоторых конструкциях, которые можно найти в исходном коде python, и о том, как их понять.

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

Понимание жизненно важно, когда дело доходит до кодирования

Также, если вы хотите узнать больше о Python 3, я хотел бы назвать отличный курс Learn Intermediate level Python от Мичиганского университета. Проверьте это.

В будущем я также буду писать больше постов для начинающих. Сообщите мне, что вы думаете о сериале. Подпишитесь на меня в Medium или подпишитесь на мой блог, чтобы быть в курсе о них. Как всегда, я приветствую отзывы и конструктивную критику, и с ними можно связаться в Twitter @mlwhiz.