Файл: Учебник по информатике. Программирование на Python. Основы 1 Программирование на Python. Основы.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 03.12.2023
Просмотров: 119
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Открытый учебник по информатике. Программирование на Python. Основы
59
Между абзацами в docstring-формате оставляется пустая строка. То есть между концом предыдущего абзаца и началом следующего ставить два переноса строки. Одиночный перенос строки используется для форматирования docstring.
Например, следующая документация к функции будет определена как двустрочная. def test():
'''
My first very very very long string
Second string
''' pass
Во всплывающем окне такой текст будет выглядеть, как на рисунке ниже.
Рисунок 3. Пример вывода многострочной документации
Для описания параметров используется следующая разметка:
:param имя_параметра: описание
Описание возвращаемого результата с помощью
:return: описание.
Открытый учебник по информатике. Программирование на Python. Основы
60
Пример. Функция для нахождения расстояния от точки до начала координат. def distance(x:int | float,y: int | float) -> int | float:
'''
Calculate distance from origin to point with coordinate (x, y)
:param x: Abscissa of point
:param y: Ordinate of point
:return: Distance origin to (x, y)
''' return (x*x + y*y)**0.5
Теперь, при использовании этой функции, мы сможем увидеть подробное описание данной функции, что позволит нам не тратить время на изучение её кода (см.рис.4).
Рисунок 4. Пример документированной функции
Открытый учебник по информатике. Программирование на Python. Основы
61
Распаковка аргументов
Что же из себя представляют параметры и каким образом значения аргументов передаются в функцию.
Список и словарь аргументов
Как мы выяснили ранее, значения аргументов могут быть переданы как позиционно, так и по ключу. Поэтому любая функция принимает два объекта
– список значений, переданных позиционно, и словарь значений, переданных по ключу.
Чтобы проиллюстрировать данное утверждение, можно провести распаковку переданных значений в передаваемые список и словарь. Подробнее про распаковку и словари поговорим в других главах учебника. def test(*argv, **kwagrv): print(argv, kwargv)
В данной функции переменная argv будет ссылаться на список значений, переданные позиционно, kwargv будет хранить пары ключ-значение для всех значений, переданных по ключу. Например, при вызове следующей функции получим такие объекты. test(4, '123z', [1,2], g=10, st='123asd')
# (4, '123z', [1, 2]) {'g': 10, 'st': '123asd'}
И такие две структуры всегда передаются при вызове функции. Далее проверяется, соответствуют ли переданные значения набору параметров. И, если соответствуют, то происходит соотнесение элементов переданных списка и словаря именам параметров.
Пример (корректная передача значений). def test2(a,b,c,d): print(a+b+c+d) test2(2, 6, c=5, d=15)
На вход функции test2 подается два значения по позиции (2 и 6) и два значения по ключу (c=5 и d=10).
По позиции:
0. a = 2 1. b = 6
По ключу:
с = 5, d = 10
Передача значений прошла корректно, все обязательные параметры определены, нет противоречий по переданным значениям.
Открытый учебник по информатике. Программирование на Python. Основы
62
Пример (некорректная передача значений). def test2(a,b,c,d): print(a+b+c+d) test2(2, 6, 8, d=15, c=10)
По позиции:
0. a = 2 1. b = 6 2. c = 8
По ключу:
с = 10, d = 15
Видим противоречие, позиционно значение параметра c определенно, как
8, по ключу – как 10. Одна переменная не может ссылаться сразу на два значения, поэтому при таком вызове функции test2 интерпретатор вернет ошибку «got multiple values for argument 'c'» или «получено несколько значений для аргумента 'c'».
Также ошибка будет, если передается больше значений, чем описано в параметрах функции. Если же необходимо передать неограниченное количество значений аргументов, то можно определять оставшиеся позиционные параметры с помощью оператора *, параметры, передаваемые по ключу – с помощью оператора **.
Так следующая функция other_pars принимает на вход два позиционных аргумента x, y, один аргумент по ключу kw и все остальные параметры в список
*other_pos, и **other_kw. def other_pars(x, y, *other_pos, kw, **other_kw): pass
Стоит отметить, что как только мы определили параметр через оператор *, все остальные параметры, описанные после него, могут быть переданы только по ключу.
Строго позиционные параметры и передача только по имени
Также, начиная с версии Python 3.8, мы можем определить, какие аргументы будут переданы только позиционно, какие только по ключу, а какие могут быть переданы любым способом. Данное поведение регламентируется соглашением PEP-0570 [20]. def имя_функции(позиционные, /, любые, *, по ключу): pass
Открытый учебник по информатике. Программирование на Python. Основы
63
Обращение к функциям
Помимо обычного вызова функции в коде программы в месте, где требуется исполнить описанный в ней алгоритм. Существуют еще несколько распространенных методов.
Лямбда-выражения
Лямбда-выражение – это функция, которая объявлена в месте, где она должна быть вызвана. В языке Python написать лямбда-выражение весьма затруднительно из-за синтаксических особенностей его объявления. Однако, сказать, что лямбда-выражение не является функцией, нельзя, так как для него создается объект PyFunctionObject.
Примерно такое же суждение можно найти на страницах документации:
«Small anonymous functions can be created with the lambda keyword. Lambda
functions can be used wherever function objects are required. They are syntactically
restricted to a single expression.»
«Небольшие безымянные функции могут быть созданы с помощью ключевого слова lambda. Лямбда-функции можно использовать везде, где требуются объекты-функции. Синтаксически они ограничены одним выражением.»
Синтаксис объявления лямбда-выражения. lambda набор_параметров: выражение
Данное выражение возвращает объект, соответствующий функции c параметрами par_list, которая вычисляет значение выражения expression.
Лямбда-выражения очень удобно использовать с функциями, обрабатывающими итераторы. Например, вместе с map.
Пример 1. Возвести все элементы списка в квадрат. nums = [-10, 5, 11, -53] sq = list(map(lambda x: x **2, nums))
# [100, 25, 121, 2809]
Помимо уже известных нам операций мы видим на месте первого аргумента функции map не известную нам функцию, а лямбда- выражение. То есть такой код преобразует все элементы списка nums по заданному выражению и сохранит результат в sq.
Открытый учебник по информатике. Программирование на Python. Основы
64
Работа с несколькими итераторами и map
Развернем информацию о том, как работает функция map с несколькими итерируемыми объектами.
Функция map параллельно достает по одному элемента из каждого итератора и подает полученные значения на вход функции, указанной в качестве первого аргумента.
Предположим, что у нас есть 3 итератора, которые возвращают в результате своей работы разное количество значений (см.рис.5).
Рисунок 5. Пример работы функции map с несколькими итераторами
Пример. Найти сумму произведений пар смежных элементов списка. list_ex = [2, 1, 5, 4, 8, 6]
Для списка list_ex нам необходимо вычислить сумму произведений
2 ∙ 1, 1 ∙ 5, 5 ∙ 4, 4 ∙ 8, 8 ∙ 6
На этом примере рассмотрим использование функции map для нескольких итерируемых объектов.
# находим все пары pairs = map(lambda a, b: (a, b), list_ex, list_ex[1:])
# находим все произведения prods = map(lambda p: p[0]*p[1], pairs)
# выводим максимум print(max(prods))
Важно помнить, что на вход функции func передается количество аргументов, равное количеству переданных map итерируемых объектов.
Открытый учебник по информатике. Программирование на Python. Основы
65
Задача.
Строка состоит не более чем из 10 6
символов и содержит только заглавные буквы латинского алфавита E, G, K. Определите максимальное количество идущих подряд символов, среди которых сочетания символов
KEGE повторяются не более двух раз [21].
Решение.
Для строки EGK
1 2 3 4 5 6 7 8
KEGEGKGEKEGEHKEGKEGEKKKKEGE, например, мы можем взять подстроки
EGKKEGEGKGEKEGEHKEG
или
EGEGKGEKEGEHKEGKEGEKKKKEG. Или другими словами три подстроки, разделенные подстроками KEGE и ограниченные символами EGE и KEG, если они находятся в середине строки.
Поэтому разобьем строку по разделителю KEGE и узнаем длины всех таких подстрок. lens = list(map(len, s.split('KEGE')))
Для примера выше получим: [3, 4, 4, 3, 0]
Теперь нам нужно найти максимальную сумму трех подряд идущих слов и прибавить к ней 8 (2 строки KEGE) и 6 (EGE в начале и KEG в конце).
Так как у первого слова слева и у последнего слова справа нет комбинаций KEGE уменьшим их длину на 3, чтобы наше рассуждение работало для всех последовательных троек. lens[0] -= 3 lens[-1] -= 3
# lens = [0, 4, 4, 3, -3]
# [8 (0, 4, 4), 11 (4, 4, 3), 4 (4, 3, -3)] для примера print(max(map(lambda a,b,c: sum(a,b,c), lens, lens[1:], lens[2:])) + 14)
Передача функции в качестве аргумента
Ранее в качестве аргумента при вызове, например функции map мы передавали известные нам функции (len, max, sum) или лямбда-выражения. На самом деле в качестве такого аргумента мы можем передавать и имена функций, которые написали сами.
Например, нам надо посчитать сколько налогов нужно будет платить с заработанной суммы в мифической стране Х.
Мы знаем, что если человек заработал меньше 20000, то налог с него не удерживается, до 50000 – 4%, до 100000 – 6%, больше 100000 – 13%.
Открытый учебник по информатике. Программирование на Python. Основы
66
Да, мы можем написать лямбда выражение с вложенными тернарными операциями, но тогда код будет громоздким. Поэтому вынесем определение налога в отдельную функцию ploti_nologi(). def ploti_nologi(money): if money < 20000: return 0 elif money <= 50000: return money*0.04 elif money < 100000: return money*0.06 else: return money*0.13
Теперь, имея список с заработками граждан страны Х, мы можем посчитать сумму налогов которые они заплатят. profits = [15000, 54000, 22000, 143000, 20000] print(sum(map(ploti_nologi, profits)))
Рекурсивная функция
Отдельная разновидность функций – рекурсивные функции. Рекурсивной функцией называется такая функция, которая в ходе своего исполнения вызывает сама себя, возможно, через другую функцию. Чтобы рекурсивная функция не вызывала сама себя бесконечно, определяют либо условие выхода из рекурсии, либо условие продолжения рекурсии.
Традиционный пример – вычисление факториала N!
Через условие продолжения рекурсии. def factorial(n): if n > 0: return n*factorial(n-1) return 1
Через условие выхода из рекурсии. def factorial(n): if n == 0: return 1 return n*factorial(n-1)
Рекурсивные функции иногда весьма изящно описывают применяемый алгоритм, однако нужно проявлять некоторую аккуратность в их использовании.
Определено это тем, что рекурсия работает вглубь и углубляется до тех пор, пока либо выполняется условие продолжения рекурсии, либо не будет соблюдаться условие выхода из рекурсии.
Открытый учебник по информатике. Программирование на Python. Основы
67
Рассмотрим эту особенность на еще одном традиционном примере – вычисление чисел Фибоначчи. Напомню, что числа Фибоначчи – это ряд чисел, начинающийся с двух единим и продолжающийся суммой предыдущих двух чисел – 1, 1, 2 (1+1), 3 (1+2), 5 (2+3), 8 (3+5), 13 (5+8), …
Рекурсивно N-число последовательности можно получить с помощью следующей функции на Python. def fibonacci(N): if N < 3: return 1 return fibonacci(N-1) + fibonacci(N-2)
Разберемся, как работает эта функция на примере вызова fibonacci(5). fibonacci(5): if 5 < 3: # не выполняется return 1 return fibonacci(4): if 4 < 3: # не выполняется return 1 return fibonacci(3): if 3 < 3: return 1 return fibonacci(2): if 2 < 3: return 1
+ fibonacci(1): if 1 < 3: return 1
+ fibonacci(2): if 2 < 3: return 1
+ fibonacci(3): if 3 < 3: return 1 return fibonacci(2): if 2 < 3: return 1
+ fibonacci(1): if 1 < 3: return 1 1
1 2
1 3
1 1
2 5
Открытый учебник по информатике. Программирование на Python. Основы
68
Оставим только последовательность вызовов функции: fib(5) →
(fib(4) →
(fib(3) →
(fib(2) → fib(1))
) → fib(2)
) →
(fib(3) →
(fib(2) → fib(1))
)
Из последовательности можно сделать важные наблюдения.
Во-первых, каждое значение всегда считается заново. Во-вторых, любой вызов функции сначала доходит до точки выхода и только затем отдает управление обратно.
Второе наблюдение – вызов рекурсивной функции порождает цепочку вызовов этой же функции – стек вызовов. Это важно понимать, потому что глубина рекурсии в Python ограничена настройками среды. По умолчанию нельзя накапливать стек длиной больше 1000. Это можно избежать, если изменить системную переменную, ограничивающую глубину рекурсии.
Делается это с помощью функции setrecursionlimit из библиотеки sys. import sys sys.setrecursionlimit(10000)
Однако, стоит помнить, что стоит избегать применения рекурсивных функций там, где их можно заменить более простым алгоритмом, например, циклическим. Или, как минимум, постараться реализовать рекурсивный алгоритм таким образом, чтобы для одних и тех же значений функция не вычисляла результат несколько раз.
Например, можно использовать следующую рекурсивную функцию, которая запоминает все вычисленные значения. def fibonacci(N, fib_numbers=[1, 1]): if N <= len(fib_numbers): return fib_numbers[N-1] fib_numbers.append(fibonacci(N-1) + fibonacci(N-2)) return fib_numbers[-1]
Данная функция имеет список вычисленных значений, к которому мы можем обратиться по имени параметра. Так как данный список инициализируется один раз при объявлении функции, изменяя его, мы можем расширять набор вычисленных значений. Также за счет природы выполнения рекурсивных