ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 04.12.2023
Просмотров: 136
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
40
Рис. 52. Пример использования локальной и глобальной переменных
Сначала произойдет вызов функции f, в которой будет создана локальная переменная a со значением 1 (получить доступ к глобальной переменной a из функции теперь нельзя). Затем функция закончит свою работу и будет выведена глобальная переменная a, со значением которой ничего не случилось.
Переменная считается локальной даже в случае, если её присваивание происходило внутри условного оператора (даже если он никогда не выполнится) (рис. 53).
Рис. 53. Пример использования локальной переменной
Эта программа завершится с ошибкой builtins.UnboundLocalError: local variable 'a' referenced before assignment (обращение к переменной до инициализации). Любое присваивание значения переменной внутри тела функции делает переменную локальной.
С помощью специальной команды global можно сделать так, что функция изменит значение глобальной переменной. Для этого нужно записать в начале функции слово global, а затем через запятую перечислить имена глобальных переменных, которые функция сможет менять. Например,
41 такой код выведет «1 1», т. к. значение глобальной переменной будет изменено внутри функции (рис. 54).
Рис. 54. Пример использования команды global
Все параметры функции являются локальными переменными со значениями, которые были переданы в функцию. Параметры также можно изменять, и это никак не повлияет на значения переменных в том месте, откуда была вызвана функция (если тип объектов-параметров был неизменяемым).
Использование глобальных переменных как на чтение, так и на запись внутри функций некорректно. Это связано с тем, что другие люди могут захотеть использовать некоторые отдельные функции из вашего кода, которые не будут работать вне вашей программы в случае использования глобальных переменных.
Поэтому использование глобальных переменных внутри функций
строго не рекомендуется. Все нужное для работы функции должно передаваться в качестве параметров.
Рекурсия
Мы уже пробовали запускать функцию из другой функции. Ничто не мешает запустить из функции саму себя. Тогда просто создастся новый экземпляр функции, который будет выполнять те же команды. Такой процесс называется рекурсией.
42
Представим себе, что у нас есть миллиард человек (это будущие экземпляры функции), сидящих в ряд, и у каждого из них есть листочек для записи (это его локальная память). Нам нужно произносить числа и написать инструкцию для людей так, чтобы они в итоге назвали все числа из последовательности в обратном порядке. Пусть каждый из них будет записывать на своем листочке только одно число. Тогда инструкция для человека будет выглядеть так:
1. Запиши названное число.
2. Если число не последнее, «потереби» следующего за тобой человека, пришла его очередь работать.
3. Когда следующий за тобой человек сказал, что он закончил, назови записанное число.
4. Скажи тому, кто тебя «теребил» (предыдущий человек), что ты закончил.
Формализуем задачу. Пусть задается последовательность натуральных чисел, заканчивающаяся нулем. Необходимо развернуть ее с помощью рекурсии (рис. 55).
Рис. 55. Пример использования рекурсии
Эта функция осуществляет действие (вывод числа) на рекурсивном спуске, т. е. после рекурсивного вызова.
Использование рекурсии
Рассмотрим задачу, где действия выполняются как на рекурсивном подъёме, так и на рекурсивном спуске. Пусть дана последовательность,
43 которая оканчивается нулём. Необходимо вывести все чётные члены последовательности в прямом порядке, а затем все нечётные члены последовательности в обратном порядке. Решение будет выглядеть так, как представлено на рис. 56.
Рис. 56. Решение задачи с использованием рекурсии
Каждый экземпляр функции считывает в свою локальную переменную n число. Если оно чётное, то сразу выводит его и запускает следующий экземпляр. После того, как все последующие экземпляры функции окончили работу (и вывели последующие нечётные числа в обратном порядке), функция выводит число, если оно было нечетным.
Рассмотрим еще одну задачу: подсчитать факториал числа, не пользуясь циклами (рис. 57).
Рис. 57. Решение задачи с использованием рекурсии
Тем, кто знаком с методом математической индукции, будет довольно просто осознать рекурсию. Как и в математической индукции, в рекурсии должны быть база (момент, когда функция не вызывает другую рекурсивную функцию) и переход (правило, по которому считается результат по
44 известному результату для меньшего параметра). Функция подсчета факториала делает только свою работу, но пользуется результатами чужого труда. Например, если функция получила на вход параметр 4, то должна вернуть 4, умноженное на 3! (факториал будет посчитан другими функциями). В случае факториала аналогом «базы индукции» может выступать 0! По определению он равен единице.
Эти примеры иллюстрируют общую схему написания рекурсивных функций: сначала проверяется условие, когда функция должна закончиться, а дальше делается все остальное. При этом параметр должен сходиться к значению базы. Обычно это означает, что при каждом следующем вызове рекурсии параметр должен уменьшаться.
1.5. Кортежи и списки данных. Цикл for для работы с элементами
коллекций
Кортежи
Наряду со строками, которые могут хранить в себе отдельные символы, в языке Питон существует тип кортеж, который позволяет хранить в себе произвольные элементы.
Кортеж может состоять из элементов произвольных типов и является неизменяемым типом, т. е. нельзя менять отдельные элементы кортежа, как и символы строки. Константные кортежи можно создавать в программе, записывая элементы через запятую и окружая скобками. Например: testTuple
= (1, 2, 3). Если кортеж является единственным выражением слева или справа от знака присваивания, то скобки могут быть опущены. Во всех остальных случаях скобки опускать не следует, это может привести к ошибкам.
Многие приемы и функции для работы со строками подходят и для кортежей. Например, можно складывать два кортежа (рис. 58).
45
Рис. 58. Пример работы с кортежами
В результате применения этой операции будет выведено (1, 2, 3, 4, 5,
6). В случае сложения создается новый кортеж, который содержит в себе элементы сначала из первого, а затем из второго кортежа (точно так же, как и в случае со строками). Кортеж можно умножить на число, результат этой операции аналогичен умножению строки на число.
Приведем пример, когда опускание скобок приводит к ошибке. (1, 2) +
(3, 4) будет давать (1, 2, 3, 4), а 1, 2 + 3, 4 будет давать (1, 5, 4), т. к. сумма будет понята Питоном как выражение для второго элемента кортежа.
Кортеж можно получить из строки, вызвав функцию tuple от строки. В результате каждая буква станет элементом кортежа. К кортежу можно применять функцию str, которая вернет текстовое представление кортежа
(элементы, перечисленные через запятую с пробелом и разделенные пробелами).
К кортежу можно применять функцию len и обращаться к элементам по индексу (в том числе по отрицательному
), так же как и к строкам.
В одном кортеже могут храниться элементы различных типов, например строки, числа и другие кортежи вперемешку. Например, в кортеже myTuple = (('a', 1, 3.14), 'abc', ((1), (2, ))), myTuple[0] будет кортежем ('a', 1,
3.14), myTuple[1] – строкой 'abc', а myTuple[2] – кортежем, состоящим из числа 1 и кортежа из одного элемента
(2, ).
Числа, записанные в скобках, интерпретируются как числа. В случае возникновения необходимости создать кортеж из одного элемента необходимо после значения элемента написать запятую. Если вывести myTuple[2][1], то напечатается (2,), а если вывести myTuple[2][1][0], то будет напечатано число 2.
46
Кортеж, содержащий в себе один элемент, называется синглтоном. Как и к строкам, к кортежам можно применять операцию среза с тем же смыслом параметров. Если в срезе один параметр, то будет возвращена ссылка на элемент с соответствующим номером. Например, print((1, 2, 3)[2]) напечатает
3. Если же в срезе более одного параметра, будет сконструирован кортеж, даже если он будет синглтоном. Например, в случае вызова print((1, 2, 3)[1:]) будет напечатано (2, 3), а в случае вызова print((1, 2, 3)[2:]) будет напечатан синглтон (3,).
Кортежи обычно предназначаются для хранения разнотиповых значений, доступ к которым может быть получен в результате обращения по индексу или с помощью операции распаковки.
Распаковкой называется процесс присваивания, в котором кортеж, составленный из отдельных переменных, находится в левой части выражения. В таком выражении справа должен находиться кортеж той же длины, например в результате выполнения кода на рис. 59.
Рис. 59. Пример работы с кортежем
В переменной name хранится «Ivan», в surname – «Ivanov», а в переменной age число 28. На английском распаковка кортежа называется tuple unpacking.
Функция range, цикл for
Процесс создания кортежа называется упаковкой кортежа. Если в одном выражении присваивания происходит и упаковка, и распаковка кортежа, то сначала выполняется упаковка, а затем распаковка кортежа. Так, в результате работы программы будет выведено «3 2 1» (рис. 60).
47
Рис. 60. Пример создания кортежа
Обратите внимание, что функции print передается в качестве параметра не кортеж, а три целых числа.
Главное, что нужно понять: запись вида (a, b, c) = (c, b, a) не эквивалентна цепочке присваиваний вида a = c; b = b; c = a. Такая цепочка присваиваний привела бы к тому, что в переменных a, b, c оказались бы значения 3, 2, 3.
Функция range
В языке Питон есть функция range, которая позволяет генерировать объекты типа iterable (к элементам которых можно получать последовательный доступ), состоящие из целых чисел.
Для вывода объектов типа iterable мы будем пользоваться функцией tuple, которая позволяет сделать кортеж, состоящий из всех элементов iterable, записанных последовательно.
Например, если запустить программу, то будет напечатано (0, 1, 2, 3, 4,
5, 6, 7, 8, 9) (рис. 61).
Рис. 61. Пример использования функции range
Функция range с одним параметром n генерирует iterable, содержащий последовательные числа от 0 до n-1.
Существует вариант range с двумя параметрами, range(from, to) сгенерирует iterable со всеми числами от from до to-1 включительно.
48
Существует range с тремя параметрами range(from, to, step), который сгенерирует iterable с числами от from, не превышающие to с шагом изменения step. Если шаг отрицателен, то from должен быть больше to.
Например, range(10, 0, –2) сгенерирует последовательность чисел 10, 8, 6, 4,
2. 0 не будет входить в эту последовательность.
Во многом параметры range напоминают значения параметров в срезах строк.
Цикл for
Цикл for позволяет поочередно перебрать элементы из чего-нибудь итерируемого (iterable или tuple). Например, мы можем перебрать названия цветов яблок (рис. 62).
Рис. 62. Пример использования цикла for
В результате выполнения этой программы будет напечатано: red apple green apple yellow apple
На место переменной color будут поочередно подставляться значения из кортежа. В общем случае цикл for выглядит так: for имяПеременной in нечтоИтерируемое:
Все действия, которые должны выполняться в for, должны выделяться отступом, как в if или while. Работа цикла for может быть прервана с помощью команды break, или может быть осуществлен переход к следующей итерации с помощью continue. Эти команды имеют тот же эффект, что и при работе с циклом while.
49
Часто for используется вместе с функцией range. Например, с помощью for можно напечатать нечетные числа от 1 до 100 (рис. 63).
Рис. 63. Пример использования цикла for
Внутри for может быть расположен и другой for. На рис. 64 показано, как выглядит код для вывода таблицы умножения всех чисел от 1 до 10 (не очень красивой).
Рис. 64. Пример использования вложенных циклов for
Как можно заметить, при использовании функции range в for мы не преобразовывали iterable в tuple. Это связано с тем, что for как раз хочет получать последовательный доступ, который умеет давать iterable. Tuple умеет намного больше, но здесь его использование приведет к ненужным затратам времени и памяти.
Списки
Список в Питоне является аналогом массивов в других языках программирования. Список – это набор ссылок на объекты (так же, как и кортеж), однако он является изменяемым.
Константные списки записываются в квадратных скобках, все остальное в них аналогично кортежам. Например, можно создать список с числами от 1 до 5: myList = [1, 2, 3, 4, 5].
50
Списки и кортежи легко преобразуются друг в друга. Для преобразования списка в кортеж надо использовать уже известную нам функцию tuple, а для преобразования кортежа в список нужна функция list.
Также функцию list можно применить к строке. В результате этого получится список, каждым элементом которого будет буква из строки. Так list('abc') будет выглядеть как ['a', 'b', 'c'].
К спискам также применима функция len и срезы, которые работают так же, как в кортежах.
Главным отличием списка от кортежа является изменяемость. То есть можно взять определенный элемент списка и изменить его (он может быть в левой части операции присваивания).
Например, в результате выполнения кода на рис. 65 будет напечатано
[1, 4, 3].
Рис. 65. Пример работы со списками
Изменение символа (или элемента в кортеже) можно было реализовать, сделав два среза и конкатенацию первой части строки, нового символа и
«хвоста» строки. Это очень медленная операция, время ее выполнения пропорционально длине строки. Замена элемента в списке осуществляется за
O(1)
, т. е. не зависит от длины списка.
Изменение списков
Список, как и другие типы в языке Питон, является ссылкой на список ссылок. При этом список является изменяемым объектом, т. е. содержимое по этой ссылке может поменяться. На рис. 66 приведен пример.
51
Рис. 66. Пример работы со списками
В результате выполнения этой программы будет напечатано [3, 2]. Это связано с тем, что присваивание в Питоне – это просто «привязывание» нового «ярлычка» к объекту. После присваивания b = a обе ссылки начинают показывать на один и тот же объект, и если он изменен по одной ссылке, то по второй ссылке он также будет доступен в измененном состоянии.
Если же написать другой код (рис. 67), то будет выведено [1, 2].
Рис. 67. Пример работы со списками
Несмотря на то, что объекты имеют одинаковое значение из-за их мутабельности (изменяемости), для каждого значения будет создан отдельный объект, и ссылки a и b будут показывать на разные объекты.
Изменение одного из них не приводит к изменению другого.
В результате выполнения кода, представленного на рис. 68, будет выведено [1, 2].
Рис. 68. Пример работы со списками
52
Сначала в памяти создается объект [1, 2], к нему привязывается ссылка a. Затем к тому же объекту привязывается ссылка b, и создается новый объект [3, 4], к которому привязывается ссылка a (отвязавшись от своего предыдущего значения). При этом ссылка b не изменилась (она может измениться, только если b будет участвовать в левой части присваивания) и по-прежнему показывает на [1, 2].
Если списки переданы в функцию в качестве параметров, то их содержимое также может быть изменено этой функцией (рис. 69).
Рис. 69. Пример работы со списками
Вывод этой программы будет ['x', 'b', 'c', 'd', 'e', 'f'].
Однако сама ссылка внутри функции не может быть изменена, если она передана как параметр функции. Рассмотрим пример на рис. 70.
Рис. 70. Пример работы со списками
Эта программа не развернет список, т. е. вывод будет ['a', 'b', 'c'].
Здесь в основной программе конструируется объект ['a', 'b', 'c'], и к нему привязывается ссылка mainList. При передаче mainList в качестве параметра в функцию создастся еще одна ссылка funcList, показывающая на объект ['a',
'b', 'c']. В результате применения среза создастся новый объект ['c', 'b', 'a'], и