Функции в Python

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

def имя (аргумент1, аргумент2, . . . аргументn): 
    . . . 
    return значение

Например, вот так может выглядеть функция:

def double(number):
    return number * 2

затем мы можем использовать эту функцию:

double(9) #18
x = 33
double(x) #66

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

9 * 2
x = 33
x * 2

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

Рекурсия

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

def factorial(n):
    if n == 1:
        return 1 # базовый случай
    else:
        return n * factorial(n-1)

Как работает код:

  1. Функция factorial(n) принимает один аргумент n, который представляет число, для которого нужно вычислить факториал.

  2. В первой строке условие if n == 1: проверяет базовый случай, когда n равно 1. В этом случае функция возвращает 1, так как факториал 1 равен 1.

  3. Если n не равно 1, то выполняется блок else:, где происходит рекурсивный вызов функции factorial(n-1). Это означает, что функция будет вызывать саму себя с аргументом n-1, пока не достигнет базового случая.

  4. На каждом уровне рекурсии функция умножает текущее значение n на результат вызова factorial(n-1), что в конечном итоге приводит к вычислению факториала исходного числа n.

Если вызвать factorial(5), то процесс будет следующим: - factorial(5) вернет 5 * factorial(4) - factorial(4) вернет 4 * factorial(3) - factorial(3) вернет 3 * factorial(2) - factorial(2) вернет 2 * factorial(1) - factorial(1) вернет 1 - Затем произойдет обратное раскручивание рекурсии, где каждое значение будет умножаться на предыдущее, в итоге получится 5! = 120.

Получение доступа к внешним переменным

Давайте посмотрим, что будет если попытаться обратиться к внешней переменной из функции:

count_of_calls = 0

def double_and_count(number):
    count_of_calls += 1
    return number * 2

Если вызвать такую функцию, будет получена ошибка:

UnboundLocalError: local variable 'count_of_calls' referenced before assignment on line 4 in main.py

Эта ошибка говорит нам о том, что мы ссылаемся на переменную, которая не объявлена. Но почему, мы же объявили ее count_of_calls = 0! Объявили, но в глобальной области видимости. В Python существует три области видимости - локальная, глобальная, и нелокальная

Давайте рассмотрим области видимости на примере нашего кода.

count_of_calls = 0 # Уровень вложенности 0, глобальная область видимости

def double_and_count(number):
    count_of_calls += 1 # Уровень вложенности 1, локальная область видимости
    return number * 2 # то же самое 

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

Чтобы получить из уровня 1 доступ к переменным уровня 0, нужно воспользоваться ключевым словом global перед вызовом этой переменной:

count_of_calls = 0

def double_and_count(number):
    global count_of_calls
    count_of_calls += 1
    return number * 2

Выражение global count_of_calls означает, что теперь все вызовы count_of_calls будут обращаться именно к той переменной из глобальной области видимости.

А чтобы получить доступ из уровня n к переменной из уровня n - 1, где n > 0, нужно использовать выражение nonlocal

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        nonlocal count, total
        count += 1
        total += new_value
        return total / count

    return averager

avg = make_averager()
print(avg(10))
print(avg(11))
print(avg(15))

Этот код определяет функцию make_averager(), которая возвращает другую функцию averager(). make_averager() использует замыкание, чтобы сохранить значения переменных count и total между вызовами функции averager(). Когда вы вызываете make_averager(), создается новая функция averager(), которая может обновлять значения count и total и вычислять среднее значение. Сама функция averager() принимает аргумент new_value, добавляет его к текущему значению total, увеличивает count на 1 и возвращает текущее среднее значение (total/count). Вызовы avg(10)avg(11) и avg(15) последовательно обновляют значения count и total и вычисляют среднее значение. Результаты этих вызовов будут выводиться на экран. Например, после первого вызова avg(10) значение total станет равным 10, а значение count станет равным 1. Следовательно, среднее значение будет равно 10 (10/1). После второго вызова avg(11) значение total станет равным 21 (10 + 11), а значение count станет равным 2 (1 + 1). Следовательно, среднее значение будет равно 10.5 (21/2).После третьего вызова avg(15) значение total станет равным 36 (21 + 15), а значение count станет равным 3 (2 + 1). Следовательно, среднее значение будет равно 12 (36/3).


ООП. Введение в объектно-ориентированное программирование в Python

Объектно-ориентированное программирование - одна из самых распространенных парадигм. Знание ООП позволит сделать код более гибким и масштабируемым

Модули в языке Python. Разбиение программ на модули. Установка сторонних библиотек

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

Pattern matching в Python - конструкция match case

В Python 3.10 был добавлен новый функционал - сопоставление с шаблонами с помощью ключевых слов match и case. В статье также рассматривается производительность match/case по сравнению с if/else.

Классы исключений. Создание собственных исключений

В этой статье описано, что означают различные классы исключений. Также рассмотрено создание собственных классов исключений.