Функции

Мы зачастую хотим делить нашу программу на составные блоки, это могут быть модули/пакеты модулей, классы для написания программ в парадигме ООП. Одним из составных блоков нашей программы может быть функция.

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

Для объявления функций используется инструкция def. Пример простой функции с именем foo, которая ничего не делает

def foo():
pass

Функции получают аргументы. Аргументы мы перечисляем в круглых скобочках.

Вызов функций

Для вызова функции(т.е перехода к исполнению ее инструкций) используется оператор вызова функции:

foo() # вызов функции без аргументов
foo(1, 2) # вызов функции c передачей двух позиционных аргументов

Возврат значения из функции / оператор return

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

def getValue():
return "value"
print(getValue())

Если мы явно ничего из функции не возвращали - из функции вернется None

def bar():
pass
print(bar()) # None

Помимо этого инструкция return прекращает дальнейшее исполнение функции и мы можем исползовать return без указания возвращаемого значения:

def baz(a):
if a > 10:
return
return "'a' less then 10"
print(baz(5)) # "'a' less then 10"
print(baz(11)) # None

Аргументы функции

  • Позиционные аргументы - аргументы передаваемые при вызове по их порядку в объявлении функции
  • Именованные аргументы - аргументы передаваемые по их имени, указанному при объвлении функции
def bar(arg1, arg2): # функция плучает два аргумента: arg1 и arg2
print(f"arg1: {arg1}, arg2: {arg2}")
# 👇 Позиционный аргумент
bar(1, arg2=2)
# 👆 Именованный аргумент

Аргументы со значением по умолчанию (Опициональные)

Если вы объявили функцию с 2 аргументами и не указали значения по умолчанию - эти аргументы будут обязательными при вызове функции, для объявления функции с опциональными аргументами используется следующий синтаксис:

# 👇 Указываем значение по умолчанию
def foo(arg1 = "default value"):
print(f"arg1 = {arg1}")
foo()

В примере мы создали функцию foo, которая получает один аргумент, но он не является обязательным, потому что для него указано значение по умолчанию("default value")

❗️Обратите внимание

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

def addvalue(value, target_list= []):
"""
Функция добавляющая значение в лист и возвращающая его, зачем она это делает?.. не будем углубляться
Но интересный факт, что если лист не передан вторым аргументом по умолчанию будет использоваться значение по умолчанию
"""
target_list.append(value)
return target_list
print(addvalue(1)) # что ожидаем увидеть? Ну правильно! [1]
print(addvalue(2)) # а здесь что будет? Должно быть неожиданным, но вернет она [1,2]

Живой примерчик:

Сворачивание аргументов

Нам было бы не удобно если функции могли получать только ограниченное кол-во аргументов, поэтому мы можем делать сворачивание аргументов. Для начала рассмотрим пример функции sum которая получает только 2 аргумента и возвращает их сумму:

def sum(a, b):
return a + b

Но что делать если мы хотим вызывать функцию с несколькими аргументами?

sum(1, 2, 3) # Что здесь произойдет?

В таком случае давайте сделаем так, чтобы все позиционные аргументы собирались в какую-то одну переменную-аргумент, назовем ее args. Для сворачивания аргуметов используется символ *

def sum(*args):
result = 0
print(args) # [1, 2, 3]
for i in args:
if type(i) == int:
result += i
return result
sum(1, 2, 3)

Вполне естественно для понимания, что переменная args внутри функции будет типа tuple. Примерно такое же вы можете делать и для именованных аргументов

# 👇 Сворачиваем все позиционные аргументы
# | 👇 Сворачиваем все оставшиеся аргументы
def sum(*args, limit=None, **kwargs):
result = 0
print(args) # [1, 2, 3]
print(kwargs) # {"offset": 10}
for i in args:
if type(i) == int:
result += i
return result
sum(1, 2, 3, limit=20, offset=10)

Для того чтобы свернуть все оставшиеся именованные аргументы используется синтаксис с двумя звездочками **. А тип переменной kwargs будет dict.

Только позиционные и только именованные аргументы

⚠️Важно заметить

Только позиционные аргументы появились в Python 3.8

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only

Только именованные

Для того чтобы сделать аргументы только именованными нам надо просто воспользоваться сворачиванием позиционных аргументов через * или *args. Все перечисленные аргументы полсле сворачивания будут передаваться только по имени.

Только позиционные

Для того чтобы сделать аргументы только позиционными они отделяются символом /. (Как показано в примере выше)

Что делает def?

Давайте теперь рассмотрим что делает оператор def, и для того чтобы меньше самому писать я обращусь к Лутцу.

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

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

Что мы можем понять из этих двух пунктов? А то, что когда вы используете инструкцию def вы просто создаете объект, у которого есть имя и магический метод __call__. По этим причинам следующий код будет работать:

def foo():
return "I am foo"
print(foo.__call__()) # Hello
print(foo.__name__) # foo
setattr(foo, "name", "value") # добавляем аттрибут объекту функции
print(foo.name) # value
# А особо интересной строчкой будет являться следующее
foo.__call__.__call__.__call__() # "I am foo"

Рекурсивный вызов функций

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

Вот пример получения n`ого-числа Фибоначи:

def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2) # 👈 А вот и рекурсивный вызов функции `fib`
print(fib(8)) #

❗️Обратите внимание

С помощью рекурсии очень удобно обрабатывать древовидные структуры(HTML, Дерево директорий...)