logo
Ещё

Функции и аргументы в Python

Функция – базовая единица кода в любом более-менее серьезном программировании. Да, пока вы учитесь – вы пишете код вроде «result = 2 + 2; print(result)», но в реальных приложениях код измеряется не отдельными операциями, а бизнес-действиями – создать, передать, вывести на экран, запросить, проверить и так далее. Все это удобно пакуется в функции, которые затем объединяются в классы, библиотеки, фреймворки и конечные приложения. Ниже – о том, как функции устроены в Python и как применять их хорошо/красиво/правильно.

Что такое «функция» и зачем она нужна?

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


Структуру и ключевые слова мы рассмотрим позже, но если вы хоть немного знаете английский – здесь не будет ничего сложного: слово def (от define) указывает на создание новой функции с именем «add_5», функция возвращает переданное ей число, предварительно приплюсовав к нему 5. Что здесь можно увидеть:

  • Функция выполняет конкретную операцию. Мы добавляем 5 к числу, которое передается в функцию.
  • Функция оперирует переданными аргументами. Мы передали в нее 4 – функция вернула 9. Технически функция может обойтись и без аргументов – просто возвращать что-нибудь.
  • Функция логически закончена. Она делает то, что мы от нее ожидаем – прибавляет 5 к результату; все нужные действия выполняются внутри функции, никаких дополнительных действий где-то вне ее производить не нужно.
  • Функция имеет четкие рамки в коде. Def – первая строка в функции; весь код, принадлежащий функции, выделен знаками табуляции.
  • Функция может быть переиспользована. Мы вызвали функцию в коде дважды.

Функции – крайне мощный инструмент разработки, потому что они позволяют одновременно:

  • Сократить количество кода и уменьшить количество багов в нем. Если вам нужно выполнить 100 одинаковых операций – можно написать 1 функцию и вызвать ее 100 раз вместо того, чтобы 100 раз писать в коде одно и то же. Это и уменьшит объем кода, и уменьшит шансы того, что при очередной попытке написать тот же функционал вы где-то ошибетесь.
  • Упростить дебаггинг. Во-первых – чем меньше кода, тем проще его дебажить. Во-вторых – в приложении, построенном на функциях, есть стэк вызовов (последовательность вызова функций), и любой язык при наличии бага укажет вам, где именно в этом стэке возникла проблема – вам нужно будет найти баг в конкретной небольшой функции, а не в листинге на 1 000 строк.
  • Существенно улучшить читаемость кода. Золотое правило разработки – любая функция должна иметь название, отражающее ее суть. Если вы «правильно» называли свои функции – вам потом будет крайне просто и модифицировать, и переиспользовать свой же собственный код.

Функции настолько незаменимы, что есть везде: в императивной парадигме в функции (подпрограммы) выносят любой повторяющийся код; в объектно-ориентированной парадигме функции превращаются в методы – действия объектов; в функциональном программировании функция превращается в объект, на котором все программирование и строится.

Типы функций

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

  • Метод. Функция класса/объекта. Обычно выполняет одну небольшую задачу, логически связанную с объектом. Вызывается записью «объект.метод()». В Python есть много встроенных методов объектов – list.add(), например.
  • Лямбды. Неименованные функции, обычно выполняющие одну маленькую задачу. Нужны там, где другая функция принимает в качестве одного из аргументов функцию. Например, для некоторых встроенных функций сортировки вы можете передать лямбду, которая будет указывать, как именно нужно сортировать сложную структуру данных. Конечно, можно написать вместо лямбды полноценную функцию, но обычно в этом нет смысла – лямбды как раз хороши там, где функция будет использована один раз и навсегда забыта.

Анатомия функций

Сейчас мы приведем в качестве примера сложную функцию, на основе которой будем поэтапно разбирать анатомию функций:

def example_function(a, b=10, *args, **kwargs):

result = a + b

if args:

result += sum(args)

if 'multiply' in kwargs:

result *= kwargs['multiply']

if 'return_type' in kwargs and kwargs['return_type'] == 'str':

return f"Результат: {result}"

elif 'return_type' in kwargs and kwargs['return_type'] == 'dict':

return {"result": result}

else:

return result 

print(example_function(5)) 

print(example_function(5, 15)) 

print(example_function(5, 15, 1, 2, 3)) 

print(example_function(5, 15, 1, 2, 3, multiply=2)) 

print(example_function(5, return_type="str"))

print(example_function(5, 15, 1, 2, return_type="dict"))

def, имя

Функция всегда начинается с ключевого слова «def», за которым через пробел следует имя функции. Сразу после имени функции идут скобки – в них перечисляются параметры. Если параметров нет – скобки остаются пустыми:

def example_function():

Если вы попытаетесь скормить эту строку интерпретатору, то получите ошибку – в теле функции в Python обязательно должно что-то быть. Если сейчас вы не хотите ничего добавлять – используйте ключевое слово «pass», которое служит заглушкой:

def example_function():

pass

Параметры и аргументы – в чем разница и как передавать/получать

А теперь давайте к самому сложному – параметрам и аргументам. Для начала – обязательно разберитесь, чем они отличаются, потому что в любой технической литературе эта разница имеет большой смысл. Итак:

  • Параметры – это переменные, которые мы указываем в скобках в той строке, в которой прописываем def и имя функции (это называется сигнатурой функции).
  • Аргументы – это переменные, которые мы передаем в функцию при ее вызове.

В коде:

def test_func(x):

return x*2

print(test_func(5))

x – это параметр, а 5 – это аргумент. Кстати, print() – тоже функция, так что test_func(5) – это аргумент.

Итак, какими бывают параметры:

  • Одиночные без дефолтного значения. Указываются переменной, которая примет в себя аргумент. Если функция будет ожидать такой аргумент, но ничего не будет передано – возникнет ошибка.

def example_function(a):

  • Одиночные с дефолтным значением. То же, что и предыдущее, но если в функцию не передадут аргумент – она возьмет указанное у параметра значение по умолчанию.

def example_function(a, b=10):

  • Переменной длины, список. Иногда мы не знаем, сколько именно аргументов будет передано в функцию – может, 1, а может и 10. В этом случае мы в конце списка параметров добавляем «*args», и все позиционные аргументы (смотрите ниже), которые не влезли в одиночные параметры, будут помещены в список args.

def example_function(a, b=10, *args):

  • Переменной длины, словарь. То же, что и предыдущее, но сюда помещаются все именованные аргументы (смотрите ниже) в виде словаря.

def example_function(a, b=10, *args, **kwargs):

Теперь разберемся с аргументами – они бывают:

  • Позиционными. В этом случае аргументы передаются в функцию в порядке очереди – первый аргумент передается в первый параметр, второй аргумент передается во второй параметр и так далее.

print(example_function(5, 15, 1, 2, 3)) 

  • Именованными. В этом случае мы указываем имя параметра, которому будет передан аргумент, и порядок можно не соблюдать. Важно: если мы используем и позиционные, и именованные аргументы, то сначала мы перечисляем все позиционные аргументы в строгом порядке, а затем уже перечисляем именованные аргументы в произвольном порядке.

print(example_function(5, 15, 1, 2, 3, multiply=2)) 

С аргументами, переданными в args и kwargs, можно работать так же, как и с обычными списками/словарями:

if args:

result += sum(args)

if 'multiply' in kwargs:

result *= kwargs['multiply']

Тело функции и return

Теперь, когда с объявлением функции и входящими данными покончено, вы можете писать код, который будет что-то делать. Но просто сделать что-либо – недостаточно, вам обычно нужно вернуть данные. Делается это ключевым словом return – как только интерпретатор встречает это слово, он возвращает в строку кода, вызвавшую функцию, прописанный в return результат:

return result 

В функции можно использовать несколько return – просто помните, что за один вызов функции можно «триггернуть» один return, не больше:

if 'return_type' in kwargs and kwargs['return_type'] == 'str':

return f"Результат: {result}"

elif 'return_type' in kwargs and kwargs['return_type'] == 'dict':

return {"result": result}

else:

return result 

Return в функции может и не быть – тогда функция вернет None.

Практики хорошего кода

Чтобы написать хорошую функцию, вам нужно придерживаться следующих правил:

  1. Называйте функции осмысленно. Если вы будете писать код, в котором основную функциональность выполняют функции a(), b(), c(), d() и e() – после выходных вы забудете, что эти функции делают.
  2. Документируйте функции. Сразу после def обычно пишут многострочный комментарий, который описывает, какие значения функция принимает и что возвращает.
  3. Старайтесь использовать меньше параметров. Если их в сигнатуре все же выходит слишком много – попытайтесь преобразовать их в структуру данных, с которой будет удобно работать.
  4. Продумайте значения по умолчанию. Далеко не всегда в функцию приходят все нужные для работы значений, поэтому для каждого параметра вам нужно решить, что вы будете делать: вызывать ошибку или вставлять дефолтное значение?
  5. В идеале функция должна делать одно действие. Если ваша функция делает 2 дела – возможно, стоит разбить ее на 2 функции.