Подготовка к ЕГЭ. Программирование на Python. Основы. Учебник по информатике. Программирование на Python. Основы 1 Программирование на Python. Основы
Скачать 1.49 Mb.
|
Чтение из файла open(filename, mode) Создание объекта для чтения текстового файла с путём filename. mode выставляется в зависимости от режима работы с файлом. По умолчанию файл открывается для чтения. file_obj = open(filename) Возвращаемый в file_obj объект так же является итератором, то есть ссылается на место в файле, на котором была завершена последняя операция считывания данных. При последовательном обращении к итератору через next() возвращаются строки файла. next(file_obj) эквивалентно вызову file_obj.readline() (см.далее). Пример – открытие файла для чтения. file = open('27-A.txt') f.read(count) Считывание следующих count символов в текстовом файле. По умолчанию считывается весь файл. Пример – файл test.txt содержит строку 'abcdefg'. file = open('test.txt') s = file.read(3) # 'abc' s = file.read(2) # 'de' s = file.read(5) # 'f' s = file.read(5) # '' f.readline() Чтение следующей несчитанной строки до переноса строки. Если вызвать метод после считывания всего файла, будет возвращена пустая строка. ВАЖНО: метод readline() возвращает строку с последним символом \n, кроме случая, когда это последняя строка. f.readlines() Чтение всех несчитанных строк, разделенных переносом строки. ВАЖНО: каждая строка в полученном списке также, как в случае с методом readline(), оканчивается сносом строки. Открытый учебник по информатике. Программирование на Python. Основы 52 f.close() Удаление объекта для работы с файлом из памяти. Данное действие закрывает файл. Тем самым позволяет открыть этот файл другим потоком. Особенно актуально закрывать файл, открытый для дозаписи. with open(filename, flag) as f: # обработка файла # файл закрыт без f.close() «Безопасный» с точки зрения работы с памятью метод чтения информации из файла. После выхода из блока команд для with объекта для работы с файлом не будет. И файл закроется автоматически. При написании простых программ, особенно в рамках ЕГЭ, нет значительной разницы, каким образом вы работаете с файлом – удаляете ли объект после обработки всего файла или нет. После завершения работы скрипта все объекты в памяти все равно перестанут существовать. Проблемы незакрытых файлов: • утечка памяти для хранения объектов, • блокировка текущей программой файла, например, если он открыт для записи, • потеря данных в случае, если программа аварийно завершилась. Поэтому очень важно всегда контролировать закрыт ли обработанный файл. Также лучше использовать функции для последовательного считывания данных. Тогда при обработке файлов большого размера не будет возникать ошибок, связанных с переполнением памяти. Так, следующие два примера хоть и делают одно и тоже, однако вариант, где мы используем метод readlines требует больше памяти и при работе с большими файлами потребует эту самую память на первоначальное считывание всех строк файла. Не нужно выделять память на все строки из файла Нужно выделять память на все строки из файла with open('test.txt') as f: data = list(map(int, f)) with open('test.txt') as f: data=list(map(int, f.readlines())) Открытый учебник по информатике. Программирование на Python. Основы 53 Функции Что такое функции При создании сложных алгоритмов зачастую приходится иметь дело с одинаковыми участками кода, которые реализуют один и тот же служебный алгоритм. Такие участки кода принято оформлять, как функции. Функцию можно рассматривать, как именованный участок кода, который вызывается для исполнения закодированного в нем алгоритма. Также существуют анонимные (неименованные) функции, о них поговорим ниже. Например, если в ходе нашего алгоритма необходимо при выводе в нескольких местах форматировать строку в формате "HH:MM:SS", то мы можем описать функцию format_time_to_str(h, m, s) и вместо описания условий форматирования каждый раз вызывать описанную функцию, которая вернет нужную строку. Такой подход локализовать ошибки, ведь при описании функции мы описываем алгоритм один раз и, в случае обнаружения ошибок его работы, исправляем ошибки в одном месте нашей программы, а не везде, где этот алгоритм был необходим. Функции в Python можно условно разделить на те, которые возвращают значение, и те, которые его не возвращают. Первые применяются для получения результатов обработки значение, вторые для выполнения отдельных действий. Примеры функций, которые не возвращают значение. • Записать в файл журнала сообщение, • Отправить сообщение в чате, • Показать справку по команде. Примеры функций, которые возвращают значение. • Найти корни квадратного уравнения, • Найти количество путей на графе по матрице смежности, • Найти произведение матриц. Открытый учебник по информатике. Программирование на Python. Основы 54 Функция, как и что угодно в Python, является объектом – PyFunctionObject. Условно описание функции можно разделить на следующие составляющие: • имя функции, • параметры, принимаемые на вход, • исполняемый код, • аннотация обрабатываемых и возвращаемых значений, • документация функции. Правила именования функций – неразрывная строка из латинских букв в верхнем и нижнем регистрах и символов нижнего подчеркивания. Традиционно имена функций, как и имена переменных, записываются в «змеином» стиле – строка в нижнем регистре, слова в которой разделены знаком подчеркивания. Пример описания функции: def func_name(x, y, z): # блок команд У такой функции имя func_name, и она принимает на вход 3 значения, для которых заданы имена x, y, z. Возвращение значений Для возвращения значения, являющимся результатом работы функции, необходимо использовать оператор return. def simple_function(): return 2 + 2 ВАЖНО: после выполнения оператор return возвращает управление тому блоку команд, в котором была вызвана функция. То есть код, записанный после оператора return выполняться не будет. def simple_function(): return 2 + 2 # этот код выполняться не будет x = 5*10 Функция может не возвращать значение. Тогда результатом выполнения функции будет объект None. def print_10(): print(10) x = print_10() # None Открытый учебник по информатике. Программирование на Python. Основы 55 Параметры Так как выполнение одного и того же алгоритма, который обрабатывает одинаковые значения, редко бывает полезным. Обычно гораздо проще найти значение один раз и использовать вычисленное значение далее. Например, нет смысла постоянно находить результат вычисления значения выражения 3x 2 +10x-20 для x = 1. Гораздо полезнее, при частом обращении к данному выражению, описать функцию f(x), которая будет вычислять значение выражения для любого числового значения x. Пример объявления функции для вычисления выражения 3x 2 +10x-20. def calc_expression(x): return 2*x**2 + 10*x – 20 print(calc_expression(5)) # 80 Стоит отметить, что есть еще один термин – аргументы. Аргументы – это значения, передаваемые на вход функции. Так, в предыдущем примере, аргументом является значение 5. Также аргументы иногда называют фактическими параметрами. Значения параметров по умолчанию Также мы можем определить значения, которые будут приняты функцией, если мы не зададим их значение. Например, мы можем определить функцию для нахождения суммы цифр в десятичной записи числа, представленного в виде строки s в системе счисления с основанием base. По умолчанию будем считать, что основание системы счисления равно 10. def sum_digit(s, base=10): str_base10 = s if base != 10: x = int(s, base) str_base10 = str(x) return sum(map(int,str_base10)) Все параметры, значения которых задаются по умолчанию, описываются последними. При этом, если количество передаваемых функции значений меньше определенного для нее количества параметров, то по умолчанию берутся параметры, начиная от последнего описанного. Открытый учебник по информатике. Программирование на Python. Основы 56 Например, если имеем такое объявление функции def test(a, b, c=10, d=15): pass # оператор пустого блока команд В случае передачи трех значений в качестве аргументов функции значение d будет принято по умолчанию. Если же мы передадим такой функции меньше двух значений, то интерпретатор вернет ошибку, так как все параметры должны быть определены. При вызове функции можно задавать значение параметров по имени. Например, для функции test() можно сделать так test(b = 10, a = 5, d = 11) Однако обычно значения параметров без значений по умолчанию указывают в том порядке, в котором они заданы при объявлении функции. В том числе поэтому их называют позиционными. Запрещается описывать параметры со значением по умолчанию перед описанием параметров без таковых. Пример, как нельзя описывать параметры функции. def bad_example(a, b = 10, c): pass ВАЖНО: объекты, на которые будут ссылаться имена аргументов, создаются ОДИН РАЗ при инициализации функции. Это очень важно учитывать, если в качестве значения по умолчанию мы хотим определить значение изменяемого типа (например, список). Пример. def append_to_list(x, ext_list = []): ext_list.append(x) return ext_list Казалось бы, при передаче единственного аргумента мы будем получать список, содержащий переданное значение. Однако при вызове функции с одним значением изменяется объект, на который ссылается параметр ext_list. Убедиться в изменении значений по умолчанию можно вызвав служебный метод __defaults__ объекта функции или проанализировав возвращаемое функцией значение. print(append_to_list.__defaults__) # ([], ) append_to_list(4) append_to_list(6) print(append_to_list.__defaults__) # ([4, 6, 8],) Открытый учебник по информатике. Программирование на Python. Основы 57 Аннотация Аннотация типов данных в Python [15] является больше средством для удобства разработки, чем указанием интерпретатору, какие данные будут сохранены в переменных или переданы в качестве аргументов при вызове функции. На сегодня существуют анализаторы аннотированного кода, например [16], которые указывают на несоответствие присваиваемого значения переменной типу, указанному в аннотации. Однако при запуске программы Python не проверит данные нюансы и вернет ошибку только на этапе применения не существующих операций для типа данных. Значения переменных и параметров аннотируются следующим образом: variable_or_parameter: expression Строго говоря, можно указывать не только тип, но и описание переменной. Однако в основном аннотацию используют именно для указание ожидаемого типа данных для значения переменной или параметра. Чтобы описать возвращаемый функцией результат, используется запись: def func_name() -> expression Зачем же аннотировать ожидаемые значения, если Python все равно их проигнорирует при исполнении программы? Такой подход повышает удобство разработки, так как большинство IDE поддерживают, например, вызов списка методов для переменных с определенным типом. Рисунок 1. Пример подсказки для результата неаннотированной и анотированной функций Открытый учебник по информатике. Программирование на Python. Основы 58 Также мы можем указать один из ожидаемых типов с помощью типа объединения Union из библиотеки typing. def square(x: Union[int, float]) -> Union[int, float]: return x*x С версии 3.10 объедининие типов может быть осуществленно через оператор |. def square(x: int | float) -> int | float: return x*x Для этого нет необходимости подключать отдельный модуль. Документирование Документирование функций, как и аннотация, позволяет быстрее понимать, как работать с функцией. Если аннотация, в основном, применима для описания типов обрабатываемых значений, то документирование – это описательная часть, которая раскрывает зачем нужна функция. Документирование функций в Python происходит согласно docstring-соглашению [17]. Для этого используется формат описания в виде разметки reStructuredText [18], рекомендованный PEP-287 [19]. Описание работы функции записывается после её объявления в тройных кавычках. def test(): ''' My first doc ''' pass Теперь IDE, обрабатывающий документированные функции, при наведении на функцию будут показывать написанный текст. Рисунок 2. Пример всплывающего окна с текстом документации Открытый учебник по информатике. Программирование на 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 |