5.1. Теория

Подпрограмма - средство языка программирования, позволяющее упаковывать и параметризовать функциональность.

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

5.1.1. Основные понятия и механизм работы

5.1.1.1. Определение подпрограммы

Подпрограмма должна быть объявлена и в общем случае содержать:

  • имя;
  • список имен и типов передаваемых параметров (необязательно);
  • тип возвращаемого значения (необязательно).

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

5.1.1.2. Вызов подпрограммы

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

  • указать имя подпрограммы;
  • передать требуемые аргументы (значения параметров).

Код, вызвавший подпрограмму, передает ей управление и ожидает завершения выполнения.

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

В настоящее время наиболее часто встречаются следующие способы передачи аргументов:

  1. По значению

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

  2. По ссылке

    Изменения, которые происходят в теле подпрограммы с переменной, переданной по ссылке, происходят с самой переданной переменной.

5.1.1.3. Механизм работы

Большинство современных языков программирования для управления вызовом подпрограмм используют стек вызовов.

Примерный цикл работы стека вызова следующий (Видео 5.1.1, Видео 5.1.2):

  1. Вызов подпрограммы создает запись в стеке; каждая запись может содержать информацию о данных вызова (аргументах, результате, а также адресе возврата).
  2. Когда подпрограмма завершается, запись удаляется из стека и программа продолжает выполняться, начиная с адреса возврата.

Видео 5.1.1 - Cтек вызовов (пример, Нетология)

Видео 5.1.2 - Cтек вызовов (пример, Stepik.org)

5.1.1.4. Преимущества и недостатки

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

Преимущества использования подпрограмм:

  • декомпозиция сложной задачи на несколько более простых подзадач: это один из двух главных инструментов структурного программирование (второй - структуры данных);
  • уменьшение дублирования кода и возможность повторного использования кода в нескольких программах - следование принципу DRY «не повторяйся» (англ. Don’t Repeat Yourself);
  • распределение большой задачи между несколькими разработчиками или стадиями проекта;
  • сокрытие деталей реализации от пользователей подпрограммы;
  • улучшение отслеживания выполнения кода (большинство языков программирования предоставляет стек вызовов подпрограмм).

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

5.1.2. Функции в Python

В Python нет формального разделения подпрограмм на функции и процедуры (как, например, в Паскале или Си), и процедурой можно считать функцию, возвращающую пустое значение - в остальном используется единственный термин - функция.

def

Для объявления функции в Python используется ключевое слово def:

def function_name([parameters]):  # `parameters`: параметры функции (через запятую)
    suite                         # Тело функции

На имя функции в Python накладываются такие же ограничения, как и на прочие идентификаторы.

Примечание

PEP8.

Соглашение рекомендует использовать:

В Python все данные - объекты, при вызове в функцию передается ссылка на этот объект. При этом, мутирующие объекты передаются по ссылке, немутирующие - по значению.

return

Функция в Python может возвращать результат своего выполнения, используя оператор return (например, return 5). В случае, если он не был указан или указан пустой оператор return, возвращается специальное значение None.

Примечание

При встрече оператора return в коде Python немедленно завершает выполнение функции, аналогично break для циклических конструкций.

Пример определения и вызова функции приведен в Листинге 5.1.1 и на Рисунке 5.1.1.

Листинг 5.1.1 - Пример определения и вызова функции | скачать
# Функция для вычисления гипотенузы
# Имя: hypot, 2 параметра: x, y
# return возвращает результат работы функции вызвавшему
def hypot(x, y):
    return (x**2 + y**2)**0.5

z = hypot(3, 4)  # Передача в функцию 2-х аргументов: 3 и 4
print(z)  # 5.0

a = 5
b = 12
print(hypot(a, b))  # 13.0 - результат функции может быть использован сразу
_images/05_01_01.png

Рисунок 5.1.1 - Передача параметров и возвращение результата функции

Python, как и многие другие языки, позволяет создавать собственные (пользовательские) функции, среди которых можно выделить четыре типа (Листинг 5.1.2):

  1. Глобальные

    Доступны из любой точки программного кода в том же модуле или из других модулей.

  2. Локальные (вложенные)

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

  3. Анонимные

    Не имеют имени и объявляются в месте использования. В Python и представлены лямбда-выражениями.

  4. Методы

    Функции, ассоциированные с каким-либо объектом (например, list.append(), где append() - метод объекта list).

Листинг 5.1.2 - Четыре типа функций в Python | скачать
class Car:
    def move(self, x):  # Метод (3)
        self.x += x


def sum_of_cubes(x, y):  # Глобальная функция (1)

    # Локальная функция (2) (ее "видит" только код внутри sum_of_cubes())
    def cube(a):
        return a**3

    return cube(x) + cube(y)  # return возвращает результат выполнения тому,
                              # кто вызвал эту функцию

players = [{"name": "Юрий", "rank": 5},
           {"name": "Сергей", "rank": 3},
           {"name": "Максим", "rank": 4}]
# Анонимная функция (4) (лямбда-выражение)
# В функции sorted() используется для определения порядка сортировки

print(sorted(players, key=lambda player: player["name"]))  # Сортировка по name
# [{'rank': 4, 'name': 'Максим'}, {'rank': 3, 'name': 'Сергей'}, {'rank': 5, 'name': 'Юрий'}]

print(sorted(players, key=lambda player: player["rank"]))  # Сортировка по rank
# [{'rank': 3, 'name': 'Сергей'}, {'rank': 4, 'name': 'Максим'}, {'rank': 5, 'name': 'Юрий'}]

5.1.3. Глобальные и локальные функции

5.1.3.1. Параметры и аргументы

5.1.3.1.1. Позиционные и ключевые параметры/аргументы

Все параметры, указываемые в Python при объявлении и вызове функции делятся на:

  • позиционные: указываются простым перечислением:

    def function_name(a, b, c):  # a, b, c - 3 позиционных параметра
        pass
    
  • ключевые: указываются перечислением ключ=значение:

    def function_name(key=value, key2=value2):  # key, key2 - 2 позиционных аргумента
        pass                                    # value, value2 - их значения по умолчанию
    

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

  • объявление функции:

    def example_func(a, b, c):        # можно : 'a', 'b', 'c' - позиционные параметры
        pass
    
    def example_func(a, b, c=3):      # можно : 'a', 'b' - позиционные параметры,
        pass                          #         'c' - ключевой параметр
    
    def example_func(a=1, b=2, c=3):  # можно : 'a', 'b', 'c' - ключевые параметры
        pass
    
    def example_func(a=1, с, b=2):    # нельзя: ключевой параметр 'a'
        pass                          #         идет раньше позиционнных
    
  • вызов функции:

    def example_func(a, b, c=3):  # a, b - позиционные параметры, c - ключевой параметр
        pass
    
    # Вызовы функции
    example_func(1, 2, 5)        # можно : аргументы 1, 2, 5 распределяются
                                 #         позиционно по параметрам 'a', 'b', 'c'
    
    example_func(1, 2)           # можно : аргументы 1, 2 распределяются позиционно
                                 #         по параметрам 'a', 'b'
                                 #         в ключевой параметр 'c' аргумент
                                 #         не передается, используется значение 3
    
    example_func(a=1, b=2)       # можно : аналогично example_func(1, 2),
                                 #         все аргументы передаются по ключу
    
    example_func(b=2, a=1)       # можно : аналогично example_func(a=1, b=2),
                                 #         если все позиционные параметры заполнены как
                                 #         ключевые аргументы, можно не соблюдать порядок
    
    example_func(c=5, b=2, a=1)  # можно : аналогично example_func(1, 2),
                                 #         аргументы передаются по ключу
    
    example_func(1)              # нельзя: для позиционного аргумента 'b'
                                 #         не передается аргумент
    
    example_func(b=1)            # нельзя: для позиционного аргумента 'a'
                                 #         не передается аргумент
    

Преимущества ключевых параметров:

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

Пример функции со смешанными типами параметров приведен в Листинге 5.1.3.

Листинг 5.1.3 - Позиционные и ключевые параметры функций в Python | скачать
# Функция выдает запрос и
#   - возвращает True в случае положительного ответа
#   - возвращает False в случае отрицательного ответа
#   - возвращает False если не получает ответ за [retries] попыток

def ask_user(prompt, retries=3, hint="Ответьте, ДА или НЕТ?"):
    while True:
        retries -= 1
        ok = input(prompt + " -> ").upper()

        if ok in ("Д", "ДА"):
            return True
        elif ok in ("Н", "НЕТ"):
            return False

        if retries <= 0:
            print("Не смог получить нужный ответ, считаю за отказ.")
            return False
        print(hint)

# С ключевыми параметрами будут доступны также следующие варианты:
# ask_user("Сохранить файл?", 0)
# ask_user("Сохранить файл?", retries=1)
# ask_user("Сохранить файл?", 2, "Жми Д или Н!!!")
# и др.

if ask_user("Сохранить файл?"):
    print("Сохранил!")
else:
    print("Не сохранил.")

# -------------
# Пример вывода:
#
# Сохранить файл? -> Не знаю
# Ответьте, ДА или НЕТ?
# Сохранить файл? -> Да
# Сохранил!

5.1.3.1.2. Упаковка и распаковка аргументов

В ряде случаев бывает полезно определить функцию, способную принимать любое число аргументов. Так, например, работает функция print(), которая может принимать на печать различное количество объектов и выводить их на экран.

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

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

Пример упаковки аргументов приведен в Листинге 5.1.4.

Листинг 5.1.4 - Упаковка аргументов | скачать
# При упаковке аргументов все переданные позиционные аргументы
# будут собраны в кортеж 'order', а ключевые - в словарь 'info'

def print_order(*order, **info):
    print("Музыкальная библиотека №1\n")

    # Словарь 'infos' должен содержать ключи 'author' и 'birthday'
    for key, value in sorted(info.items()):
        print(key, ":", value)

    # Кортеж 'order' содержит все наименования произведений
    print("Вы выбрали:")
    for item in order:
        print("  -", item)

    print("\nПриходите еще!")

print_order("Славянский марш", "Лебединое озеро", "Спящая красавица",
            "Пиковая дама", "Щелкунчик",
            author="П.И. Чайковский", birthday="07/05/1840")

# -------------
# Пример вывода:
#
# Музыкальная библиотека №1
#
# author : П.И. Чайковский
# birthday : 07/05/1840
# Вы выбрали:
#   - Славянский марш
#   - Лебединое озеро
#   - Спящая красавица
#   - Пиковая дама
#   - Щелкунчик
#
# Приходите еще!

Python также предусматривает и обратный механизм - распаковку аргументов, используя аналогичные обозначения перед аргументом:

  • *: кортеж/список распаковывается как отдельные позиционные аргументы и передается в функцию;
  • **: словарь распаковывается как набор ключевых аргументов и передается в функцию.

Пример распаковки аргументов приведен в Листинге 5.1.5.

Листинг 5.1.5 - Распаковка аргументов | скачать
# Площадь треугольника по формуле Герона
#
# Данный пример функции предназначен для демонстрации распаковки
# Не проектируйте функцию таким образом -
# расчетная функция должна возвращать число, а не строку!
def heron_area_str(a, b, c, units="сантиметры", print_error=True):
    if a + b <= c or a + c <= b or b + c <= a:
        if print_error:
            return "Проверьте введенные стороны треугольника!"
        return

    p = (a + b + c) / 2
    s = (p * (p - a) * (p - b) * (p - c)) ** 0.5
    return "{} {}".format(s, units)

abc = [3, 4, 5]
params = dict(print_error=True, units="см.")

# При распаковке аргументов список 'abc' будет распакован в
# позиционные аргументы, а словарь 'params' - ключевые
print(heron_area_str(*abc, **params))

# -------------
# Пример вывода:
#
# 6.0 см.

5.1.3.2. Область видимости

Область видимости - область программы, где определяются идентификаторы, и транслятор выполняет их поиск. За пределами области видимости тот же самый идентификатор может быть связан с другой переменной, либо быть свободным (не связанным ни с какой из них).

В Python выделяется четыре области видимости:

  1. Локальная (англ. Local)

    Собственная область внутри инструкции def.

  2. Нелокальная (англ. Enclosed)

    Область в пределах вышестоящей инструкции def.

  3. Глобальная (англ. Global)

    Область за пределами всех инструкций def - глобальная для всего модуля.

  4. Встроенная (англ. Built-in).

    «Системная» область модуля builtins: содержит предопределенные идентификаторы, например, функцию max() и т.п.

Локальная и нелокальная области видимости являются относительными, глобальная и встроенная - абсолютными.

Основные положения:

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

В Листинге 5.1.6 приведен пример четырех областей видимости.

Листинг 5.1.6 - 4 области видимости в Python | скачать
def min_of_cubes(x, y):

    # Идентификаторы 'x' и 'y' являются:
    # - локальными для min_of_cubes()
    # - нелокальными для cube()

    def cube(a):
        return a**3  # 'a' - локальный идентификатор функции cube()

    return min(cube(x), cube(y), cube(c))  # Функция min() находится
                                           # во встроенной области
                                           # видимости и видна везде

# Идентификаторы 'a', 'b' и 'c' имеют глобальную область видимости
a, b, c = 2, 3, 4
print(min_of_cubes(a, b))  # 8

Схема разрешения имен в языке Python называется правилом LEGB (Local, Enclosed, Global, Built-in) [6]: когда внутри функции выполняется обращение к неизвестному имени, интерпретатор пытается отыскать его в четырех областях видимости по очереди до первого нахождения.

В Листингах 5.1.7 (а-д) приведены некоторые случае выполнения LEGB-правила.

Листинг 5.1.7 (а) - Области видимости в Python (пример) | скачать
# Увеличиваем переменную 'a' локально
def sum_of_2(a, b):
    a += 2
    return a + b

a, b = 3, 4
print(sum_of_2(a, b))  # 9
print(a, b)            # 3 4
Листинг 5.1.7 (б) - Области видимости в Python (пример) | скачать
# Добавляем к сумме локальную переменную 'x'
def sum_of_2(a, b):
    x = 5
    return a + b + x

a, b = 3, 4
print(sum_of_2(a, b))  # 12
print(a, b, x)         # NameError: name 'x' is not defined
Листинг 5.1.7 (в) - Области видимости в Python (пример) | скачать
# Пробуем увеличить глобальную переменную 'c'
def sum_of_2(a, b):
    c *= 5  # UnboundLocalError: local variable 'c' referenced before assignment
    return a + b + с

a, b, c = 3, 4, 5
print(sum_of_2(a, b))
Листинг 5.1.7 (г) - Области видимости в Python (пример) | скачать
# Пробуем изменить глобальную переменную 'c'
def sum_of_2(a, b):
    с = 10
    return a + b + с

a, b, c = 3, 4, 5
print(sum_of_2(a, b))  # 17
print(a, b, c)         # 3 4 5
Листинг 5.1.7 (д) - Области видимости в Python (пример) | скачать
# Используем глобальную переменную 'c', не имея соответствующего параметра
def sum_of_2(a, b):
    return a + b + c

a, b, c = 3, 4, 5
print(sum_of_2(a, b))  # 12

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

global
nonlocal

Если необходимо изменять в функции переменные более закрытой области видимости, существует 3 способа:

  • использовать инструкцию global: сообщая, что функция будет изменять один или более глобальных идентификаторов;
  • использовать инструкцию nonlocal: сообщая, что вложенная функция будет изменять один или более идентификаторов внешних функций;
  • передать мутирующий аргумент в качестве параметра функции.

В Листингах 5.1.8, 5.1.9 и 5.1.10 приведены примеры использования ключевых слов global, nonlocal, а также передачи мутирующего аргумента.

Листинг 5.1.8 - Использование global позволяет менять значение глобальной переменной внутри функции | скачать
def func():
    # 'value' создается как локальный идентификатор
    value = 100

def func_with_global():
    global value
    # global указывает, что нужно использовать 'value'
    # из глобальной области видимости
    value = 100

value = 0
func()
print(value)  # 0

func_with_global()
print(value)  # 100
Листинг 5.1.9 - Использование nonlocal позволяет менять значение нелокальной переменной внутри вложенной функции | скачать
def func():

    def inner_func():
        # 'value' создается как локальный идентификатор inner_func()
        value = 100

    def inner_func_with_nonlocal():
        nonlocal value
        # Благодаря nonlocal используется 'value' из func()
        value = 100

    value = 10
    inner_func()
    print(value)  # 10

    inner_func_with_nonlocal()
    print(value)  # 100

value = 0
func()
print("global value =", value)  # 0
Листинг 5.1.10 - Передача мутирующего аргумента в функцию | скачать
# Функция принимает список, и добавляет сумму элементов в конец списка
#
# Изменение внутри функции мутирующего объекта приводит к его
# изменению и в вызывающем коде
def sum_list(lst):
    lst.append(sum(lst))

my_list = [1, 2, 3, 4, 5]
sum_list(my_list)
print(my_list)  # [1, 2, 3, 4, 5, 15]

Примечание

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

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

Предупреждение

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

Листинг 5.1.11 - Мутирующие аргументы сохраняют значения после нескольких вызовов | скачать
# Функция принимает число и список, к которому его нужно добавить
# Если список не передан, число должно оказаться в пустом списке

# 1. Данная реализация не учитывает единовременное создание значений
#    ключевых аргументов, и приводит к неожиданным эффектам
def change_list(a, lst=[]):
    lst.append(a)
    return lst

my_list = change_list(1)
my_list = change_list(2)
my_list = change_list(3)
print(my_list)  # [1, 2, 3]

# Та же функция, реализованная правильно
def change_list_2(a, lst=None):
    if lst is None:
        L = []

    L.append(a)
    return L

my_list = change_list_2(1)
my_list = change_list_2(2)
my_list = change_list_2(3)
print(my_list)  # [3]

5.1.3.3. Возврат нескольких значений

Часто из функции необходимо вернуть несколько значений (например, в Паскале, для этого используются выходные параметры с ключевым словом var). Одним из лучших способов для этого в Python является возврат кортежа с несколькими значениями (Листинг 5.1.12).

Листинг 5.1.12 - Возврат нескольких значений в виде кортежа | скачать
# Функция выполняет решение квадратного уравнения,
# сообщая вызывающему коду, сколько решений удалось получить
# в формате (num, (x1, ...)),
# где в начале идет количество решений, а в отдельном кортеже сами решения
def solve_equation(a, b, c):
    d = b**2 - 4 * a * c

    if d < 0:
        return (0, ())
    elif d == 0:
        x = -b / (2*a)
        return (1, (x, ))
    else:
        x1 = (-b - d**0.5) / (2*a)
        x2 = (-b + d**0.5) / (2*a)
        return (2, (x1, x2))

# Рекурсивный вариант вычисления факториала
a = int(input("a = "))
b = int(input("b = "))
c = int(input("c = "))

num, x = solve_equation(a, b, c)

print("\nРезультат решения: ", end="")
if num == 0:
    print("решений нет")
elif num == 1:
    print("x =", x[0])
elif num == 2:
    print("x1 =", x[0], " x2 =", x[1])
else:
    print("ошибка!")

# -------------
# Пример вывода:

# a = 1
# b = 4
# c = 3
#
# Результат решения: x1 = -3.0  x2 = -1.0

Прочие способы (например, изменение глобальных переменных) не рекомендуются [7].

5.1.3.4. Рекурсия

Рекурсия - вызов функции внутри самой себя, непосредственно (простая рекурсия) или через другие функции (сложная или косвенная рекурсия)

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

Не рекомендуется использовать рекурсию, если такая функция может привести или приводит к большой глубине рекурсии - лучше заменить ее циклической конструкцией. Рекурсивный вызов требуется некоторое количество оперативной памяти компьютера, и при чрезмерно большой глубине рекурсии может наступить переполнение стека (англ. англ. Stack Overflow) вызовов.

В Листинге 5.1.13 приведен пример реализации рекурсивной функции.

Листинг 5.1.13 - Рекурсивные функции | скачать
# Вычисление факториала, используя цикл
def factorial_1(x):
    res = 1
    for i in range(x):
        res *= i + 1
    return res

# Рекурсивный вариант вычисления факториала
def factorial_2(x):
    if x == 1:
        return 1
    else:
        return x * factorial_2(x - 1)

print(factorial_1(5))  # 120
print(factorial_2(5))  # 120

5.1.3.5. Строки документации

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

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

Соглашения по документированию функций содержатся в документе PEP 257.

Верно оформленная строка документации является краткой справкой по функции, которую можно увидеть, вызвав метод __doc__, или функцию help().

В Листинге 5.1.14 приведен пример написания документации к функции.

Листинг 5.1.14 - Документирование функции в Python | скачать
# Для небольшой функции можно использовать однострочное документирование
# Как правило, это описание действия в повелительном наклонении


def sqrt(x):
    """Вернуть квадратный корень из 'x'."""
    return x**0.5


# Если функция объемная, используется многострочное документирование
def heron(a, b, c):
    """Вернуть площадь треугольника по формуле Герона.

    Параметры:
        - a, b, c (float): стороны треугольника.

    Результат:
        - float: значение площади.
        - None: если треугольник не существует.
    """
    if not (a + b > c and a + c > b and b + c > a):
        return

    p = (a + b + c) / 2
    return sqrt(p * (p - a) * (p - b) * (p - c))


# Справочную информацию о функции можно получить 2 способами:
# heron.__doc__ или help(heron)
print(heron.__doc__)

params = [float(x) for x in input('Введите стороны (a b c): ').split()]
s = heron(*params)
if s:
    print('S = {:.2f}'.format(s))
else:
    print('Треугольник не существует!')

# -------------
# Пример вывода:

# Вернуть площадь треугольника по формуле Герона.
#
#     Аргументы:
#         - a, b, c (float): стороны треугольника.
#
#     Результат:
#         - float: значение площади.
#         - None: если треугольник не существует.
#
# Введите стороны (a b c): 3 4 5
# S = 6.00

5.1.4. Анонимные функции

Python поддерживает синтаксис, позволяющий определять анонимные функции (лямбда-функции или лямбда-выражения):

lambda
lambda parameters: expression
  • часть parameters является необязательной, и если она присутствует, то обычно представляет собой простой список имен переменных, разделенных запятыми (позиционных аргументов);
  • выражение expression не может содержать условных инструкций или циклов (условные выражения - допустимы), а также не может содержать инструкцию return;
  • результатом лямбда-выражения является анонимная функция.

Когда лямбда-функция вызывается, она возвращает результат вычисления выражения expression.

Пример записи лямбда-функции приведен в Листинге 5.1.15.

Листинг 5.1.15 - Лямбда-функции | скачать
# Лямбда-функции, как правило, используются в случаях, где использование
# функции не требует наличия у нее имени
#
# Если это требуется - лучше объявить обычную функцию, используя def

# 1. Обычная и лямбда-функция выполняют одно и то же действие,
#    разница в синтаксисе
def sqr1(x):
    return x**2

sqr2 = lambda x: x**2  # Если лямбда-выражение привязывается к идентификатору -
                       # - оно не нужно; используйте обычные функции

print(sqr1(6))  # 36
print(sqr2(6))  # 36

# 2. Лямбда-выражения удобно использовать для элементов функционального программирования
# Эл-ты из таблицы Менделеева (номер группы, порядковый номер, наименование)
elements = [(2, 12, "Mg"), (1, 11, "Na"), (1, 3, "Li"), (2, 4, "Be")]

elements.sort(key=lambda x: x[1])  # Сортировка порядковому номеру
print(elements)  # [(1, 3, 'Li'), (2, 4, 'Be'), (1, 11, 'Na'), (2, 12, 'Mg')]

# Функции max и min имеют ключевой аргумент key - параметр сортировки
# key - ссылка на функцию, имеющую 1 возвращающую значение, по которому следует
#       сравнивать величины

# 2.1. Используя лямбда-выражение
lst = ["Java", "Algol", "C++"]
print(max(lst, key=lambda x: x.count("a")))  # Элемент lst, в котором
                                             # больше всего "a"

# 2.2. Тоже самое возможно с использованием обычной функции
def find_a(x):
    return x.find("a")

print(min(lst, key=find_a))   # Элемент lst, в котором
                              # "a" находится левее

5.1.5. Побочный эффект

Побочный эффект (англ. Side Effect) - любые действия программы, изменяющие среду выполнения (англ. Execution Environment).

К побочным эффектам выполнения функции можно отнести:

  • изменение данных в памяти;
  • чтение/запись файла или устройства;
  • ввод/вывод значений;
  • самостоятельную реакцию на исключительные ситуации;
  • и др.

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

Создавая функцию, необходимо избегать побочных эффектов - такие функции легче тестируются и не содержат скрытых действий. Один из примеров такой функции - функция solve_equation() в Листинге 5.1.12.

Естественно, что полностью избежать побочных эффектов невозможно. В таких случаях необходимо локализовать участки кода с побочным эффектом в отдельные функции (Листинге 5.1.16).

Листинг 5.1.16 - Локализация кода с побочным эффектом в отдельные функции | скачать
def heron(a, b, c):
    """Вернуть площадь треугольника по формуле Герона.

    Параметры:
        - a, b, c (float): стороны треугольника.

    Результат:
        - float: значение площади.
        - None: если треугольник не существует.
    """
    if not (a + b > c and a + c > b and b + c > a):
        return

    p = (a + b + c) / 2
    return (p * (p - a) * (p - b) * (p - c))**0.5


def input_data():
    """Запросить стороны треугольника с клавиатуры.

    Результат:
        - tuple of float: (a, b, c).

    Побочный эффект!
    """
    return (float(x) for x in input('Введите стороны (a b c): ').split())

def print_res(res):
    """Вывести на экран 'res'.

    Параметры:
        - res (float): площадь треугольника.

    Побочный эффект!
    """
    if res:
        print('S = {:.2f}'.format(res))
    else:
        print('Треугольник не существует!')


a, b, c = input_data()
res = heron(a, b, c)
print_res(res)

Предупреждение

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

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


[1]Sebesta, W.S Concepts of Programming languages. 10E; ISBN 978-0133943023.
[2]Python - официальный сайт. URL: https://www.python.org/.
[3]Python - FAQ. URL: https://docs.python.org/3/faq/programming.html.
[4]Саммерфилд М. Программирование на Python 3. Подробное руководство. — М.: Символ-Плюс, 2009. — 608 с.: ISBN: 978-5-93286-161-5.
[5]Лучано Рамальо. Python. К вершинам мастерства. — М.: ДМК Пресс , 2016. — 768 с.: ISBN: 978-5-97060-384-0, 978-1-491-94600-8.
[6]A Beginner’s Guide to Python’s Namespaces, Scope Resolution, and the LEGB Rule. URL: http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html.
[7]How do I write a function with output parameters (call by reference). URL: https://docs.python.org/3/faq/programming.html#id17.