Программирование на Python 3. Руководство издательство СимволПлюс
![]()
|
isin stance() , стр. 284 202 Глава 4. Управляющие структуры и функции finally: if fh is not None: fh.close() Во внешнем блоке try мы использовали отдельные блоки except, пото му что в каждом конкретном случае обработка выполняется поразно му. Если было получено исключение синтаксического анализа, мы знаем, что соответствующее сообщение уже было выведено и нам нуж но лишь прервать работу с эти файлом и перейти к следующему, поэто му нам ничего не требуется делать в обработчике исключений. Если было получено исключение EOFError, это может быть результат дейст вительно преждевременного достижения конца файла либо повторно го его возбуждения. В любом случае мы выводим сообщение и текст исключения. Если возникло исключение EnvironmentError (то есть если возникло исключение IOError или OSError), мы просто выводим сообще ние исключения. В заключение, независимо от того, что произошло, если файл оказался открытым, мы закрываем его. Собственные функции Функции представляют собой средство, дающее возможность упако вывать и параметризовать функциональность. В языке Python можно создать четыре типа функций: глобальные функции, локальные функ ции, лямбдафункции и методы. Все функции, которые мы создавали до сих пор, являются глобальны ми функциями. Глобальные объекты (включая функции) доступны из любой точки программного кода в том же модуле (то есть в том же са мом файле .py), которому принадлежит объект. Глобальные объекты доступны также и из других модулей, как будет показано в следующей главе. Локальные функции (их еще называют вложенными функциями) – это функции, которые объявляются внутри других функций. Эти функции видимы только внутри тех функций, где они были объявле ны – они особенно удобны для создания небольших вспомогательных функций, которые нигде больше не используются. Мы познакомимся с ними в главе 7. Лямбда функции – это выражения, поэтому они могут создаваться не посредственно в месте их использования; они имеют множество огра ничений по сравнению с обычными функциями. Методы – это те же функции, которые ассоциированы с определенным типом данных и могут использоваться только в связке с этим типом данных; методы будут представлены в главе 6, когда будут рассматри ваться вопросы объектноориентированного программирования. В языке Python имеется множество встроенных функций, а стандарт ная библиотека и библиотеки сторонних разработчиков добавляют Собственные функции 203 еще сотни (тысячи, если посчитать еще и методы) поэтому большинст во функций, которые нам могут потребоваться, уже написаны. По этой причине всегда стоит обращаться к электронной документации, чтобы увидеть, какие функции доступны. Смотрите врезку «Электрон ная документация». Электронная документация В этой книге дается полный охват языка Python 3, встроенных функций и наиболее часто используемых модулей из стандарт ной библиотеки, тем не менее в электронной документации мож но найти значительный объем справочной информации о языке Python и особенно об обширнейшей стандартной библиотеке. Электронная документация доступна на сайте docs.python.org, а также поставляется в составе самого интерпретатора Python. Для операционной системы Windows документация поставляет ся в формате справочных файлов Windows. Выберите пункт ме ню Пуск →Все программы→Python 3.x→Python Manuals (Start→All Pro grams → Python 3.x →Python Manuals), чтобы запустить средство про смотра справочных файлов Windows. Этот инструмент обладает функциями индексирования и поиска, которые упрощают воз можность поиска по документу. Пользователи операционной системы UNIX получают документацию в формате HTML. В до полнение к различным гиперссылкам в ней содержатся различ ные страницы с предметными указателями. Кроме того, в левой части каждой страницы присутствует очень удобная функция «Quick Search». Наиболее часто начинающими пользователями используется до кумент «Library Reference», а опытными пользователями – до кумент «Global Module Index». Оба документа содержат ссылки, ведущие на страницы с описанием всей стандартной библиотеки Python, а, кроме того, документ «Library Reference» содержит ссылки на страницы с описанием всех встроенных функцио нальных возможностей языка Python. Определенно имеет смысл ознакомиться с документацией, осо бенно с документами «Library Reference» и «Global Module In dex», чтобы получить представление о том, что может предло жить стандартная библиотека, и пощелкать мышью на темах, ко торые вас заинтересуют. Это даст вам первое впечатление о том, что доступно, и поможет запомнить, где можно отыскать доку ментацию, которая будет представлять для вас интерес. (Крат кое описание стандартной библиотеки языка Python приводится в главе 5.) 204 Глава 4. Управляющие структуры и функции Синтаксис создания функции (глобальной или локальной) имеет сле дующий вид: def functionName(parameters): suite Параметры parameters являются необязательными и при наличии бо лее одного параметра записываются как последовательность иденти фикаторов через запятую или в виде последовательности пар identifi er=value , о чем вскоре будет говориться подробнее. Например, ниже приводится функция, которая вычисляет площадь треугольника по формуле Герона: def heron(a, b, c): s = (a + b + c) / 2 return math.sqrt(s * (s a) * (s b) * (s c)) Внутри функции каждый параметр, a, b и c, инициализируется соот ветствующими значениями, переданными в виде аргументов. При вы зове функции мы должны указать все аргументы, например, heron(3, 4, 5). Если передать слишком мало или слишком много аргументов, будет возбуждено исключение TypeError. Производя такой вызов, мы говорим, что используем позиционные аргументы, потому что каж дый переданный аргумент становится значением параметра в соответ Кроме того, в интерпретаторе также имеется справочная систе ма. Если вызвать встроенную функцию help() без аргументов, вы попадете в электронную справочную систему – чтобы получить в ней нужную информацию, просто следуйте инструкциям, а что бы вернуться в интерпретатор – введите символ «q» или команду «quit». Если вы знаете, описание какого модуля или типа данных хотите получить, можно вызвать функцию help(), передав ей имя модуля или типа в виде аргумента. Например, выполнив инст рукцию help(str), вы получите информацию о типе данных str, включая описания всех его методов; инструкция help(dict.up date) выведет информацию о методе update() типа данных dict; а инструкция help(os) отобразит информацию о модуле os (если перед этим он был импортирован). Если вы уже знакомы с языком Python, то часто бывает доста точно просто просмотреть, какие атрибуты (например, методы) имеет тот или иной тип данных. Эту информацию можно полу чить с помощью функции dir(), например, вызов dir(str) пере числит все методы строк, а вызов dir(os) перечислит все кон станты и функции модуля os (опять же при условии, что модуль был предварительно импортирован). Собственные функции 205 ствующей позиции. То есть в данном случае при вызове функции пара метр a получит значение 3, параметр b – значение 4 и параметр с – зна чение 5. Все функции в языке Python возвращают какоелибо значение, хотя вполне возможно (и часто так и делается) просто игнорировать это зна чение. Возвращаемое значение может быть единственным значением или кортежем значений, а сами значения могут быть коллекциями, поэтому практически не существует никаких ограничений на то, что могут возвращать функции. Мы можем покинуть функцию в любой момент, используя инструкцию return. Если инструкция return ис пользуется без аргументов или если мы вообще не используем инст рукцию return, функция будет возвращать значение None. (В главе 6 мы рассмотрим инструкцию yield, которая в функциях определенного ти па может использоваться вместо инструкции return.) Некоторые функции имеют параметры, для которых может существо вать вполне разумное значение по умолчанию. Например, ниже при водится функция, которая подсчитывает количество алфавитных сим волов в строке; по умолчанию подразумеваются алфавитные символы из набора ASCII: def letter_count(text, letters=string.ascii_letters): letters = frozenset(letters) count = 0 for char in text: if char in letters: count += 1 return count Здесь при помощи синтаксиса parameter=default было определено зна чение по умолчанию для параметра letters. Это позволяет вызывать функцию letter_count() с единственным аргументом, например, let ter_count("Maggie and Hopey") . В этом случае внутри функции параметр letter будет содержать строку, которая была задана как значение по умолчанию. Но за нами сохраняется возможность изменить значение по умолчанию, например, указав дополнительный позиционный аргу мент: letter_count("Maggie and Hopey", "aeiouAEIOU"), или используя именованный аргумент (об именованных аргументах рассказывается ниже): letter_count("Maggie and Hopey", letters="aeiouAEIOU"). Синтаксис параметров не позволяет указывать параметры, не имею щие значений по умолчанию, после параметров со значениями по умолчанию, поэтому такое определение: def bad(a, b=1, c):, будет вы зывать синтаксическую ошибку. С другой стороны, мы не обязаны пе редавать аргументы в том порядке, в каком они указаны в определе нии функции – мы можем использовать именованные аргументы и пе редавать их в виде name=value. Ниже демонстрируется короткая функция, возвращающая заданную строку, если ее длина меньше или равна заданной длине, и усеченную 206 Глава 4. Управляющие структуры и функции версию строки с добавлением в конец значения параметра indicator – в противном случае: def shorten(text, length=25, indicator="..."): if len(text) > length: text = text[:length len(indicator)] + indicator return text Вот несколько примеров вызова этой функции: shorten("The Road") # вернет: 'The Road' shorten(length=7, text="The Road") # вернет: 'The ...' shorten("The Road", indicator="&", length=7) # вернет: 'The Ro&' shorten("The Road", 7, "&") # вернет: 'The Ro&' Поскольку оба параметра, length и indicator, имеют значение по умол чанию, любой из них или даже оба сразу могут быть опущены, тогда будут использоваться значения по умолчанию – этот случай соответст вует первому вызову. Во втором вызове оба аргумента являются име нованными, поэтому их можно указывать в любом порядке. В третьем вызове используются позиционный аргумент и именованные аргумен ты. Первым указан позиционный аргумент (позиционные аргументы всегда должны предшествовать именованным аргументам), а за ним следуют два именованных аргумента. В четвертом вызове все аргумен ты позиционные. Различие между обязательным и необязательным пара метром заключается в наличии значения по умолчанию, то есть параметр со значением по умолчанию является необязательным (интерпретатор может использовать значение по умолчанию), а параметр без значения по умолчанию является обязательным (интерпретатор не может делать никаких предположений). Осторожное ис пользование значений по умолчанию может упростить программный код и сделать вызовы функций более по нятными. Вспомните, что функция open() имеет один обязательный аргумент (имя файла) и шесть необяза тельных аргументов. Используя смесь из позиционных и именованных аргументов, мы можем указывать толь ко необходимые аргументы, опуская другие. Это дает нам возможность записать такой вызов: open(filename, encoding="utf8") , вместо того чтобы указывать все аргу менты, например: open(filename, "r", None, "utf8", None, None, True) . Еще одно преимущество использования име нованных аргументов состоит в том, что они способны сделать вызов функции более удобочитаемым, особенно в случае использования логических аргументов. Значения по умолчанию создаются на этапе выполнения инст рукции def (то есть в момент создания функции), а не в момент Врезка «Чте ние и запись текстовых файлов», стр. 157 Собственные функции 207 ее вызова. Для неизменяемых аргументов, таких как строки или чис ла, это не имеет никакого значения, но в использовании изменяемых аргументов кроется труднозаметная ловушка. def append_if_even(x, lst=[]): # ОШИБКА! if x % 2 == 0: lst.append(x) return lst В момент создания этой функции параметр lst ссылается на новый список. Всякий раз, когда эта функция вызывается с одним первым параметром, параметр lst будет ссылаться на список, созданный как значение по умолчанию вместе с функцией – то есть при каждом таком вызове новый список создаваться не будет. Как правило, это не совсем то, что нам хотелось бы – мы ожидаем, что каждый раз, когда функ ция вызывается без второго аргумента, будет создаваться новый пус той список. Ниже приводится новая версия функции, на этот раз ис пользующая правильный подход к работе с изменяемыми аргумента ми, имеющими значения по умолчанию: def append_if_even(x, lst=None): if lst is None: lst = [] if x % 2 == 0: lst.append(x) return lst Здесь, всякий раз, когда функция вызывается без второго аргумента, мы создаем новый список. А если аргумент lst определен, использует ся он, как и в предыдущей версии функции. Такой прием, основанный на использовании значения по умолчанию None и создании нового объ екта, должен применяться к словарям, спискам, множествам и любым другим изменяемым типам данных, которые предполагается исполь зовать в виде аргументов со значениями по умолчанию. Ниже приво дится немного более короткая версия функции, которая обладает тем же поведением: def append_if_even(x, lst=None): lst = [] if lst is None else lst if x % 2 == 0: lst.append(x) return lst Использование условного выражения позволяет сократить размер функции на одну строку для каждого параметра, имеющего изменяе мое значение по умолчанию. Имена и строки документирования Использование осмысленных имен для функций и их параметров помо гает понимать назначение функции другим программистам, а также, 208 Глава 4. Управляющие структуры и функции спустя некоторое время после создания функции, и самому автору функции. Ниже приводятся несколько основных правил, которых мы рекомендуем придерживаться. • Используйте единую схему именования и придерживайтесь ее не уклонно. В этой книге имена ИМЕНА КОНСТАНТ записываются символа ми в верхнем регистре; имена Классов (и исключений) записываются символами верхнего и нижнего регистра, причем каждое слово в имени начинается с символа верхнего регистра; похожим образом записываются имена Функций и методов графического интерфейса, за исключением первого символа, который всегда записывается в ниж нем регистре; а все остальные имена записываются только символа ми нижнего регистра или символами_нижнего_регистра_с_символом_под черкивания • Избегайте использовать аббревиатуры в любых именах, если эти аб бревиатуры не являются стандартными и не получили широкого распространения. • Соблюдайте разумный подход при выборе имен для переменных и параметров: имя x прекрасно подходит для координаты x, а имя i отлично подходит на роль переменной цикла, но вообще имена должны быть достаточно длинными и описательными. Имя должно описывать скорее назначение элемента данных, чем его тип (напри мер, имя amount_due предпочтительнее, чем имя money), если только имя не является универсальным для конкретного типа данных, на пример, имя параметра text в функции shorten() (стр. 209). • Имена функций и методов должны говорить о том, что они делают или что они возвращают (в зависимости от их назначения), и нико гда – как они это делают, потому что эта характеристика может из мениться со временем. Ниже приводятся несколько примеров имен: def find(l, s, i=0): # НЕУДАЧНЫЙ ВЫБОР def linear_search(l, s, i=0): # НЕУДАЧНЫЙ ВЫБОР def first_index_of(sorted_name_list, name, start=0): # ХОРОШИЙ ВЫБОР Все три функции возвращают индекс первого вхождения имени в спи ске имен, причем поиск в списке начинается с указанного индекса и используется алгоритм поиска, который предполагает, что список уже отсортирован. Первый случай приходится признать неудачным, потому что имя функции ничего не говорит о том, что будут искать, а имена ее пара метров (по всей видимости) указывают на их типы (list, str, int), но ничего не говорят об их назначении. Второй вариант также следует признать неудачным, потому что имя функции описывает алгоритм, использованный первоначально, но с течением времени алгоритм мо гут изменить. Это может быть неважно для того, кто будет пользовать ся функцией, но может вводить в заблуждение тех, кто будет сопрово Собственные функции 209 ждать программный код, если имя функции предполагает реализацию алгоритма линейного поиска, а в действительности со временем функ цию могли переписать под использование алгоритма поиска методом дихотомии. Третий вариант можно назвать удачным, потому что имя функции говорит о том, что она возвращает, а имена параметров не двусмысленно показывают, что ожидает получить функция. Ни одна из функций не имеет возможности указать, что произойдет, если поиск завершится неудачей – вернут ли они, скажем, значение –1, или возбудят исключение? В какомто виде такая информация должна быть включена в описание, предоставляемое пользователям функции. Мы можем добавить описание к любой функции, используя строки до кументирования – это обычные строки, которые следуют сразу за строкой с инструкцией def и перед программным кодом функции. На пример, ниже приводится функция shorten(), которую мы уже видели ранее, но на этот раз приводится полный ее текст: def shorten(text, length=25, indicator="..."): """Возвращает text или усеченную его копию с добавлением indicator в конце text – любая строка; length – максимальная длина возвращаемой строки string (включая indicator); indicator – строка, добавляемая в конец результата, чтобы показать, что текст аргумента text был усечен >>> shorten("The Road") 'The Road' >>> shorten("No Country for Old Men", 20) 'No Country for Ol...' >>> shorten("Cities of the Plain", 15, "*") 'Cities of the *' """ if len(text) > length: text = text[:length len(indicator)] + indicator return text Нет ничего необычного в том, что текст описания длиннее самой функ ции. В соответствии с общепринятыми соглашениями первая строка в описании должна представлять собой краткое, однострочное описа ние функции, затем следует пустая строка и далее следует полное опи сание функции, в конце которого приводится несколько примеров то го, как может выглядеть использование функции в интерактивной оболочке. В главе 5 мы узнаем, как примеры, присутствующие в опи сании функции, могут использоваться для нужд модульного тестиро вания. 210 Глава 4. Управляющие структуры и функции Распаковывание аргументов и параметров В предыдущей главе мы видели, что для передачи пози ционных аргументов можно использовать оператор рас паковывания последовательностей (*). Например, если возникает необходимость вычислить площадь треуголь ника, а длины всех его сторон хранятся в списке, то мы могли бы вызвать функцию так: heron(sides[0], sides[1], sides[2]) , или просто распаковать список и сделать вызов намного проще: heron(*sides). Если элементов в списке (или в другой последовательности) больше, чем парамет ров в функции, мы можем воспользоваться операцией из влечения среза, чтобы извлечь нужное число аргументов. Мы можем также использовать оператор распаковывания последова тельности в списке параметров функции. Это удобно, когда необходи мо создать функцию, которая может принимать переменное число по зиционных аргументов. Ниже приводится функция product(), которая вычисляет произведение своих аргументов: def product(*args): result = 1 for arg in args: result *= arg return result Эта функция имеет единственный аргумент с именем args. Наличие символа * перед ним означает, что внутри функции параметр args об ретает форму кортежа, значениями элементов которого будут значе ния переданных аргументов. Ниже приводятся несколько примеров вызова функции: product(1, 2, 3, 4) # args == (1, 2, 3, 4); вернет: 24 product(5, 3, 8) # args == (5, 3, 8); вернет: 120 product(11) # args == (11,); вернет: 11 Мы можем использовать именованные аргументы вслед за позицион ными, как в функции, которая приводится ниже, вычисляющей сумму своих аргументов, каждый из которых возводится в заданную степень: def sum_of_powers(*args, power=1): result = 0 for arg in args: result += arg ** power return result Эта функция может вызываться только с позиционными аргументами, например: sum_of_powers(1, 3, 5), или как с позиционными, так и с име нованным аргументами, например: sum_of_powers(1, 3, 5, power=2). Допускается также использовать символ «*» в качестве самостоятель ного «параметра». В данном случае он указывает, что после символа Распаковыва ние последо вательностей, стр. 137 Собственные функции 211 «*» не может быть других позиционных параметров, однако указание именованных аргументов допускается. Ниже приводится модифици рованная версия функции heron(). На этот раз функция принимает точно три позиционных аргумента и один необязательный именован ный аргумент. def heron2(a, b, c, *, units="meters"): s = (a + b + c) / 2 area = math.sqrt(s * (s a) * (s b) * (s c)) return "{0} {1}".format(area, units) Ниже приводятся несколько примеров вызовов функции: heron2(25, 24, 7) # вернет: '84.0 meters' heron2(41, 9, 40, units="inches") # вернет: '180.0 inches' heron2(25, 24, 7, "inches") # ОШИБКА! Возбудит исключение TypeError В третьем вызове мы попытались передать четыре позиционных аргу мента, но оператор * не позволяет этого и вызывает исключение Type Error Поместив оператор * первым в списке параметров, мы тем самым пол ностью запретим использование любых позиционных аргументов и вы нудим тех, кто будет вызывать ее, использовать именованные аргу менты. Ниже приводится пример сигнатуры такой (вымышленной) функции: def print_setup(*, paper="Letter", copies=1, color=False): Мы можем вызывать функцию print_setup() без аргументов, допуская использование значений по умолчанию. Или изменить некоторые или все значения по умолчанию, например: print_setup(paper="A4", color= True) . Но если мы попытаемся использовать позиционные аргументы, например: print_setup("A4"), будет возбуждено исключение TypeError. Так же, как мы распаковываем последовательности для заполнения позиционных параметров, можно распаковывать и отображения – с помощью оператора распаковывания отображений (**). 1 Мы можем использовать оператор **, чтобы передать содержимое словаря в функ цию print_setup(). Например: options = dict(paper="A4", color=True) print_setup(**options) В данном случае пары «ключзначение» словаря options будут распа кованы, и каждое значение будет ассоциировано с параметром, чье имя соответствует ключу этого значения. Если в словаре обнаружится ключ, не совпадающий ни с одним именем параметра, будет возбужде но исключение TypeError. Любые аргументы, для которых в словаре не 1 Как мы уже видели в главе 2, когда ** используется в качестве двухместно го оператора, он является аналогом функции pow(). 212 Глава 4. Управляющие структуры и функции найдется соответствующего элемента, получат значение по умолча нию, но если такие аргументы не имеют значения по умолчанию, бу дет возбуждено исключение TypeError. Кроме того, имеется возможность использовать оператор распаковы вания вместе с параметрами в объявлении функции. Это позволяет создавать функции, способные принимать любое число именованных аргументов. Ниже приводится функция add_person_details(), которая принимает номер карточки социального страхования и фамилию в ви де позиционных аргументов, а также произвольное число именован ных аргументов: def add_person_details(ssn, surname, **kwargs): print("SSN =", ssn) print(" surname =", surname) for key in sorted(kwargs): print(" {0} = {1}".format(key, kwargs[key])) Функция print() Функция print() может принимать произвольное число позици онных аргументов и имеет три именованных аргумента: sep, end и file. Все именованные аргументы имеют значение по умолча нию. В качестве значения по умолчанию для параметра sep ис пользуется пробел – если функции передано два или более пози ционных аргументов, при выводе они отделяются друг от друга значением sep, но если функция получит единственный позици онный аргумент, этот параметр в выводе не участвует. В качест ве значения по умолчанию для параметра end используется сим вол \n, именно по этой причине функция print() завершает вы вод своих аргументов переводом строки. В качестве значения по умолчанию для параметра file используется sys.stdout, поток стандартного вывода, который обычно представляет консоль. Имеется возможность переопределять значение любого именован ного аргумента, если значения по умолчанию чемто не устраива ют. Например, в аргументе file можно передать объект файла, от крытый на запись или на дополнение в конец, а в аргументах sep и end можно передавать любые строки, включая пустые. Когда необходимо вывести несколько элементов в одной и той же строке, обычно применяется прием, когда функция print() вызывается с аргументом end, в качестве значения которого ис пользуется требуемый разделитель, а в самом конце вызывается функция print() без аргументов, только для того, чтобы вывести символ перевода строки. Например, смотрите функцию print_di gits() (стр. 213). Собственные функции 213 Эта функция может вызываться как только с двумя позиционными ар гументами, так и с дополнительной информацией, например: add_per son_details(83272171, "Luther", forename="Lexis", age=47). Такая возмож ность обеспечивает огромную гибкость. Конечно, мы можем также од новременно принимать переменное число позиционных аргументов и переменное число именованных аргументов: def print_args(*args, **kwargs): for i, arg in enumerate(args): print("positional argument {0} = {1}".format(i, arg)) for key in kwargs: print("keyword argument {0} = {1}".format(key, kwargs[key])) Эта функция просто выводит полученные аргументы. Она может вы зываться вообще без аргументов или с произвольным числом позици онных и именованных аргументов. Доступ к переменным в глобальной области видимости Иногда бывает удобно иметь несколько глобальных переменных, дос тупных из разных функций программы. В этом нет ничего плохого, ес ли речь идет о «константах», но в случае переменных – это не самый лучший выход, хотя для коротких одноразовых программ это в неко торых случаях можно считать допустимым. Программа digit_names.py принимает необязательный код языка («en» или «fr») и число в виде аргументов командной строки и выво дит названия всех цифр заданного числа. То есть если в командной строке программе было передано число «123», она выведет «one two three». В программе имеется три глобальные переменные: Language = "en" ENGLISH = {0: "zero", 1: "one", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"} FRENCH = {0: "zйro", 1: "un", 2: "deux", 3: "trois", 4: "quatre", 5: "cinq", 6: "six", 7: "sept", 8: "huit", 9: "neuf"} Мы следуем соглашению, в соответствии с которым имена перемен ных, играющих роль констант, записываются только символами верх него регистра, и установили английский язык по умолчанию. (В языке Python отсутствует прямой способ создания констант, вместо этого он полностью полагается на то, что программист будет неуклонно следо вать общепринятым соглашениям.) В некотором другом месте про граммы выполняется обращение к переменной Language, и ее значение используется при выборе соответствующего словаря: def print_digits(digits): dictionary = ENGLISH if Language == "en" else FRENCH for digit in digits: print(dictionary[int(digit)], end=" ") print() 214 Глава 4. Управляющие структуры и функции Когда интерпретатор Python встречает имя переменной Language внут ри функции, он пытается отыскать его в локальной области видимости (в области видимости функции) и не находит. Поэтому он продолжает поиск в глобальной области видимости (в области видимости файла .py ), где и обнаруживает его. Назначение именованного аргумента end, используемого в первом вызове функции print(), описывается во врез ке «Функция print()». Ниже приводится содержимое функции main() программы. Она изме няет значение переменной Language в случае необходимости и вызывает функцию print_digits() для вывода результата. def main(): if len(sys.argv) == 1 or sys.argv[1] in {"h", "help"}: print("usage: {0} [en|fr] number".format(sys.argv[0])) sys.exit() args = sys.argv[1:] if args[0] in {"en", "fr"}: global Language Language = args.pop(0) print_digits(args.pop(0)) Обратите внимание на использование инструкции global в этой функ ции. Эта инструкция используется для того, чтобы сообщить интер претатору, что данная переменная существует в глобальной области видимости (в области видимости файла .py) и что операция присваива ния должна применяться к глобальной переменной; без этой инструк ции операция присваивания создаст локальную переменную с тем же именем. Если не использовать инструкцию global, программа сохранит свою работоспособность, но когда интерпретатор встретит пере менную Language в условной инструкции if, он попытается оты скать ее в локальной области видимости (в области видимости функции) и, не обнаружив ее, создаст новую локальную пере менную с именем Language, оставив глобальную переменную Language без изменений. Эта малозаметная ошибка будет прояв ляться только в случае запуска программы с аргументом «fr», потому что в этом случае будет создана новая локальная пере менная Language, в которую будет записано значение «fr», а гло бальная переменная Language, которая используется функцией print_digits() , попрежнему будет иметь значение «en». В сложных программах лучше вообще не использовать глобальные пе ременные, за исключением констант, которые не требуют употребле ния инструкции global. Собственные функции 215 Лямбдафункции Лямбдафункции – это функции, для создания которых используется следующий синтаксис: lambda parameters: expression Часть parameters является необязательной, а если она присутствует, то обычно представляет собой простой список имен переменных, разделенных запятыми, то есть позиционных аргументов, хотя при необходимости допускается использовать полный синтаксис определе ния аргументов, используемый в инструкции def. Выра жение expression не может содержать условных инструк ций или циклов (хотя условные выражения являются допустимыми), а также не может содержать инструкцию return (или yield). Результатом лямбдавыражения явля ется анонимная функция. Когда вызывается лямбда функция, она возвращает результат вычисления выра жения expression. Если выражение expression представ ляет собой кортеж, оно должно быть заключено в круг лые скобки. Ниже приводится пример простой лямбдафункции, которая добавля ет (или не добавляет) суффикс «s» в зависимости от того, имеет ли ар гумент значение 1: s = lambda x: "" if x == 1 else "s" Лямбдавыражение возвращает анонимную функцию, которая при сваивается переменной s. Любая (вызываемая) переменная может вы зываться как функция при помощи круглых скобок, поэтому после выполнения некоторой операции можно при помощи функции s() вы вести сообщение с числом обработанных файлов, например: print("{0} file{1} processed".format(count, s(count)) Лямбдафункции часто используются в виде аргумента key встроенной функции sorted() или метода list.sort(). Предположим, что имеется список, элементами которого являются трехэлементные кортежи (но мер группы, порядковый номер, название), и нам необходимо отсорти ровать этот список различными способами. Ниже приводится пример такого списка: elements = [(2, 12, "Mg"), (1, 11, "Na"), (1, 3, "Li"), (2, 4, "Be")] Отсортировав список, мы получим следующий результат: [(1, 3, 'Li'), (1, 11, 'Na'), (2, 4, 'Be'), (2, 12, 'Mg')] Ранее, когда мы рассматривали функцию sorted(), то ви дели, что имеется возможность изменить порядок сорти ровки, если в аргументе key передать требуемую функ Функции генераторы, стр. 324 Функция sorted() , стр. 164, 170 216 Глава 4. Управляющие структуры и функции цию. Например, если необходимо отсортировать список не по естест венному порядку: номер группы, порядковый номер и название, а по порядковому номеру и названию, то мы могли бы написать маленькую функцию def ignore0(e): return e[1], e[2] и передавать ее в аргументе key . Но создавать в программе массу крошечных функций, подобных этой, очень неудобно, поэтому часто используется альтернативный подход, основанный на применении лямбдафункций: elements.sort(key=lambda e: (e[1], e[2])) Здесь в качестве значения аргумента key используется выражение lambda e: (e[1], e[2]) , которому в виде аргумента e последовательно передаются все трехэлементные кортежи из списка. Круглые скобки, окружающие лямбдавыражение, обязательны, когда выражение яв ляется кортежем и лямбдафункция создается как аргумент другой функции. Для достижения того же эффекта можно было бы использо вать операцию извлечения среза: elements.sort(key=lambda e: e[1:3]) Немного более сложная версия обеспечивает возможность сортировки по названию, без учета регистра символов, и порядковому номеру: elements.sort(key=lambda e: (e[2].lower(), e[1])) Ниже приводятся два эквивалентных способа создания функции, вы числяющей площадь треугольника по известной формуле × основание × высота: def area(b, h): area = lambda b, h: 0.5 * b * h return 0.5 * b * h Мы можем вызвать функцию area(6, 5) независимо от того, была ли она создана как лямбдафункция или с помощью инструкции def, и ре зультат будет один и тот же. Другая замечательная область применения лямбдафунк ций – создание словарей со значениями по умолчанию. В предыдущей главе говорилось, что при обращении к та кому словарю с несуществующим ключом будет создан соответствующий элемент с указанным ключом и со зна чением по умолчанию. Ниже приводятся несколько при меров создания таких словарей: minus_one_dict = collections.defaultdict(lambda: 1) point_zero_dict = collections.defaultdict(lambda: (0, 0)) message_dict = collections.defaultdict(lambda: "No message available") При обращении к словарю minus_one_dict с несуществующим ключом будет создан новый элемент с указанным ключом и со значением –1. Точно так же при обращении к словарю point_zero_dict вновь создан ный элемент получит в качестве значения кортеж (0, 0), а при обра 1 2 --- Словари со значениями по умолча нию, стр. 161 Собственные функции 217 щении к словарю message_dict значением по умолчанию будет строка «No message available». Утверждения Что произойдет, если функция получит аргументы, имеющие ошибоч ные значения? Что случится, если в реализации алгоритма будет допу щена ошибка и вычисления будут выполнены неправильно? Самое не приятное , что может произойти, – это то, что программа будет выпол няться без какихлибо видимых проблем, но будет давать неверные ре зультаты. Один из способов избежать таких коварных проблем состоит в том, чтобы писать тесты, о которых кратко будет рассказано в главе 5. Другой способ состоит в том, чтобы определить предвари тельные условия и ожидаемый конечный результат, и сообщать об ошибке, если они не соответствуют друг другу. В идеале следует ис пользовать как тестирование, так и метод на основе сравнения предва рительных условий и ожидаемых результатов. Предварительные условия и ожидаемый результат можно задать с по мощью инструкции assert, которая имеет следующий синтаксис: assert boolean_expression, optional_expression Если выражение boolean_expression возвращает значение False, возбу ждается исключение AssertionError. Если задано необязательное выра жение optional_expression, оно будет использовано в качестве аргумен та исключения AssertionError, что удобно для передачи сообщений об ошибках. Однако следует отметить, что утверждения предназначены для использования разработчиками, а не конечными пользователями. Проблемы, возникающие в процессе нормальной эксплуатации про граммы, такие как отсутствующие файлы или ошибочные аргументы командной строки, должны обрабатываться другими средствами, на пример, посредством вывода сообщений об ошибках или записи сооб щений в файл журнала. Ниже приводятся две версии функции product(). Обе версии эквива лентны в том смысле, что обе они требуют, чтобы все передаваемые им аргументы имели ненулевое значение, а вызов с нулевыми значе ниями рассматривается как ошибка программиста. def product(*args): # пессимистичная def product(*args): # оптимистичная assert all(args), "0 argument" result = 1 result = 1 for arg in args: for arg in args: result *= arg result *= arg assert result, "0 argument" return result return result «Пессимистичная» версия, слева, проверяет все аргументы (точнее – до первого нулевого значения) при каждом вызове. «Оптимистичная» версия, справа, проверяет результат – если хотя бы один аргумент имеет нулевое значение, то и результат будет равен 0. 218 Глава 4. Управляющие структуры и функции Если любую из этих версий вызвать со значением 0 в одном из аргу ментов, будет возбуждено исключение AssertionError и в поток стан дартного вывода сообщений об ошибках (sys.stderr – обычно консоль) будет выведено следующее: Traceback (most recent call last): File "program.py", line 456, in x = product(1, 2, 0, 4, 8) File "program.py", line 452, in product assert result, "0 argument" AssertionError: 0 argument Интерпретатор автоматически выведет диагностическую информацию с именем файла, именем функции и номером строки, а также текст со общения, указанного нами. Но как быть с инструкциями assert, после того как программа будет готова к выпуску в виде окончательной версии (при этом она, безус ловно, успешно проходит все тесты и не нарушает ни одного утвержде ния)? Мы можем сообщить интерпретатору о том, что больше не требу ется выполнять инструкции assert, то есть их нужно отбрасывать во время выполнения программы. Для этого программа должна запус каться с ключом командной строки – O , например python – O program.py Другой способ добиться этого состоит в том, чтобы установить пере менную окружения PYTHONOPTIMIZE в значение O. 1 Если наши пользова тели не пользуются строками документирования (обычно им этого и не требуется), мы можем использовать ключ – OO , который эффективно удаляет как инструкции assert, так и строки документирования: обра тите внимание, что для установки такого поведения нет переменной окружения. Некоторые разработчики используют упрощенный под ход: они создают копии программ, где все инструкции assert заком ментированы, и в случае прохождения всех тестов они выпускают вер сию программы без инструкций assert. Пример: make_html_skeleton.py В этом разделе мы объединим некоторые приемы, описанные в этой главе, и продемонстрируем их в контексте законченной программы. Очень маленькие вебсайты часто создаются и обслуживаются вруч ную. Один из способов облегчить эту работу состоит в том, чтобы напи сать программу, которая будет генерировать заготовки файлов HTML, которые позднее будут наполняться содержимым. Программа make_ html_skeleton.py выполняется в интерактивном режиме, она запраши вает у пользователя различные сведения и затем создает заготовку файла HTML. Функция main() содержит цикл, позволяющий созда вать одну заготовку за другой, и сохраняет общую информацию (на 1 Это буква «O», а не цифра 0. – Прим. перев. Пример: make_html_skeleton.py 219 пример, информацию об авторских правах), что избавляет пользовате лей от необходимости вводить ее снова и снова. Ниже приводится при мер типичного сеанса работы с программой: make_html_skeleton.py Make HTML Skeleton Enter your name (for copyright): Harold Pinter Enter copyright year [2008]: 2009 Enter filename: careersynopsis Enter title: Career Synopsis Enter description (optional): synopsis of the career of Harold Pinter Enter a keyword (optional): playwright Enter a keyword (optional): actor Enter a keyword (optional): activist Enter a keyword (optional): Enter the stylesheet filename (optional): style Saved skeleton careersynopsis.html Create another (y/n)? [y]: Make HTML Skeleton Enter your name (for copyright) [Harold Pinter]: Enter copyright year [2009]: Enter filename: Cancelled Create another (y/n)? [y]: n Обратите внимание, что при создании второй заготовки имя и год по лучили значения по умолчанию, введенные ранее, поэтому пользова телю не пришлось вводить их вторично. Но для имени файла значение по умолчанию отсутствует, поэтому, когда имя файла не было указа но, процедура создания заготовки была прервана. Теперь, когда мы увидели, как пользоваться программой, мы готовы приступить к изучению программного кода. Программа начинается двумя инструкциями импорта: import datetime import xml.sax.saxutils Модуль datetime предоставляет ряд простых функций для создания объектов datetime.date и datetime.time. Модуль xml.sax.saxutils содер жит удобную функцию xml.sax.saxutils.escape(), которая принимает строку и возвращает эквивалентную ей строку, в которой специаль ные символы языка разметки HTML («&», «<» и «>») замещаются их эквивалентами («&», «<» и «>»). Далее определяются три глобальные строки, которые используются в качестве шаблонов. |