Python Suzi
Python Suzi
Курс лекций
(PDF для hunger.ru от nerezus’а)
Изучается язык программирования Python, его основные библиотеки и некоторые
приложения.
3. Элементы Эта лекция может показаться необычной для того, кто использует
функционального императивные языки программирования (вроде Pascal, C++ или Java).
программирования Тем не менее, функциональный подход дает программисту мощные
средства, позволяя создавать не только более компактный, но и более
устойчивый к ошибкам программный код. Совсем не обязательно
писать с помощью Python чисто функциональные программы, но
необходимо научиться видеть, где элементы функционального
программирования принесут максимальный эффект.
4. Объектно-ориентированное программирование
2
7. Работа с данными Работа с современными форматами данных - одно из сильных
в различных мест стандартной библиотеки Python. В этой лекции будут
форматах рассмотрены типичные для Python подходы к чтению,
преобразованию и записи информации в требуемых форматах.
В настоящее время разработано и доступно в Интернете
большое количество модулей для всевозможных форматов
данных.
3
Лекция #1: Введение в программирование на языке
Python
Что такое Python?
О Python (лучше произносить "питон", хотя некоторые говорят "пайтон") - предмете
данного изучения, лучше всего говорит создатель этого языка программирования,
голландец Гвидо ван Россум:
В процессе изучения будет раскрыт смысл этого определения, а сейчас достаточно знать,
что Python - это универсальный язык программирования. Он имеет свои преимущества и
недостатки, а также сферы применения. В поставку Python входит обширная стандартная
библиотека для решения широкого круга задач. В Интернете доступны качественные
библиотеки для Python по различным предметным областям: средства обработки текстов и
технологии Интернет, обработка изображений, инструменты для создания приложений,
механизмы доступа к базам данных, пакеты для научных вычислений, библиотеки
построения графического интерфейса и т.п. Кроме того, Python имеет достаточно простые
средства для интеграции с языками C, C++ (и Java) как путем встраивания (embedding)
интерпретатора в программы на этих языках, так и наоборот, посредством использования
библиотек, написанных на этих языках, в Python-программах. Язык Python поддерживает
несколько парадигм программирования: императивное (процедурный, структурный,
модульный подходы), объектно-ориентированное и функциональное программирование.
Можно считать, что Python - это целая технология для создания программных продуктов (и
их прототипов). Она доступна почти на всех современных платформах (как 32-битных, так
и на 64-битных) с компилятором C и на платформе Java.
Может показаться, что, в программной индустрии нет места для чего-то другого кроме
C/C++, Java, Visual Basic, C#. Однако это не так. Возможно, благодаря данному курсу
лекций и практических занятий у Python появятся новые приверженцы, для которых он
станет незаменимым инструментом.
В этой лекции не ставится цели систематически описать Python: для этого существует
оригинальное справочное руководство. Здесь предлагается рассмотреть язык
одновременно в нескольких аспектах, что достигается набором примеров, которые
позволят быстрее приобщиться к реальному программированию, чем в случае строгого
академического подхода.
4
интерпретатор на основании переданного, обычно называют прагматикой. При написании
программы очень важно, чтобы в этой цепочке не возникало сбоев.
Программа на Python
Программа на языке Python может состоять из одного или нескольких модулей. Каждый
модуль представляет собой текстовый файл в кодировке, совместимой с 7-битной
кодировкой ASCII. Для кодировок, использующих старший бит, необходимо явно указывать
название кодировки. Например, модуль, комментарии или строковые литералы которого
записаны в кодировке KOI8-R, должен иметь в первой или второй строке следующую
спецификацию:
О том, как делать программу модульной, станет известно в следующих лекций. В примерах
ниже используются как фрагменты модулей, записанных в файл, так и фрагменты диалога
с интерпретатором Python. Последние отличаются характерным приглашением >>>. Символ
решетка (#) отмечает комментарий до конца строки.
5
Основные алгоритмические конструкции
Предполагается, что слушатели уже умеют программировать хотя бы на уровне школьной
программы, и потому вполне достаточно провести параллели между алгоритмическими
конструкциями и синтаксисом Python. Кроме того, Python как правило не подводит
интуицию программиста (по крайней мере, науке хорошо известны типичные ловушки
начинающих программистов на Python), поэтому изучать синтаксис Python
предпочтительнее на примерах, а не с помощью синтаксических диаграмм или форм
Бэкуса-Наура.
Последовательность операторов
a = 1
b = 2
a = a + b
b = a - b
a = a - b
print a, b
Что делает этот пример? Проверить свою догадку можно с помощью интерактивного
режима интерпретатора Python.
При работе с Python в интерактивном режиме как бы вводится одна большая программа,
состоящая из последовательных действий. В примере выше использованы операторы
присваивания и оператор print.
if a > b:
c = a
else:
c = b
Этот кусок кода на Python интуитивно понятен каждому, кто помнит, что if по-английски
значит "если", а else - "иначе". Оператор ветвления имеет в данном случае две части,
операторы каждой из которых записываются с отступом вправо относительно оператора
ветвления. Более общий случай - оператор выбора - можно записать с помощью
следующего синтаксиса (пример вычисления знака числа):
if a < 0:
s = -1
elif a == 0:
s = 0
else:
s = 1
Стоит заметить, что elif - это сокращенный else if. Без сокращения пришлось бы
применять вложенный оператор ветвления:
if a < 0:
s = -1
else:
if a == 0:
6
s = 0
else:
s = 1
Циклы
s = "abcdefghijklmnop"
while s != "":
print s
s = s[1:-1]
Оператор while говорит интерпретатору Python: "пока верно условие цикла, выполнять
тело цикла". В языке Python тело цикла выделяется отступом. Каждое исполнение тела
цикла будет называться итерацией. В приведенном примере убирается первый и
последний символ строки до тех пор, пока не останется пустая строка.
Для большей гибкости при организации циклов применяются операторы break (прервать) и
continue (продолжить). Первый позволяет прервать цикл, а второй - продолжить цикл,
перейдя к следующей итерации (если, конечно, выполняется условие цикла).
Следующий пример читает строки из файла и выводит те, у которых длина больше 5:
f = open("file.txt", "r")
while 1:
l = f.readline()
if not l:
break
if len(l) > 5:
print l,
f.close()
В языке Python логическое значение несет каждый объект: нули, пустые строки и
последовательности, специальный объект None и логический литерал False имеют
значение "ложь", а прочие объекты значение "истина". Для обозначения истины обычно
используется 1 или True.
Примечание:
Литералы True и False для обозначения логических значений появились в Python 2.3.
Цикл ДЛЯ выполняет тело цикла для каждого элемента последовательности. В следующем
примере выводится таблица умножения:
7
Здесь циклы for являются вложенными. Функция range() порождает список целых чисел
из полуоткрытого диапазона [1, 10). Перед каждой итерацией счетчик цикла получает
очередное значение из этого списка. Полуоткрытые диапазоны общеприняты в Python.
Считается, что их использование более удобно и вызывает меньше программистских
ошибок. Например, range(len(s)) порождает список индексов для списка s (в Python-
последовательности первый элемент имеет индекс 0). Для красивого вывода таблицы
умножения применена операция форматирования % (для целых чисел тот же символ
используется для обозначения операции взятия остатка от деления). Строка
форматирования (задается слева) строится почти как строка форматирования для
printf из C.
Функции
В этом примере определена функция двух аргументов (из которых второй имеет значение
по умолчанию - 0). Вариантов вызова этой функции с конкретными параметрами также
несколько. Стоит только заметить, что при вызове функции сначала должны идти
позиционные параметры, а затем, именованные. Аргументы со значениями по умолчанию
должны следовать после обычных аргументов. Оператор return возвращает значение
функции. Из функции можно возвратить только один объект , но он может быть кортежем
из нескольких объектов.
Исключения
try:
res = int(open('a.txt').read()) / int(open('c.txt').read())
print res
except IOError:
print "Ошибка ввода-вывода"
except ZeroDivisionError:
print "Деление на 0"
except KeyboardInterrupt:
print "Прерывание с клавиатуры"
except:
print "Ошибка"
В этом примере берутся числа из двух файлов и делятся одно на другое. В результате этих
нехитрых действий может возникнуть несколько исключительных ситуаций, некоторые из
них отмечены в частях except (здесь использованы стандартные встроенные исключения
Python). Последняя часть except в этом примере улавливает все другие исключения,
8
которые не были пойманы выше. Например, если хотя бы в одном из файлов находится
нечисловое значение, функция int() возбудит исключение ValueError. Его-то и сможет
отловить последняя часть except. Разумеется, выполнение части try в случае
возникновения ошибки уже не продолжается после выполнения одной из частей except.
try:
value = dict[key]
except:
value = default_value
Вместо
if dict.has_key(key):
value = dict[key]
else:
value = default_value
Примечание:
Пример уже несколько устаревшей идиомы языка Python иллюстрирует только дух этого
подхода: в современном Python лучше записать так value = dict.get(key,
default_value).
Исключения можно возбуждать и из программы. Для этого служит оператор raise. Заодно
следующий пример показывает канонический способ определения собственного
исключения:
class MyError(Exception):
pass
try:
...
raise MyError, "my error 1"
...
except MyError, x:
print "Ошибка:", x
c = a + b
assert c == a + b
Кроме описанной формы оператора, есть еще форма try-finally для гарантированного
выполнения некоторых действий при передаче управления изнутри оператора try-finally
вовне. Он может применяться для освобождения занятых ресурсов, что требует
обязательного выполнения, независимо от произошедших внутри катаклизмов:
try:
...
9
finally:
print "Обработка гарантированно завершена"
Карта встроенных типов (с именами функций для приведения к нужному типу и именами
классов для наследования от этих типов):
Два типа: int (целые числа) и long (целые произвольной точности) служат моделью для
представления целых чисел. Первый соответствует типу long в компиляторе C для
используемой архитектуры. Числовые литералы можно записать в системах счисления с
основанием 8, 10 или 16:
10
# В этих литералах записано число 10
print 10, 012, 0xA, 10L
Стоит заметить, что если в результате операции получается значение, выходящее за рамки
допустимого, тип int может быть неявно преобразован в long:
>>> type(-2147483648)
<type 'int'>
>>> type(-2147483649)
<type 'long'>
Также нужно быть осторожным при записи констант. Ноли в начале числа - признак
восьмеричной системы счисления, в которой нет цифры 8:
>>> 008
File "<stdin>", line 1
008
^
SyntaxError: invalid token
Тип float
>>> pi = 3.1415926535897931
>>> pi ** 40
7.6912142205156999e+19
Примечание:
11
Из полезных встроенных функций можно вспомнить round(), abs().
Тип complex
Тип bool
Следует отметить, что Python даже не вычисляет второй операнд операции and или or,
если ее исход ясен по первому операнду. Таким образом, если первый операнд истиннен,
он и возвращается как результат or, в противном случае возвращается второй операнд.
Для операции and все аналогично.
В Python строки бывают двух типов: обычные и Unicode-строки. Фактически строка - это
последовательность символов (в случае обычных строк можно сказать "последовательность
байтов"). Строки-константы можно задать в программе с помощью строковых литералов.
Для литералов наравне используются как апострофы ('), так и обычные двойные кавычки
("). Для многострочных литералов можно использовать утроенные апострофы или
утроенные кавычки. Управляющие последовательности внутри строковых литералов
задаются обратной косой чертой (\). Примеры написания строковых литералов:
s1 = "строка1"
s2 = 'строка2\nс переводом строки внутри'
s3 = """строка3
с переводом строки внутри"""
u1 = u'\u043f\u0440\u0438\u0432\u0435\u0442' # привет
u2 = u'Еще пример' # не забудьте про coding!
Для строк имеется еще одна разновидность: необработанные строковые литералы. В этих
литералах обратная косая черта и следующие за ней символы не интерпретируются как
спецсимволы, а вставляются в строку "как есть":
my_re = r"(\d)=\1"
12
Обычно такие строки требуются для записи регулярных выражений (о них пойдет речь в
лекции, посвященной обработке текстовой информации).
Тип tuple
a, b = b, a
Тип list
В "чистом" Python нет массивов с произвольным типом элемента. Вместо них используются
списки. Их можно задать с помощью литералов, записываемых в квадратных скобках, или
посредством списковых включений. Варианты задания списка приведены ниже:
Для работы со списками существует несколько методов, дополнительных к тем, что имеют
неизменчивые последовательности. Все они связаны с изменением списка.
Последовательности
Синтаксис Семантика
len(s) Длина последовательности s
13
x in s Проверка принадлежности элемента последовательности. В новых версиях
Python можно проверять принадлежность подстроки строке. Возвращает True
или False
x not in s = not x in s
s + s1 Конкатенация последовательностей
s*n или Последовательность из n раз повторенной s. Если n < 0, возвращается пустая
n*s последовательность.
s[i] Возвращает i-й элемент s или len(s)+i-й, если i < 0
s[i:j:d] Срез из последовательности s от i до j с шагом d будет рассматриваться ниже
min(s) Наименьший элемент s
max(s) Наибольший элемент s
Метод Описание
append(x) Добавляет элемент в конец последовательности
count(x) Считает количество элементов, равных x
extend(s) Добавляет к концу последовательности последовательность s
index(x) Возвращает наименьшее i, такое, что s[i] == x. Возбуждает
исключение ValueError, если x не найден в s
insert(i, x) Вставляет элемент x в i-й промежуток
pop([i]) Возвращает i-й элемент, удаляя его из последовательности
reverse() Меняет порядок элементов s на обратный
sort([cmpfunc]) Сортирует элементы s. Может быть указана своя функция сравнения
cmpfunc
>>> s = [0, 1, 2, 3, 4]
>>> print s[0], s[-1], s[3]
0 4 3
>>> s[2] = -2
>>> print s
[0, 1, -2, 3, 4]
>>> del s[2]
>>> print s
14
[0, 1, 3, 4]
Примечание:
Несколько интереснее обстоят дела со срезами. Дело в том, что в Python при взятии среза
последовательности принято нумеровать не элементы, а промежутки между ними.
Поначалу это кажется необычным, тем не менее, очень удобно для указания произвольных
срезов. Перед нулевым (по индексу) элементом последовательности промежуток имеет
номер 0, после него - 1 и т.д.. Отрицательные значения отсчитывают промежутки с конца
строки. Для записи срезов используется следующий синтаксис:
последовательность[нач:кон:шаг]
где нач - промежуток начала среза, кон - конца среза, шаг - шаг. По умолчанию нач=0,
кон=len(последовательность), шаг=1, если шаг не указан, второе двоеточие можно
опустить.
>>> s = range(10)
>>> s
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> s[0:3]
[0, 1, 2]
>>> s[-1:]
[9]
>>> s[::3]
[0, 3, 6, 9]
>>> s[0:0] = [-1, -1, -1]
>>> s
[-1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> del s[:3]
>>> s
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Как видно из этого примера, с помощью срезов удобно задавать любую подстроку, даже
если она нулевой длины, как для удаления элементов, так и для вставки в строго
определенное место.
Тип dict
15
d.update(d0) # пополняется словарь из другого
print len(d) # количество пар в словаре
Тип file
Объекты этого типа предназначены для работы с внешними данными. В простом случае -
это файл на диске. Файловые объекты должны поддерживать основные методы: read(),
write(), readline(), readlines(), seek(), tell(), close() и т.п.
f1 = open("file1.txt", "r")
f2 = open("file2.txt", "w")
for line in f1.readlines():
f2.write(line)
f2.close()
f1.close()
import urllib
f1 = urllib.urlopen("http://python.onego.ru")
Выражения
В современных языках программирования принято производить большую часть обработки
данных в выражениях. Синтаксис выражений у многих языков программирования
примерно одинаков. Синтаксис выражений Python не удивит программиста чем-то новым.
(Разве что цепочечные сравнения могут приятно порадовать.)
Операция Название
lambda лямбда-выражение
or логическое ИЛИ
and логическое И
not x логическое НЕ
in, not in проверка принадлежности
is, is not проверка идентичности
<,<=,>,>=,!=,== сравнения
| побитовое ИЛИ
^ побитовое исключающее ИЛИ
& побитовое И
<<, >> побитовые сдвиги
+, - сложение и вычитание
16
*, /, % умножение, деление, остаток
+x, -x унарный плюс и смена знака
~x побитовое НЕ
** возведение в степень
x.атрибут ссылка на атрибут
x[индекс] взятие элемента по индексу
x[от:до] выделение среза (от и до)
f(аргумент,...) вызов функции
( ... ) скобки или кортеж
[ ... ] список или списковое включение
{кл:зн, ...} словарь пар ключ-значение
`выражения` преобразование к строке (repr)
В случае неясности приоритетов желательно применять скобки. Несмотря на то, что одни и
те же символы могут использоваться для разных операций, приоритеты операций не
меняются. Так, % имеет тот же приоритет, что и *, а потому в следующем примере скобки
просто необходимы, чтобы операция умножения произошла перед операцией
форматирования:
Очень часто выражения стоят в правой части оператора присваивания или расширенного
присваивания. В Python (в отличие, скажем, от C) нет операции присваивания, поэтому
синтаксически перед знаком = могут стоять только идентификатор, индекс, срез, доступ к
атрибуту или кортеж (список) из перечисленного. (Подробности в документации).
Имена
Об именах (идентификаторах) говорилось уже не раз, тем не менее, необходимо сказать
несколько слов об их применении в языке Python.
Имя может начинаться с латинской буквы (любого регистра) или подчеркивания, а дальше
допустимо использование цифр. В качестве идентификаторов нельзя применять ключевые
слова языка и нежелательно переопределять встроенные имена. Список ключевых слов
можно узнать так:
17
>>> import keyword
>>> keyword.kwlist
['and', 'assert', 'break', 'class', 'continue', 'def', 'del',
'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or',
'pass', 'print', 'raise', 'return', 'try', 'while', 'yield']
Для понимания того, как Python находит значение некоторой переменной, необходимо
ввести понятие блока кода. В Python блоком кода является то, что исполняется как
единое целое, например, тело определения функции, класса или модуля.
Локальные имена - имена, которым присвоено значение в данном блоке кода. Глобальные
имена - имена, определяемые на уровне блока кода определения модуля или те, которые
явно заданы в операторе global. Встроенные имена - имена из специального словаря
__builtins__.
Области видимости имен могут быть вложенными друг в друга, например, внутри
вызванной функции видны имена, определенные в вызывающем коде. Переменные,
которые используются в блоке кода, но связаны со значением вне кода, называются
свободными переменными.
Так как переменную можно связать с объектом в любом месте блока, важно, чтобы это
произошло до ее использования, иначе будет возбуждено исключение NameError.
Связывание имен со значениями происходит в операторах присваивания, for, import, в
формальных аргументах функций, при определении функции или класса, во втором
параметре части except оператора try-except.
С областями видимости и связыванием имен есть много нюансов, которые хорошо описаны
в документации. Желательно, чтобы программы не зависели от таких нюансов, а для этого
достаточно придерживаться следующих правил:
Убрать связь имени с объектом можно с помощью оператора del. В этом случае, если
объект не имеет других ссылок на него, он будет удален. Для управления памятью в Python
используется подсчет ссылок (reference counting), для удаления наборов объектов с
зацикленными ссылками - сборка мусора (garbage collection).
Стиль программирования
18
Стиль программирования - дополнительные ограничения, накладываемые на структуру
и вид программного кода группой совместно работающих программистов с целью
получения удобных для применения, легко читаемых и эффективных программ. Основные
ограничения на вид программы дает синтаксис языка программирования, и его нарушения
вызывают синтаксические ошибки. Нарушение стиля не приводит к синтаксическим
ошибкам, однако как отдельные программисты, так и целые коллективы сознательно
ограничивают себя в средствах выражения ради упрощения совместной разработки,
отладки и сопровождения программного продукта.
Для языка Python Гвидо ван Россум разработал официальный стиль. С оригинальным
текстом "Python Style Guide" можно ознакомиться по адресу
http://www.python.org/doc/essays/styleguide.html.
• Имена модулей лучше давать строчными буквами, например, shelve, string, либо
делать первые буквы слов прописными, StringIO, UserDict. Имена написанных на C
модулей расширения обычно начинаются с подчеркивания "_", а соответствующие
им высокоуровневые обертки - с прописных букв: _tkinter и Tkinter.
• Ключевые слова нельзя использовать в качестве имен, однако, если все-таки
необходимо воспользоваться этим именем, стоит добавить одиночное подчеркивание
в конце имени. Например: class_.
20
• Классы обычно называют, выделяя первые буквы слов прописными, как в Tag или
HTTPServer.
• Имена исключений обычно содержат в своем составе слово "error" (или "warning").
Встроенные модули пишут это слово со строчной буквы (как os.error) (но могут
писать и с прописной): distutils.DistutilsModuleError.
• Функции, экспортируемые модулем, могут именоваться по-разному. Можно давать с
прописных букв имена наиболее важных функций, а вспомогательные писать
строчными.
• Имена глобальных переменных (если таковые используются) лучше начинать с
подчеркивания, чтобы они не импортировались из модуля оператором from-import
со звездочкой.
• Имена методов записываются по тем же правилам, что и имена функций.
• Имена констант (имен, которые не должны переопределяться) лучше записывать
прописными буквами, например: RED, GREEN, BLUE.
• При работе с языком Python необходимо учитывать, что интерпретатор считает
некоторые классы имен специальными (обычно такие имена начинаются с
подчеркивания).
Заключение
В этой лекции синтаксис языка показан на примерах, что в случае с Python оправдано, так
как эта часть языка достаточна проста. Были рассмотрены основные операторы языка,
выражения и многие из встроенных типов данных, кратко объяснены принципы работы
Python с именами, приведены правила официального стиля программирования на Python.
21
Лекция #2: Основные стандартные модули Python
Одним из важных преимуществ языка Python является наличие большой библиотеки
модулей и пакетов, входящих в стандартную поставку. Как говорят, к Python "приложены
батарейки".
Понятие модуля
Перед тем как приступить к изучению моделей стандартной библиотеки, необходимо
определить то, что в Python называется модулем.
В языке Python набор модулей, посвященных одной проблеме, можно поместить в пакет.
Хорошим примером такого пакета является пакет xml, в котором собраны модули для
различных аспектов обработки XML.
Модули в Python
Модуль оформляется в виде отдельного файла с исходным кодом. Стандартные модули
находятся в каталоге, где их может найти соответствующий интерпретатор языка. Пути к
каталогам, в которых Python ищет модули, можно увидеть в значении переменной
sys.path:
>>> sys.path
['', '/usr/local/lib/python23.zip', '/usr/local/lib/python2.3',
'/usr/local/lib/python2.3/plat-linux2', '/usr/local/lib/python2.3/lib-tk',
'/usr/local/lib/python2.3/lib-dynload',
'/usr/local/lib/python2.3/site-packages']
В последних версиях Python модули можно помещать и в zip-архивы для более компактного
хранения (по аналогии с jar-архивами в Java).
22
При запуске программы поиск модулей также идет в текущем каталоге. (Нужно
внимательно называть собственные модули, чтобы не было конфликта имен со
стандартными или дополнительно установленными модулями.)
import os
import pre as re
from sys import argv, environ
from string import *
os.system("dir")
digits = re.compile("\d+")
print argv[0], environ
Повторный импорт модуля происходит гораздо быстрее, так как модули кэшируются
интерпретатором. Загруженный модуль можно загрузить еще раз (например, если модуль
изменился на диске) с помощью функции reload():
import mymodule
. . .
reload(mymodule)
Однако в этом случае все объекты, являющиеся экземплярами классов из старого варианта
модуля, не изменят своего поведения.
При работе с модулями есть и другие тонкости. Например, сам процесс импорта модуля
можно переопределить. Подробнее об этом можно узнать в оригинальной документации.
Встроенные функции
В среде Python без дополнительных операций импорта доступно более сотни встроенных
объектов, в основном, функций и исключений. Для удобства функции условно разделены
по категориям:
1. Функции преобразования типов и классы: coerce, str, repr, int, list, tuple, long,
float, complex, dict, super, file, bool, object
2. Числовые и строковые функции: abs, divmod, ord, pow, len, chr, unichr, hex, oct,
cmp, round, unicode
3. Функции обработки данных: apply, map, filter, reduce, zip, range, xrange, max,
min, iter, enumerate, sum
4. Функции определения свойств: hash, id, callable, issubclass, isinstance, type
5. Функции для доступа к внутренним структурам: locals, globals, vars, intern, dir
6. Функции компиляции и исполнения: eval, execfile, reload, __import__, compile
7. Функции ввода-вывода: input, raw_input, open
8. Функции для работы с атрибутами: getattr, setattr, delattr, hasattr
9. Функции-"украшатели" методов классов: staticmethod, classmethod, property
10. Прочие функции: buffer, slice
23
Совет:
>>> help(len)
Help on built-in function len:
len(...)
len(object) -> integer
Return the number of items of a sequence or mapping.
Или так:
Функции и классы из этой категории служат для преобразования типов данных. В старых
версиях Python для преобразования к нужному типу использовалась одноименная функция.
В новых версиях Python роль таких функций играют имена встроенных классов (однако
семантика не изменилась). Для понимания сути достаточно небольшого примера:
>>> int(23.5)
23
>>> float('12.345')
12.345000000000001
>>> dict([('a', 2), ('b', 3)])
{'a': 2, 'b': 3}
>>> object
<type 'object'>
>>> class MyObject(object):
... pass
...
24
encoding[, encoding. Ошибки кодирования обрабатываются в соответствии с errors,
errors]]) который может принимать значения: 'strict' (строгое преобразование),
'replace' (с заменой несуществующих символов) или 'ignore'
(игнорировать несуществующие символы). По умолчанию:
encoding='utf-8', errors='strict'.
>>> s = "abcde"
>>> s1 = "abcde"
>>> s2 = "ab" + "cde"
>>> print "hash:", hash(s), hash(s1), hash(s2)
hash: -1332677140 -1332677140 -1332677140
>>> print "id:", id(s), id(s1), id(s2)
id: 1076618592 1076618592 1076618656
Здесь, можно увидеть, что для одного и того же строкового литерала "abcde" получается
один и тот же объект, тогда как для одинаковых по значению объектов вполне можно
получить разные объекты.
Функция vars() возвращает таблицу локальных имен некоторого объекта (если параметр
не задан, она возвращает то же, что и locals()). Обычно используется в качестве словаря
для операции форматирования:
a = 1
25
b = 2
c = 3
print "%(a)s + %(b)s = %(c)s" % vars()
a = 2
b = 3
for op in "+-*/%":
e = "a " + op + " b"
print e, "->", eval(e)
У функции eval() кроме подлежащего вычислению выражения есть еще два параметра - с
их помощью можно задать глобальное и локальное пространства имен, из которых будут
разрешаться имена выражения. Пример выше, переписанный для использования с
собственным словарем имен в качестве глобального пространства имен:
for op in "+-*/%":
e = "a " + op + " b"
print e, "->", eval(e, {'a': 2, 'b': 3})
Функции ввода-вывода
f = open("file.txt", "r", 1)
for line in f:
. . .
f.close()
Функция принимает три аргумента: имя файла (путь к файлу), режим открытия ("r" -
чтение, "w" - запись, "a" - добавление или "w+", "a+", "r+" - изменение. Также может
прибавляться "t", что обозначает текстовый файл. Это имеет значение только на
платформе Windows). Третий аргумент указывает режим буферизации: 0 - без
буферизации, 1 - построчная буферизация, больше 1 - буфер указанного размера в байтах.
У объектов в языке Python могут быть атрибуты (в терминологии языка C++ - члены-
данные и члены-функции). Следующие две программы эквивалентны:
# первая программа:
class A:
pass
a = A()
26
a.attr = 1
try:
print a.attr
except:
print None
del a.attr
# вторая программа:
class A:
pass
a = A()
setattr(a, 'attr', 1)
if hasattr(a, 'attr'):
print getattr(a, 'attr')
else:
print None
delattr(a, 'attr')
1. Сервисы периода выполнения. Модули: sys, atexit, copy, traceback, math, cmath,
random, time, calendar, datetime, sets, array, struct, itertools, locale, gettext.
2. Поддержка цикла разработки. Модули: pdb, hotshot, profile, unittest, pydoc.
Пакеты docutils, distutils.
3. Взаимодействие с ОС (файлы, процессы). Модули: os, os.path, getopt, glob,
popen2, shutil, select, signal, stat, tempfile.
4. Обработка текстов. Модули: string, re, StringIO, codecs, difflib, mmap, sgmllib,
htmllib, htmlentitydefs. Пакет xml.
5. Многопоточные вычисления. Модули: threading, thread, Queue.
6. Хранение данных. Архивация. Модули: pickle, shelve, anydbm, gdbm, gzip, zlib,
zipfile, bz2, csv, tarfile.
7. Платформо-зависимые модули. Для UNIX: commands, pwd, grp, fcntl, resource,
termios, readline, rlcompleter. Для Windows: msvcrt, _winreg, winsound.
8. Поддержка сети. Протоколы Интернет. Модули: cgi, Cookie, urllib, urlparse,
httplib, smtplib, poplib, telnetlib, socket, asyncore. Примеры серверов:
SocketServer, BaseHTTPServer, xmlrpclib, asynchat.
9. Поддержка Internet. Форматы данных. Модули: quopri, uu, base64, binhex,
binascii, rfc822, mimetools, MimeWriter, multifile, mailbox. Пакет email.
10. Python о себе. Модули: parser, symbol, token, keyword, inspect, tokenize, pyclbr,
py_compile, compileall, dis, compiler.
11. Графический интерфейс. Модуль Tkinter.
Примечание:
Очень часто модули содержат один или несколько классов, с помощью которых создается
объект нужного типа, а затем речь идет уже не об именах из модуля, а об атрибутах этого
объекта. И наоборот, некоторые модули содержат лишь функции, слишком общие для того,
чтобы работать над произвольными объектами (либо достаточно большой категорией
объектов).
27
Модуль sys
Модуль copy
lst1 = [0, 0, 0]
lst = [lst1] * 3
print lst
lst[0][1] = 1
print lst
Дело в том, что список lst содержит ссылки на один и тот же список! Для того чтобы
действительно размножить список, необходимо применить функцию copy() из модуля
copy:
В модуле copy есть еще и функция deepcopy() для глубокого копирования, при которой
объекты копируются на всю возможную глубину, рекурсивно.
28
В этих модулях собраны математические функции для действительных и комплексных
аргументов. Это те же функции, что используются в языке C. В таблице ниже даны
функции модуля math. Там, где аргумент обозначен буквой z, аналогичная функция
определена и в модуле cmath.
Функция или
Описание
константа
acos(z) арккосинус z
asin(z) арксинус z
atan(z) арктангенс z
atan2(y,x) atan(y/x)
ceil(x) наименьшее целое, большее или равное x
cos(z) косинус z
cosh(x) гиперболический косинус x
e константа e
exp(z) экспонента (то есть, e**z)
fabs(x) абсолютное значение x
floor(x) наибольшее целое, меньшее или равное x
fmod(x,y) остаток от деления x на y
frexp(x) возвращает мантиссу и порядок x как пару (m, i), где m - число с
плавающей точкой, а i - целое, такое, что x = m * 2.**i. Если 0,
возвращает (0,0), иначе 0.5 <= abs(m) < 1.0
hypot(x,y) sqrt(x*x + y*y)
ldexp(m,i) m * (2**i)
log(z) натуральный логарифм z
log10(z) десятичный логарифм z
modf(x) возвращает пару (y,q) - целую и дробную часть x. Обе части имеют знак
исходного числа
pi константа пи
pow(x,y) x**y
sin(z) синус z
sinh(z) гиперболический синус z
sqrt(z) корень квадратный от z
tan(z) тангенс z
tanh(z) гиперболический тангенс z
Модуль random
29
randrange([start,] Выдает случайное целое число из диапазона range(start, stop,
stop[, step]) step). Аналогично choice(range(start, stop, step)).
normalvariate(mu, Выдает число из последовательности нормально распределенных
sigma) псевдослучайных чисел. Здесь mu - среднее, sigma -
среднеквадратическое отклонение (sigma > 0)
Модуль time
Этот модуль дает функции для получения текущего времени и преобразования форматов
времени.
Модуль sets
Модуль реализует тип данных для множеств. Следующий пример показывает, как
использовать этот модуль. Следует заметить, что в Python 2.4 и старше тип set стал
встроенным, и вместо sets.Set можно использовать set:
import sets
A = sets.Set([1, 2, 3])
B = sets.Set([2, 3, 4])
print A | B, A & B, A - B, A ^ B
for i in A:
if i in B:
print i,
Модуль itertools
Этот модуль содержит набор функций для работы с итераторами. Итераторы позволяют
работать с данными последовательно, как если бы они получались в цикле.
Альтернативный подход - использование списков для хранения промежуточных
результатов - требует подчас большого количества памяти, тогда как использование
итераторов позволяет получать значения на момент, когда они действительно требуются
для дальнейших вычислений. Итераторы будут рассмотрены более подробно в лекции по
функциональному программированию.
Модуль locale
30
locale.setlocale(locale.LC_ALL, None)
print time.strftime("%d %B %Y", time.localtime (time.time()))
locale.setlocale(locale.LC_ALL, "ru_RU.KOI8-R")
print time.strftime("%d %B %Y", time.localtime (time.time()))
В результате:
18 November 2004
18 Ноября 2004
Модуль gettext
import sets
import math
"""Модуль для вычисления простых чисел от 2 до N """
def primes(N):
"""Возвращает все простые от 2 до N"""
sieve = sets.Set(range(2, N))
for i in range(2, math.sqrt(N)):
if i in sieve:
sieve -= sets.Set(range(2*i, N, i))
return sieve
Модуль pdb
31
18 sieve -= sets.Set(range(2*i, N, i))
19 return sieve
20
(Pdb) n
> /home/rnd/workup/intuit-python/examples/Sieve.py(16)primes()
-> for i in range(2, int(math.sqrt(N))):
(Pdb) n
> /home/rnd/workup/intuit-python/examples/Sieve.py(17)primes()
-> if i in sieve:
(Pdb) n
> /home/rnd/workup/intuit-python/examples/Sieve.py(18)primes()
-> sieve -= sets.Set(range(2*i, N, i))
(Pdb) n
> /home/rnd/workup/intuit-python/examples/Sieve.py(16)primes()
-> for i in range(2, int(math.sqrt(N))):
(Pdb) p sieve
Set([2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39,
41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79,
81, 83, 85, 87, 89, 91, 93, 95, 97, 99])
(Pdb) n
> /home/rnd/workup/intuit-python/examples/Sieve.py(17)primes()
-> if i in sieve:
(Pdb) n
> /home/rnd/workup/intuit-python/examples/Sieve.py(18)primes()
-> sieve -= sets.Set(range(2*i, N, i))
(Pdb) n
> /home/rnd/workup/intuit-python/examples/Sieve.py(16)primes()
-> for i in range(2, int(math.sqrt(N))):
(Pdb) p sieve
Set([2, 3, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35, 37, 41, 43, 47, 49,
53, 55, 59, 61, 65, 67, 71, 73, 77, 79, 83, 85, 89, 91, 95, 97])
Модуль profile
>>> profile.run("Sieve.primes(100000)")
709 function calls in 1.320 CPU seconds
Здесь ncalls - количество вызовов функции или метода, tottime - полное время
выполнения кода функции (без времени нахождения в вызываемых функциях), percall -
тоже, в пересчете на один вызов, cumtime - аккумулированное время нахождения в
функции, вместе со всеми вызываемыми функциями. В последнем столбце приведено имя
файла, номер строки с функцией или методов и его имя.
32
Примечание:
Модуль unittest
# file: test_Sieve.py
import Sieve, sets
import unittest
class TestSieve(unittest.TestCase):
def setUp(self):
pass
def testone(self):
primes = Sieve.primes(1)
self.assertEqual(primes, sets.Set())
def test100(self):
primes = Sieve.primes(100)
self.assert_(primes == sets.Set([2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47,
53, 59, 61, 67, 71, 73, 79, 83, 89, 97]))
if __name__ == '__main__':
unittest.main()
$ python test_Sieve.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
Кстати, сам Python и его стандартная библиотека имеют тесты для каждого модуля - они
находятся в каталоге test в месте, где развернуты файлы поставки Python, и являются
частью пакета test.
33
Модуль pydoc
$ pydoc Sieve
Help on module Sieve:
NAME
Sieve - Модуль для вычисления простых чисел от 2 до N
FILE
Sieve.py
FUNCTIONS
primes(N)
Возвращает все простые от 2 до N
Эта страница помощи появилась благодаря тому, что были написаны строки документации
- как ко всему модулю, так и к функции primes(N).
pydoc -p 8088
Пакет docutils
Этот пакет и набор утилит пока что не входит в стандартную поставку Python, однако о нем
нужно знать тем, кто хочет быстро готовить документацию (руководства пользователя и
т.п.) для своих модулей. Этот пакет использует специальный язык разметки
(ReStructuredText), из которого потом легко получается документация в виде HTML, LaTeX и
в других форматах. Текст в формате RST легко читать и в исходном виде. С этим
инструментом можно познакомиться на http://docutils.sourceforge.net
Пакет distutils
Модуль os
import os
PATH = os.environ['PATH']
35
stat(path) Возвращает информацию о path в виде не менее чем
десятиэлементного кортежа. Для доступа к элементам кортежа можно
использовать константы из модуля stat, например stat.ST_MTIME
(время последней модификации файла).
utime(path, Устанавливает значения времен последней модификации (mtime) и
times) доступа к файлу (atime). Если times равен None, в качестве времен
берется текущее время. В других случаях times рассматривается как
двухэлементный кортеж (atime, mtime). Для получения atime и mtime
некоторого файла можно использовать stat() совместно с
константами модуля stat.
Модуль stat
В этом модуле описаны константы, которые можно использовать как индексы к кортежам,
применяемым функциями os.stat() и os.chmod() (а также некоторыми другими). Их
можно уточнить в документации по Python.
Модуль tempfile
import tempfile
f = tempfile.TemporaryFile()
f.write("0"*100) # записывается сто символов 0
f.seek(0) # уст. указатель на начало файла
print len(f.read()) # читается до конца файла и вычисляется длина
Как и следовало ожидать, в результате будет выведено 100. Временный файл будет
удален, как только будут удалены все ссылки на его объект.
Обработка текстов
Многопоточные вычисления
Модуль pickle
Сохранение:
Восстановление:
import pickle
input_file = open("mydata.dat", "r")
mydata = pickle.load(input_file)
print mydata
input_file.close()
Модуль shelve
Для хранения объектов в родном для Python формате можно применять полку (shelve). По
своему интерфейсу полка ничем не отличается от словаря. Следующий пример показывает,
как использовать полку:
import shelve
data = ("abc", 12) # - данные (объект)
key = "key" # - ключ (строка)
filename = "polka.dat" # - имя файла для хранения полки
d = shelve.open(filename) # открытие полки
d[key] = data # сохранить данные под ключом key
# (удаляет старое значение, если оно было)
data = d[key] # загрузить значение по ключу
len(d) # получить количество объектов на полке
d.sync() # запись изменений в БД на диске
del d[key] # удалить ключ и значение
flag = d.has_key(key) # проверка наличия ключа
lst = d.keys() # список ключей
d.close() # закрытие полки
Доступ к хэшу из Python мало отличается от доступа к словарю. Разница лишь в том, что
хэш еще нужно открыть для создания, чтения или записи, а затем закрыть. Кроме того, при
записи хэш блокируется, чтобы не испортить данные.
Модуль csv
# Запись в файл:
f = file("my.csv", "w")
writer = csv.writer(f)
for row in mydata:
writer.writerow(row)
f.close()
# Чтение из файла:
reader = csv.reader(file("my.csv"))
for row in reader:
print row
Платформо-зависимые модули
Эта категория модулей имеет применение только для конкретных операционных систем и
семейств операционных систем. Довольно большое число модулей в стандартной поставке
Python посвящено трем платформам: Unix, Windows и Macintosh.
В документации по языку обычно отмечено, для каких платформ доступен тот или иной
модуль или даже отдельная функция.
Сегодня наиболее мощным инструментом для обработки сообщений в формате RFC 2822
является пакет email. С его помощью можно как разбирать сообщения в удобном для
программной обработки виде, так и формировать сообщение на основе данных о полях и
основном содержимом (включая вложения).
Python о себе
Графический интерфейс
Заключение
В этой лекции говорилось о встроенных функциях языка Python и модулях его стандартной
библиотеки. Некоторые направления будут рассмотрены более подробно в следующих
лекциях. Python имеет настолько обширную стандартную библиотеку, что в рамках одной
лекции можно только сделать ее краткий обзор, подкрепив небольшими примерами
наиболее типичные идиомы при использовании модулей.
39
Лекция #3: Элементы функционального
программирования
Что такое функциональное программирование?
Функциональное программирование - это стиль программирования, использующий
только композиции функций. Другими словами, это программирование в выражениях, а не
в императивных командах.
Функциональная программа
В математике функция отображает объекты из одного множества (множества
определения функции) в другое (множество значений функции). Математические
функции (их называют чистыми) "механически", однозначно вычисляют результат по
заданным аргументам. Чистые функции не должны хранить в себе какие-либо данные
между двумя вызовами. Их можно представлять себе черными ящиками, о которых
известно только то, что они делают, но совсем не важно, как.
Кстати, бинарные операции "+", "-", "*", "/", которые записываются в выражениях,
являются "математическими" функциями над двумя аргументами -- операндами. Их
используют настолько часто, что синтаксис языка программирования имеет для них более
короткую запись. Модуль operator позволяет представлять эти операции в
функциональном стиле:
40
Как уже говорилось, определить функцию в Python можно двумя способами: с помощью
оператора def и lambda-выражения. Первый способ позволяет использовать операторы.
При втором - определение функции может быть только выражением.
Забегая вперед, можно заметить, что методы классов определяются так же, как и функции.
Отличие состоит в специальном смысле первого аргумента self (в нем передается
экземпляр объекта).
def swapcase(s):
return s.swapcase()
print swapcase("ABC")
print inc(12)
print inc(12, 2)
def max_min(*args):
# args - список аргументов в порядке их указания при вызове
return max(args), min(args)
print swiss_knife(1)
print swiss_knife(1, 2, 3, 4, 5)
print swiss_knife(1, 2, 3, a='abc', b='sdf')
# print swiss_knife(1, a='abc', 3, 4) # !!! ошибка
lst = [2, 3, 4, 5]
dct = {'a': 'abc', 'b': 'sdf'}
print swiss_knife(1, *lst, **dct)
func = lambda x, y: x + y
В языке Python функция может возвратить только одно значение, которое может быть
кортежем. В следующем примере видно, как стандартная функция divmod() возвращает
частное и остаток от деления двух чисел:
def bin(n):
"""Цифры двоичного представления натурального числа """
digits = []
while n > 0:
n, d = divmod(n, 2)
digits = [d] + digits
return digits
print bin(69)
Примечание:
Важно понять, что за именем функции стоит объект. Этот объект можно связать с другим
именем:
print mylist(1),
print mylist(2)
Вместо ожидаемого [1] [2] получается [1] [1, 2], так как добавляются элементы к
"значению по умолчанию".
Рекурсия
В некоторых случаях описание функции элегантнее всего выглядит с применением вызова
этой же функции. Такой прием, когда функция вызывает саму себя, называется
рекурсией. В функциональных языках рекурсия обычно используется много чаще, чем
итерация (циклы).
def bin(n):
"""Цифры двоичного представления натурального числа """
if n == 0:
return []
n, d = divmod(n, 2)
return bin(n) + [d]
print bin(69)
Здесь видно, что цикл while больше не используется, а вместо него появилось условие
окончания рекурсии: условие, при выполнении которого функция не вызывает себя.
def Fib(n):
if n < 2:
return n
else:
return Fib(n-1) + Fib(n-2)
Предупреждение:
Функция apply()
Обработка последовательностей
Многие алгоритмы сводятся к обработке массивов данных и получению новых массивов
данных в результате. Среди встроенных функций Python есть несколько для работы с
последовательностями.
Функция range() уже упоминалась при рассмотрении цикла for. Эта функция принимает от
одного до трех аргументов. Если аргумент всего один, она генерирует список чисел от 0
(включительно) до заданного числа (исключительно). Если аргументов два, то список
начинается с числа, указанного первым аргументом. Если аргументов три - третий аргумент
задает шаг
44
Функция map()
>>> l1 = [2, 7, 5, 3]
>>> l2 = [-2, 1, 0, 4]
>>> print map(lambda x, y: x+y, l1, l2)
[0, 8, 5, 7]
В этом примере применена безымянная функция для получения суммы двух операндов ко
всем элементам l1 и l2. В случае если одна из последовательностей короче другой, вместо
соответствующего операнда будет None, что, конечно, собьет операцию сложения. В
зависимости от решаемой задачи, можно либо видоизменить функцию, либо считать
разные по длине последовательности ошибкой, которую нужно обрабатывать как
отдельную ветвь алгоритма.
>>> l1 = [2, 7, 5, 3]
>>> l2 = [-2, 1, 0, 4]
>>> print map(None, l1, l2)
[(2, -2), (7, 1), (5, 0), (3, 4)]
Функция filter()
Списковые включения
Для более естественной записи обработки списков в Python 2 была внесена новинка:
списковые включения. Фактически это специальный сокращенный синтаксис для
вложенных циклов for и условий if, на самом низком уровне которых определенное
выражение добавляется к списку, например:
all_pairs = []
for i in range(5):
for j in range(5):
if i <= j:
45
all_pairs.append((i, j))
Как легко заметить, списковые включения позволяют заменить map() и filter() на более
удобные для прочтения конструкции.
Функция sum()
>>> sum(range(10))
45
Эта функция работает только для числовых типов, она не может конкатенировать строки.
Для конкатенации списка строк следует использовать метод join().
Функция reduce()
Следует помнить, что в качестве передаваемого объекта может оказаться список, который
позволит накапливать промежуточные результаты. Тем самым, reduce() может
использоваться для генерации последовательностей.
lst = range(10)
f = lambda x, y: (x[0] + y, x[1]+[x[0] + y])
print reduce(f, lst, (0, []))
В итоге получается:
Функция zip()
Эта функция возвращает список кортежей, в котором i-й кортеж содержит i-е элементы
аргументов-последовательностей. Длина результирующей последовательности равна длине
самой короткой из последовательностей-аргументов:
46
>>> print zip(range(5), "abcde")
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]
Итераторы
Применять для обработки данных явные последовательности не всегда эффективно, так
как на хранение временных данных может тратиться много оперативной памяти. Более
эффективным решением представляется использование итераторов - специальных
объектов, обеспечивающих последовательный доступ к данным контейнера. Если в
выражении есть операции с итераторами вместо контейнеров, промежуточные данные не
будут требовать много места для хранения - ведь они запрашиваются по мере
необходимости для вычислений. При обработке данных с использованием итераторов
память будет требоваться только для исходных данных и результата, да и то необязательно
вся сразу - ведь данные могут читаться и записываться в файл на диске.
Функция iter()
Эта функция имеет два варианта использования. В первом она принимает всего один
аргумент, который должен "уметь" предоставлять свой итератор. Во втором один из
аргументов - функция без аргументов, другой - стоповое значение. Итератор вызывает
указанную функцию до тех пор, пока та не возвратит стоповое значение. Второй вариант
встречается много реже первого и обычно внутри метода класса, так как сложно порождать
значения "на пустом месте":
def forit(mystate=[]):
if len(mystate) < 3:
mystate.append(" ")
return " "
Если функция не возвращает значения явно, она возвращает None, что и использовано в
47
примере выше.
Функция enumerate()
Функция sorted()
>>> sorted('avdsdf')
['a', 'd', 'd', 'f', 's', 'v']
Функция itertools.chain()
даст в результате
1 2 3 8 9 0
Функция itertools.repeat()
1 1 1 1
Функция itertools.count()
for i in itertools.count(1):
print i,
if i > 100:
break
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
48
96 97 98 99 100 101
Функция itertools.cycle()
tango = [1, 2, 3]
for i in itertools.cycle(tango):
print i,
1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2
3 1 2 3 1
2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
1 2 3 1 2
3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 . . .
(1, 1) (2, 2)
Здесь следует заметить, что обычная функция map() нормально воспринимает итераторы в
любом сочетании с итерабельными (поддающимися итерациям) объектами:
Функция ifilter() работает как filter(). Кроме того, в модуле itertools есть функция
ifilterfalse(), которая как бы добавляет отрицание к значению функции:
-2 -3
print
for i in dropwhile(lambda x: x > 0, [1, -2, 3, -3]):
print i,
1
-2 3 -3
Таким образом, takewhile() дает значения, пока условие истинно, а остальные значения
даже не берет из итератора (именно не берет, а не высасывает все до конца!). И,
наоборот, dropwhile() ничего не выдает, пока выполняется условие, зато потом выдает
все без остатка.
Функция itertools.izip()
Функция itertools.groupby()
Эта функция дебютировала в Python 2.4. Функция принимает два аргумента: итератор
(обязательный) и необязательный аргумент - функцию, дающую значение ключа:
groupby(iterable[, func]). Результатом является итератор, который возвращает
двухэлементный кортеж: ключ и итератор по идущим подряд элементам с этим ключом.
Если второй аргумент опущен, элемент итератора сам является ключом. В следующем
примере группируются идущие подряд положительные и отрицательные элементы:
Функция itertools.tee()
Эта функция тоже появилась в Python 2.4. Она позволяет клонировать итераторы. Первый
аргумент - итератор, подлежащий клонированию. Второй (N) -- количество необходимых
копий. Функция возвращает кортеж из N итераторов. По умолчанию N=2. Функция имеет
смысл, только если итераторы задействованы более или менее параллельно. В противном
случае выгоднее превратить исходный итератор в список.
Собственный итератор
class Fibonacci:
"""Итератор последовательности Фибоначчи до N"""
def __iter__(self):
# сами себе итератор: в классе есть метод next()
return self
50
def next(self):
if self.n < self.max:
a, self.n, self.a, self.b = self.a, self.n+1, self.b, self.a+self.b
return a
else:
raise StopIteration
# Использование:
for i in Fibonacci(100):
print i,
Простые генераторы
Разработчики языка не остановились на итераторах. Как оказалось, в интерпретаторе
Python достаточно просто реализовать простые генераторы. Под этим термином
фактически понимается специальный объект, вычисления в котором продолжаются до
выработки очередного значения, а затем приостанавливаются до возникновения
необходимости в выдаче следующего значения. Простой генератор формируется функцией-
генератором, которая синтаксически похожа на обычную функцию, но использует
специальный оператор yield для выдачи следующего значения. При вызове такая функция
ничего не вычисляет, а создает объект с интерфейсом итератора для получения значений.
Другими словами, если функция должна возвращать последовательность, из нее довольно
просто сделать генератор, который будет функционально эквивалентной "ленивой"
реализацией. Ленивыми называются вычисления, которые откладываются до самого
последнего момента, когда получаемое в результате значение сразу используется в другом
вычислении.
def Fib(N):
a, b = 0, 1
for i in xrange(N):
yield a
a, b = b, a + b
for i in Fib(100):
print i,
Генераторное выражение
51
open("output.dat", "w").writelines(
l.replace(" - ", " - ") for l in open("input.dat"))
Здесь для генераторного выражения не потребовалось дополнительных скобок, так как оно
расположено внутри скобок вызова функции.
Карринг
Библиотека Xoltar toolkit (автор Bryn Keller) включает модуль functional, который
позволяет упростить использование возможностей функционального программирования.
Модуль functional применяет "чистый" Python. Библиотеку можно найти по адресу:
http://sourceforge.net/projects/xoltar-toolkit.
print subtract(3, 2)
subtract_from_3 = curry(subtract, 3)
print subtract_from_3(2)
print curry(subtract, 3)(2)
Во всех трех случаях будет выведено 1. В следующем примере получается новая функция,
подставляя второй аргумент. Вместо другого аргумента вставляется специальное значение
Blank:
print subtract(3, 2)
subtract_2 = curry(subtract, Blank, 2)
print subtract_2(3)
print curry(subtract, Blank, 2)(3)
Заключение
В этой лекции рассматривался принцип построения функциональных программ. Кроме того,
было показано, что в Python и его стандартных модулях имеются достаточно мощные
выразительные средства для создания функциональных программ. В случае, когда
требуются дополнительные возможности, например, карринг, их можно легко реализовать
или взять готовую реализацию.
Ссылки по теме
Статья Д. Мертца http://www-106.ibm.com/developerworks/library/l-prog.html
52
Лекция #4: Объектно-ориентированное
программирование
Python проектировался как объектно-ориентированный язык программирования. Это
означает (по Алану Кэю, автору объектно-ориентированного языка Smalltalk), что он
построен с учетом следующих принципов:
Язык Python имеет достаточно мощную, но, вместе с тем, самобытную поддержку объектно-
ориентированного программирования. В этой лекции ООП представляется без лишних
формальностей. Работа с Python убеждает, что писать программы в объектно-
ориентированном стиле не только просто, но и приятно.
Примечание:
Основные понятия
При процедурном программировании программа разбивается на части в соответствии с
алгоритмом: каждая часть (подпрограмма, функция, процедура) является составной
частью алгоритма.
53
Каждый объект хранит свое состояние (для этого у него есть атрибуты) и имеет
определенный набор методов. (Синонимы: атрибут, поле, слот, объект-член,
переменная экземпляра). Методы определяют поведение объекта. Объекты класса
имеют общее поведение.
Класс определяет тип объекта, то есть его возможные состояния и набор операций.
Абстракция и декомпозиция
Абстракция в ООП позволяет составить из данных и алгоритмов обработки этих данных
объекты, отвлекаясь от несущественных (на некотором уровне) с точки зрения
составленной информационной модели деталей. Таким образом, программа подвергается
декомпозиции на части "дозированной" сложности. Отдельный объект, даже вместе с
совокупностью его связей с другими объектами, человеком воспринимается легче (именно
так он привык оперировать в реальном мире), чем что-то неструктурированное и
монотонное.
Объекты
До этой лекции объекты Python встречались много раз: ведь каждое число, строка,
функция, модуль и т.п. - это объекты. Некоторые встроенные объекты имеют в Python
синтаксическую поддержку (для задания литералов). Таковы числа, строки, списки,
кортежи и некоторые другие типы.
Теперь следует посмотреть на них в свете только что приведенных определений. Пример:
54
a = 3
b = 4.0
c = a + b
Здесь происходит следующее. Сначала имя "a" связывается в локальном пространстве имен
с объектом-числом 3 (целое число). Затем "b" связывается с объектом-числом 4.0 (число с
плавающей точкой). После этого над объектами 3 и 4.0 выполняется операция сложения, и
имя "c" связывается с получившимся объектом. Кстати, операциями, в основном, будут
называться методы, которые имеют в Python синтаксическую поддержку, в данном случае -
инфиксную запись. То же самое можно записать как:
c = a.__add__(b)
Здесь __add__() - метод объекта a, который реализует операцию + между этим объектом и
другим объектом.
Узнать набор методов некоторого объекта можно с помощью встроенной функции dir():
>>> dir(a)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__',
'__delattr__', '__div__', '__divmod__', '__doc__', '__float__',
'__floordiv__', '__getattribute__', '__getnewargs__', '__hash__',
'__hex__', '__init__', '__int__', '__invert__', '__long__',
'__lshift__', '__mod__', '__mul__', '__neg__', '__new__',
'__nonzero__', '__oct__', '__or__', '__pos__', '__pow__',
'__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__',
'__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__',
'__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__',
'__rshift__', '__rsub__', '__rtruediv__', '__rxor__',
'__setattr__', '__str__', '__sub__', '__truediv__', '__xor__']
Здесь стоит указать на еще одну особенность Python. Не только инфиксные операции, но и
встроенные функции ожидают наличия некоторых методов у объекта. Например, можно
записать:
abs(c)
c.__abs__()
a = 1
# ...
del a
# имени a больше нет
Типы и классы
Тип определяет область допустимых значений объекта и набор операций над ним. В ООП
тип тесно связан с поведением - действиями объекта, состоящими в изменении
внутреннего состояния и вызовами методов других объектов.
Примечание:
Пока что в Python есть "классические" и "новые" классы. Первые классы определяются
сами по себе, а вторые обязательно ведут свою родословную от класса object. Для целей
данного изложения разница между этими видами классов не имеет значения.
В этом примере модуль sets содержит определение класса Set. Вызывается конструктор
этого класса с параметром [1, 2, 3]. В результате с именем s будет связан объект-
множество из трех элементов 1, 2, 3.
Определение класса
Пусть в ходе анализа данной предметной области необходимо определить класс Граф.
Граф - это множество вершин и набор ребер, попарно соединяющий эти вершины. Над
графом можно проделывать операции, такие как добавление вершины, ребра, проверка
наличия ребра в графе и т.п. На языке Python определение класса может выглядеть так:
class G:
def __init__(self, V, E):
self.vertices = set(V)
self.edges = set(E)
def __str__(self):
return "%s; %s" % (self.vertices, self.edges)
56
Использовать класс можно следующим образом:
print g
g.add_vertex(5)
g.add_edge((5,6))
print g.has_edge((1,6))
print g
print isinstance(g, G)
Инкапсуляция
Обычно считается, что без инкапсуляции невозможно представить себе ООП, что это
ключевое понятие. История развития методологий программирования движима борьбой со
сложностью разработки программного обеспечения. Сложность больших программных
систем, в создании которых участвует сразу большое количество разработчиков,
уменьшается, если на верхнем уровне не видно деталей реализации нижних уровней.
Собственно, процедурный подход был первым шагом на этом пути. Под инкапсуляцией
(incapsulation, что можно перевести по-разному, но на нужные ассоциации хорошо наводит
слово "обволакивание") понимается сокрытие информации о внутреннем устройстве
объекта, при котором работа с объектом может вестись только через его общедоступный
(public) интерфейс. Таким образом, другие объекты не должны вмешиваться в "дела"
объекта, кроме как используя вызовы методов.
Доступ к свойствам
В языке Python не считается зазорным получить доступ к некоторому атрибуту (не методу)
напрямую, если, конечно, этот атрибут описан в документации как часть интерфейса
класса. Такие атрибуты называются свойствами (properties). В других языках
программирования принято для доступа к свойствам создавать специальные методы
(вместо того чтобы напрямую обращаться к общедоступным членам-данным). В Python
достаточно использовать ссылку на атрибут, если свойство ни на что в объекте не влияет
(то есть другие объекты могут его произвольно менять). Если же свойство менее
тривиально и требует каких-то действий в самом объекте, его можно описать как свойство
(пример взят из документации к Python):
class C(object):
def getx(self): return self.__x
def setx(self, value): self.__x = value
57
def delx(self): del self.__x
x = property(getx, setx, delx, "I'm the 'x' property.")
>>> c = C()
>>> c.x = 1
>>> print c.x
1
>>> del c.x
Следует отметить, что в экземпляре класса в Python можно организовать доступ к любым
(даже несуществующим) атрибутам, обрабатывая запрос на доступ к атрибуту группой
специальных методов:
class AttDict(object):
def __init__(self, dict=None):
object.__setattr__(self, '_selfdict', dict or {})
58
print ad.a, ad.b, ad.c
ad.d = 512
print ad.d
Сокрытие данных
>>> class X:
... x = 0
... _x = 0
... __x = 0
...
>>> dir(X)
['_X__x', '__doc__', '__module__', '_x', 'x']
Полиморфизм
В переводе с греческого полиморфизм означает "многоформие". Так в информатике
называют возможность использования одного имени для выполнения различных действий.
При написании функции в Python обычно не проверяется, к какому типу (классу) относится
тот или иной аргумент: некоторые методы просто применяются к переданному объекту. Тем
самым функции получаются максимально обобщенными: они не требуют от объектов-
параметров большего, чем наличие методов с определенным именем, набором аргументов и
семантикой.
def get_last(x):
return x[-1]
Описанной функции будет подходить в качестве аргумента все, от чего можно взять индекс
-1 (последний элемент). Однако семантика "взятие последнего элемента" выполняется
только для последовательностей. Функция будет работать и для словарей, но смысл при
этом будет немного другой.
59
Имитация типов
class CountArgs(object):
def __call__(self, *args, **kwargs):
return len(args) + len(kwargs)
cc = CountArgs()
print cc(1, 3, 4)
Как видно из этого примера, экземпляры класса CountArgs можно вызывать подобно
функциям (в результате будет возвращено количество переданных параметров). При
попытке вызова экземпляра на самом деле будет вызван метод __call__() со всеми
аргументами.
class Point:
def __init__(self, x, y):
self.coord = (x, y)
def __nonzero__(self):
return self.coord[0] != 0 or self.coord[1] != 0
def __cmp__(self, p):
return cmp(self.coord, p.coord)
Программа выведет:
. * * * * * *
. . * * * * *
. . . * * * *
. . . o * * *
. . . . . * *
. . . . . . *
. . . . . . .
В данной программе класс Point (Точка) имеет метод __nonzero__(), который определяет
истинностное значение объекта класса. Истину будут давать только точки, отличные от (0,
0). Другой метод - __cmp__() - вызывается при необходимости сравнить точку и другой
объект (имеющий как и точка атрибут coord, который содержит кортеж как минимум из
двух элементов). Нужно заметить, что вместо __cmp__ можно определить отдельные методы
для операций сравнения: __lt__, __le__, __ne__, __eq__, __ge__, __gt__ (для <, <=, !=, <>,
=>, > соответственно).
class Plussable:
60
def __add__(self, x):
...
def __radd__(self, x):
...
def __iadd__(self, x):
...
Здесь метод __add__() вызывается, когда экземпляр класса Plussable стоит слева от
сложения, __radd__() - если справа от сложения и метод слева от него не имеет метода
__add__(). Метод __iadd__() нужен для реализации +=.
Наследование
В языке Python во главе иерархии ("новых") классов стоит класс object. Для ориентации в
иерархии существуют некоторые встроенные функции, которые будут рассмотрены ниже.
Функция issubclass(x, y) может сказать, является ли класс x подклассом класса y:
Множественное наследование
61
В отличие, например, от Java, в языке Python можно наследовать класс от нескольких
классов. Такая ситуация называется множественным наследованием (multiple
inheritance).
class A:
def a(self): return 'a'
class B:
def b(self): return 'b'
class C:
def c(self): return 'c'
class AB:
a = ma
b = mb
class BC:
b = mb
c = mc
class ABC:
a = ma
b = mb
c = mc
62
В случае, когда надклассы имеют одинаковые методы, использование того или иного
метода определяется порядком разрешения методов (method resolution order). Для
"новых" классов узнать этот порядок очень просто с помощью атрибута __mro__:
>>> str.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)
Это означает, что сначала методы ищутся в классе str, затем в basestring, а уже потом - в
object.
Агрегация
Контейнеры
class Stack:
def __init__(self):
"""Инициализация стека"""
self._stack = []
def top(self):
"""Возвратить вершину стека (не снимая)"""
return self._stack[-1]
def pop(self):
"""Снять со стека элемент"""
return self._stack.pop()
def push(self, x):
"""Поместить элемент на стек"""
self._stack.append(x)
def __len__(self):
"""Количество элементов в стеке"""
return len(self._stack)
def __str__(self):
"""Представление в виде строки"""
return " : ".join(["%s" % e for e in self._stack])
Использование:
>>> s = Stack()
>>> s.push(1)
>>> s.push(2)
>>> s.push("abc")
>>> print s.pop()
abc
>>> print len(s)
2
>>> print s
1 : 2
63
Таким образом, контейнеры позволяют управлять набором (любых) других объектов в
соответствии со структурой их хранения, не вмешиваясь во внутренние дела объектов.
Узнав интерфейс класса Stack, можно и не догадаться, что он реализован на основе
списка, и каким именно образом он реализован с помощью него. Но для использования
стека это не важно.
Примечание:
Итераторы
class Zahlreim:
def __init__(self, lst, n):
self.n = n
self.lst = lst
self.current = 0
def __iter__(self):
return self
def next(self):
if self.lst:
self.current = (self.current + self.n - 1) % len(self.lst)
return self.lst.pop(self.current)
else:
raise StopIteration
Программа выдаст
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
5 10 6 2 9 8 1 4 7 3
Ассоциация
Если в случае агрегации имеется довольно четкое отношение "ИМЕЕТ" (HAS-A) или
"СОДЕРЖИТСЯ-В", которое даже отражено в синтаксисе Python:
lst = [1, 2, 3]
64
if 1 in lst:
...
В языке Python границы между агрегацией и ассоциацией несколько размыты, так как
объекты при агрегации обычно не хранятся в области памяти, выделенной под контейнер
(хранятся только ссылки).
Объекты могут также ссылаться друг на друга. В этом случае возникают циклические
ссылки, которые при неаккуратном использовании могут привести (в старых версиях
Python) к утечкам памяти. В новых версиях Python для циклических ссылок работает
сборщик мусора.
Например, при представлении дерева ссылки могут идти от родителей к детям и обратно от
каждого дочернего узла к родительскому.
Слабые ссылки
Для работы со слабыми ссылками применяется модуль weakref. Основные принципы его
работы станут понятны из следующего примера:
Статический метод
65
Иногда необходимо использовать метод, принадлежащий классу, а не его экземпляру. В
этом случае можно описать статический метод. До появления декораторов (до Python 2.4)
определять статический метод приходилось следующим образом:
class A(object):
def name():
return A.__name__
name = staticmethod(name)
print A.name()
a = A()
print a.name()
В Python 2.4 для применения описателей (descriptors) был придуман новый синтаксис -
декораторы:
class A(object):
@staticmethod
def name():
return A.__name__
Смысл декоратора в том, что он "пропускает" определяемую функцию (или метод) через
заданную в нем функцию. Теперь писать name три раза не потребовалось. Декораторов
может быть несколько, и применяются они в обратном порядке.
Метод класса
Если статический метод имеет свои аналоги в C++ и Java, то метод класса основан на том,
что в Python классы являются объектами. В отличие от статического метода, в метод класса
первым параметром передается объект-класс. Вместо self для подчеркивания
принадлежности метода к методам класса принято использовать cls.
Пример использования метода класса можно найти в модуле tree пакета nltk (Natural
Language ToolKit, набор инструментов для естественного языка). Ниже приведен лишь
фрагмент определения класса Tree (базового класса для других подклассов). Метод
convert класса Tree определяет процедуру преобразования дерева одного типа в дерево
другого типа. Эта процедура абстрагируется от деталей реализации конкретных типов,
описывая обобщенный алгоритм преобразования:
class Tree:
# ...
def convert(cls, val):
if isinstance(val, Tree):
children = [cls.convert(child) for child in val]
return cls(val.node, children)
else:
return val
convert = classmethod(convert)
Метаклассы
В Python класс тоже является объектом, поэтому ничего не мешает написать класс,
назначением которого будет создание других классов динамически, во время выполнения
программы.
def cls_factory_f(func):
class X(object):
pass
setattr(X, func.__name__, func)
return X
def my_method(self):
print "self:", self
My_Class = cls_factory_f(my_method)
my_object = My_Class()
my_object.my_method()
Теперь можно задаться целью построить класс, экземплярами которого будут классы.
Такой класс, от которого порождаются классы, и называется метаклассом.
В Python имеется класс type, который на деле является метаклассом. Вот как с помощью
его конструктора можно создать класс:
def my_method(self):
print "self:", self
В качестве первого параметра type передается имя класса, второй параметр - базовые
классы для данного класса, третий - атрибуты.
class My_Class(object):
def my_method(self):
print "self:", self
Примечание:
Следует заметить, что в метаклассах принято называть первый аргумент методов не self, а
cls, чтобы напомнить, что экземпляр, над которым работает программист, является не
просто объектом, а классом.
Мультиметоды
В этом примере operator.add ведет себя как мультиметод, выполняя разные действия для
различных комбинаций параметров.
# функции мультиметода
def m1(a, b): return 'AA'
def m2(a, b): return 'AB'
def m3(a, b): return 'BA'
68
# определение мультиметода (без одной функции)
g = Generic()
g.add_method(Method((A, A), m1))
g.add_method(Method((A, B), m2))
g.add_method(Method((B, A), m3))
# применение мультиметода
try:
print 'Типы аргументов:', 'Результат'
print 'A, A:', g(A(), A())
print 'A, B:', g(A(), B())
print 'B, A:', g(B(), A())
print 'B, B:', g(B(), B())
except AmbiguousMethodError:
print 'Неоднозначный выбор метода'
Устойчивые объекты
Для того чтобы объекты жили дольше, чем создавшая их программа, необходим механизм
их представления в виде последовательности байтов. Во второй лекции уже
рассматривался модуль pickle, который позволяет сериализовать объекты.
a = CC()
69
print a
b = copy.deepcopy(a)
print b
Механизм getstate/setstate позволяет передавать при копировании только то, что нужно
для воссоздания объекта, тогда как атрибут __dict__ может содержать много лишнего.
Более того, __dict__ может содержать объекты, которые просто так сериализации не
поддаются, и поэтому getstate/setstate - единственная возможность обойти подобные
ограничения.
Примечание:
Это также касается передачи объектов по сетям передачи данных. Если простейшие
объекты (вроде строк или чисел) можно передавать напрямую через HTTP, XML-RPC, SOAP
и т.д., где они имеют собственный тип, то произвольные объекты необходимо
консервировать на передающей стороне и расконсервировать на принимающей.
Критика ООП
Объектно-ориентированный подход сегодня считается "самым передовым". Однако не
следует слепо верить в его всемогущество. Отдача (в виде скорости разработки) от
объектного проектирования чувствуется только в больших проектах и в проектах, которые
родственны объектному подходу: построение графического интерфейса, моделирование
систем и т.п.
Также спорна большая гибкость объектных программ к изменениям. Она зависит от того,
вносится ли новый метод (для серии объектов) или новый тип объекта. При процедурном
подходе при появлении нового метода пишется отдельная процедура, в которой в каждой
ветке алгоритма обрабатывается свой тип данных (то есть такое изменение требует
редактирования одного места в коде). При ООП изменять придется каждый класс, внося в
него новый метод (то есть изменения в нескольких местах). Зато ООП выигрывает при
70
внесении нового типа данных: ведь изменения происходят только в одном месте, где
описываются все методы для данного типа. При процедурном подходе приходится изменять
несколько процедур. Сказанное иллюстрируется ниже. Пусть имеются классы A, B, C и
методы a, b, c:
# ООП
class A:
def a(): ...
def b(): ...
def c(): ...
class B:
def a(): ...
def b(): ...
def c(): ...
class C:
def a(): ...
def b(): ...
def c(): ...
# процедурный подход
def a(x):
if type(x) is A: ...
if type(x) is B: ...
if type(x) is C: ...
def b(x):
if type(x) is A: ...
if type(x) is B: ...
if type(x) is C: ...
def c(x):
if type(x) is A: ...
if type(x) is B: ...
if type(x) is C: ...
При внесении нового типа объекта изменения в ОО-программе затрагивают только один
модуль, а в процедурной - все процедуры:
# ООП
class D:
def a(): ...
def b(): ...
def c(): ...
# процедурный подход
def a(x):
if type(x) is A: ...
if type(x) is B: ...
if type(x) is C: ...
if type(x) is D: ...
def b(x):
if type(x) is A: ...
if type(x) is B: ...
if type(x) is C: ...
if type(x) is D: ...
def c(x):
if type(x) is A: ...
if type(x) is B: ...
71
if type(x) is C: ...
if type(x) is D: ...
И наоборот, теперь нужно добавить новый метод обработки. При процедурном подходе
просто пишется новая процедура, а вот для объектного приходится изменять все классы:
# процедурный подход
def d(x):
if type(x) is A: ...
if type(x) is B: ...
if type(x) is C: ...
# ООП
class A:
def a(): ...
def b(): ...
def c(): ...
def d(): ...
class B:
def a(): ...
def b(): ...
def c(): ...
def d(): ...
class C:
def a(): ...
def b(): ...
def c(): ...
def d(): ...
Заключение
Даже достаточно неформальное введение в ООП потребовало определения большого
количества терминов. В лекции была сделана попытка с помощью примеров передать не
столько букву, сколько дух терминологии ООП. Были рассмотрены все базовые понятия:
объект, тип, класс и виды отношений между объектами (IS-A, HAS-A, USE-A). Слушатели
получили представление о том, что такое инкапсуляция и полиморфизм в стиле ООП, а
также наследование - продление времени жизни объекта за рамками исполняющейся
программы, известное как устойчивость объекта (object persistence). Были указаны
недостатки ООП, но при этом весь предыдущий материал объективно свидетельствовал о
достоинствах этого подхода.
Возможно, что именно эта лекция приведет слушателей к пониманию ООП, пригодному и
удобному для практической работы.
Ссылки
Дэвид Мертц http://www-106.ibm.com/developerworks/linux/library/l-pymeta.html
72
Лекция #5: Численные алгоритмы. Матричные
вычисления
Numeric Python - это несколько модулей для вычислений с многомерными массивами,
необходимых для многих численных приложений. Модуль Numeric вносит в Python
возможности таких пакетов и систем как MatLab, Octave (аналог MatLab), APL, J, S+, IDL.
Пользователи найдут Numeric достаточно простым и удобным. Стоит заметить, что
некоторые синтаксические возможности Python (связанные с использованием срезов) были
специально разработаны для Numeric.
Модуль Numeric
Модуль Numeric определяет полноценный тип-массив и содержит большое число функций
для операций с массивами. Массив - это набор однородных элементов, доступных по
индексам. Массивы модуля Numeric могут быть многомерными, то есть иметь более одной
размерности.
Создание массива
Методы массивов
Придать нужную форму массиву можно функцией Numeric.reshape(). Эта функция сразу
создает объект-массив нужной формы из последовательности.
В этом примере -1 в указании формы говорит о том, что соответствующее значение можно
вычислить. Общее количество элементов массива известно (10), поэтому длину вдоль
одной из размерностей задавать не обязательно.
Следует заметить, что это новый вид того же массива, поэтому присваивание значений его
элементам приводит к изменениям в исходном массиве.
>>> a.tolist()
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
Срезы
75
Срез не копирует массив (как это имеет место со списками), а дает доступ к некоторой
части массива. Далее в примере меняется на 0 каждый третий элемент строки 1:
Универсальные функции
Модуль Numeric определяет набор функций для применения к элементам массива. Функции
применимы не только к массивам, но и к последовательностям (к сожалению, итераторы
пока не поддерживаются). В результате получаются массивы.
Функция Описание
add(x, y), subtract(x, y) Сложение и вычитание
multiply(x, y), divide(x, y) Умножение и деление
remainder(x, y), fmod(x, y) Получение остатка от деления (для целых чисел и чисел с
плавающей запятой)
power(x) Возведение в степень
sqrt(x) Извлечение корня квадратного
negative(x), absolute(x), Смена знака и абсолютное значение
fabs(x)
ceil(x), floor(x) Наименьшее (наибольшее) целое, большее (меньшее) или
равное аргументу
hypot(x, y) Длина гипотенузы (даны длины двух катетов)
sin(x), cos(x), tan(x) Тригонометрические функции
arcsin(x), arccos(x), Обратные тригонометрические функции
76
arctan(x)
arctan2(x, y) Арктангенс от частного аргумента
sinh(x), cosh(x), tanh(x) Гиперболические функции
arcsinh(x), arccosh(x), Обратные гиперболические функции
arctanh(x)
exp(x) Экспонента (ex)
log(x), log10(x) Натуральный и десятичный логарифмы
maximum(x, y), minimum(x, y) Максимум и минимум
conjugate(x) Сопряжение (для комплексных чисел)
equal(x, y), not_equal(x, y) Равно, не равно
greater(x, y), Больше, больше или равно
greater_equal(x, y)
less(x, y), less_equal(x, y) Меньше, меньше или равно
logical_and(x, y), Логические И, ИЛИ
logical_or(x, y)
logical_xor(x, y) Логическое исключающее ИЛИ
logical_not(x) Логические НЕ
bitwise_and(x, y), Побитовые И, ИЛИ
bitwise_or(x, y)
bitwise_xor(x, y) Побитовое исключающее ИЛИ
invert(x) Побитовая инверсия
left_shift(x, n), Побитовые сдвиги влево и вправо на n битов
right_shift(x, n)
77
array([ 3, 12, 30])
>>> add.outer([1,2], [3,4]) # т.е. [[1+3 1+4] [2+3 2+4]]
array([[4, 5],
[5, 6]])
Функций достаточно много, поэтому подробно будут рассмотрены только две из них, а
остальные сведены в таблицу.
Функция Numeric.take()
78
Функция Numeric.take() позволяет взять часть массива по заданным на определенном
измерении индексам. По умолчанию номер измерения (третий аргумент) равен нулю.
a Исходный массив.
offset Смещение вправо от "главной" диагонали (по умолчанию 0).
axis1 Первое из измерений, на которых берется диагональ (по умолчанию 0).
axis2 Второе измерение, образующее вместе с первым плоскость, на которой и берется
диагональ. По умолчанию axis2=1.
79
Sum [2 7] = 9
Sum [3] = 3
Функция Numeric.choose()
Эта функция использует один массив с целыми числами от 0 до n для выбора значения из
одного из заданных массивов:
>>> a = Numeric.identity(4)
>>> b0 = Numeric.reshape(Numeric.arrayrange(16), (4, 4))
>>> b1 = -Numeric.reshape(Numeric.arrayrange(16), (4, 4))
>>> print Numeric.choose(a, (b0, b1))
[[ 0 1 2 3]
[ 4 -5 6 7]
[ 8 9 -10 11]
[ 12 13 14 -15]]
Функция и ее
Назначение функции
аргументы
allclose(a, b[, eps[, Сравнение a и b с заданными относительными eps и абсолютными
A]]) A погрешностями. По умолчанию eps равен 1.0e-1, а A = 1.0e-8.
alltrue(a[, axis]) Логическое И по всей оси axis массива a
argmax(a[, axis]) Индекс максимального значения в массиве по заданному
измерению axis
argmin(a[, axis]) Индекс минимального значения в массиве по заданному
измерению axis
argsort(a[, axis]) Индексы отсортированного массива, такие, что
take(a,argsort(a, axis),axis) дает отсортированный массив a,
как если бы было выполнено sort(a, axis)
array(a[, type]) Создание массива на основе последовательности a данного типа
type
arrayrange(start[, Аналог range() для массивов
stop[, step[,
type]]])
asarray(a[, type[, То же, что и array(), но не создает новый массив, если a уже
savespace]]) является массивом.
choose(a, Создает массив на основе элементов, взятых по индексам из a
(b0,...,bn)) (индексы от 0 до n включительно). Формы массивов a, b1, ..., bn
должны совпадать
clip(a, a_min, a_max) Обрубает значения массива a так, чтобы они находились между
значениями из a_min и a_max поэлементно
compress(cond, a[, Возвращает массив только из тех элементов массива a, для
axis]) которых условие cond истинно (не нуль)
concatenate(a[, Соединение двух массивов (конкатенация) по заданному
axis]) измерению axis (по умолчанию - по нулевой)
convolve(a, b[, Свертка двух массивов. Аргумент mode может принимать значения
mode]) 0, 1 или 2
cross_correlate(a, Взаимная корреляция двух массивов. Параметр mode может
b[, mode]) принимать значения 0, 1 или 2
80
cumproduct(a[, axis]) Произведение по измерению axis массива a с промежуточными
результатами
cumsum(a[, axis]) Суммирование с промежуточными результатами
diagonal(a[, k[, Взятие k-й диагонали массива a в плоскости измерений axis1 и
axis1[, axis2]]]) axis2
dot(a, b) Внутреннее (матричное) произведение массивов. По
определению: innerproduct(a, swapaxes(b, -1, -2)), т.е. с
переставленными последними измерениями, как и должно быть
при перемножении матриц
dump(obj, file) Запись массива a (в двоичном виде) в открытый файловый объект
file. Файл должен быть открыт в бинарном режиме. В файл
можно записать несколько объектов подряд
dumps(obj) Строка с двоичным представлением объекта obj
fromfunction(f, dims) Строит массив, получая информацию от функции f(), в качестве
аргументов которой выступают значения кортежа индексов.
Фактически является сокращением для
f(*tuple(indices(dims)))
fromstring(s[, Создание массива на основе бинарных данных, хранящихся в
count[, type]]) строке
identity(n) Возвращает двумерный массив формы (n, n)
indices(dims[, type]) Возвращает массив индексов заданной длины по каждому
измерению с изменением поочередно по каждому изменению.
Например, indices([2, 2])[1] дает двумерный массив [[0, 1],
[0, 1]].
innerproduct(a, b) Внутреннее произведение двух массивов (по общему измерению).
Для успешной операции a.shape[-1] должен быть равен
b.shape[-1]. Форма результата будет a.shape[:-1] + b.shape[:-
1]. Элементы пропадающего измерения попарно умножаются и
получающиеся произведения суммируются
load(file) Чтение массива из файла file. Файл должен быть открыт в
бинарном режиме
loads(s) Возвращает объект, соответствующий бинарному представлению,
заданному в строке
nonzero(a) Возвращает индексы ненулевых элементов одномерного массива
ones(shape[, type]) Массив из единиц заданной формы shape и обозначения типа type
outerproduct(a, b) Внешнее произведение a и b
product(a[, axis]) Произведение по измерению axis массива a
put(a, indices, b) Присваивание частям массива, a[n] = b[n] для всех индексов
indices
putmask(a, mask, b) Присваивание a элементов из b, для которых маска mask имеет
значение истина
ravel(a) Превращение массива в одномерный. Аналогично reshape(a, (-
1,))
repeat(a, n[, axis]) Повторяет элементы массива a n раз по измерению axis
reshape(a, shape) Возвращает массив нужной формы (нового массива не создает).
Количество элементов в исходном и новом массивах должно
совпадать
resize(a, shape) Возвращает массив с произвольной новой формой shape. Размер
исходного массива не важен
searchsorted(a, i) Для каждого элемента из i найти место в массиве a. Массив a
81
должен быть одномерным и отсортированным. Результат имеет
форму массива i
shape(a) Возвращает форму массива a
sometrue(a[, axis]) Логическое ИЛИ по всему измерению axis массива a
sort(a[, axis]) Сортировка элементов массива по заданному измерению
sum(a[, axis]) Суммирование по измерению axis массива a
swapaxes(a, axis1, Смена измерений (частный случай транспонирования)
axis1)
take(a, indices[, Выбор частей массива a на основе индексов indices по
axis]) измерению axis
trace(a[, k[, axis1[, Сумма элементов вдоль диагонали, то есть
axis2]]]) add.reduce(diagonal(a, k, axis1, axis2))
transpose(a[, axes]) Перестановка измерений в соответствии с axes, либо, если axes
не заданы - расположение их в обратном порядке
where(cond, a1, a2) Выбор элементов на основании условия cond из a1 (если не нуль)
и a2 (при нуле) поэлементно. Равносилен
choose(not_equal(cond, 0), (y, x)). Формы массивов-
аргументов a1 и a2 должны совпадать
zeros(shape[, type]) Массив из нулей заданной формы shape и обозначения типа type
В этой таблице в качестве обозначения типа type можно указывать рассмотренные выше
константы: Int, Float и т.п.
Модуль LinearAlgebra
Модуль LinearAlgebra содержит алгоритмы линейной алгебры, в частности нахождение
определителя матрицы, решений системы линейных уравнений, обращение матрицы,
нахождение собственных чисел и собственных векторов матрицы, разложение матрицы на
множители: Холецкого, сингулярное, метод наименьших квадратов.
82
>>> a = Numeric.array([[1.0, 2.0], [0.5, 1.0]])
>>> x = LinearAlgebra.solve_linear_equations(a, b)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "/usr/local/lib/python2.3/site-packages/Numeric/LinearAlgebra.py", line
98,
in solve_linear_equations raise LinAlgError, 'Singular matrix'
LinearAlgebra.LinAlgError: Singular matrix
def inverse(a):
return solve_linear_equations(a, Numeric.identity(a.shape[0]))
Модуль RandomArray
В этом модуле собраны функции для генерации массивов случайных чисел различных
распределений и свойств. Их можно применять для математического моделирования.
83
Можно получать и случайные перестановки с помощью RandomArray.permutation():
Заключение
В этой лекции рассматривался набор модулей для численных вычислений. Модуль Numeric
определяет тип многомерный массив и множество функций для работы с массивами. Также
были представлены модули для линейной алгебры и моделирования последовательностей
случайных чисел различных распределений.
Ссылки
Сайт, посвященный Numeric Python: http://www.pfdubois.com/numpy/
84
Лекция #6: Обработка текстов. Регулярные выражения.
Unicode
Под обработкой текстов понимается анализ, преобразование, поиск, порождение
текстовой информации. По большей части работа с естественными текстами не будет
глубже, чем это возможно без систем искусственного интеллекта. Кроме того, здесь
предполагается опустить рассмотрение обработки текстов посредством текстовых
процессоров и редакторов, хотя некоторые из них (например, Cooledit) предоставляют
возможность писать макрокоманды на Python.
Следует отметить, что для Python созданы также модули для работы с естественными
языками, а также для лингвистических исследований. Хорошим учебным примером может
служить nltk (the Natural Language Toolkit).
Строки
Строки в языке Python являются типом данных, специально предназначенным для
обработки текстовой информации. Строка может содержать произвольно длинный текст
(ограниченный имеющейся памятью).
В новых версиях Python имеются два типа строк: обычные строки (последовательность
байтов) и Unicode-строки (последовательность символов). В Unicode-строке каждый символ
может занимать в памяти 2 или 4 байта, в зависимости от настроек периода компиляции.
Четырехбайтовые знаки используются в основном для восточных языков.
Примечание:
Кодировка Python-программы
Строковые литералы
s1 = "строка 1"
s2 = r'\1\2'
s3 = """apple\ntree""" # \n - символ перевода строки
s4 = """apple
tree""" # строка в утроенных кавычках может иметь внутри переводы строк
s5 = '\x73\65'
u1 = u"Unicode literal"
u2 = u'\u0410\u0434\u0440\u0435\u0441'
Примечание:
Обратная косая черта не должна быть последним символом в литерале, то есть, "str\"
вызовет синтаксическую ошибку.
Операция форматирования
"%" [ключ][флаг*][шир][.точность][длина_типа]спецификатор
ключ: "(" символ за исключением круглых скобок* ")"
флаг: "+" | "-" | пробел | "#" | "0"
шир: ("1" ... "9")("0" ... "9")* | "*"
точность: ("1" ... "9")* | "*"
длина_типа: "a" ... "z" | "A" ... "Z"
спецификатор: "a" ... "z" | "A" ... "Z" | "%"
ключ
Ключ из словаря.
флаги
шир
точность
длина_типа
Модификатор типа.
спецификатор
Где
Символ Что указывает
применяется
0 флаг Заполнение нулями слева
- флаг Выравнивание по левому краю
+ флаг Обязательный вывод знака числа
пробел флаг Использовать пробел на месте знака числа
d, i спецификатор Знаковое целое
u спецификатор Беззнаковое целое
87
o спецификатор Восьмеричное беззнаковое целое
x, X спецификатор Шестнадцатеричное беззнаковое целое (со строчными или
прописными латинскими буквами)
e, E спецификатор Число с плавающей запятой в формате с экспонентой
f, F спецификатор Число с плавающей запятой
g, G спецификатор Число с плавающей точкой в более коротком написании
(автоматически выбирается e или f)
с спецификатор Одиночный символ (целое число или односимвольная строка)
r спецификатор Любой объект, приведенный к строке функцией repr()
s спецификатор Любой объект, приведенный к строке функцией str()
% спецификатор Знак процента. Для задания одиночного процента необходимо
записать %%
Индексы и срезы
>>> s = "транспорт"
>>> print s[0], s[-1]
т т
>>> print s[-4:]
порт
>>> print s[:5]
транс
>>> print s[4:8]
спор
Примечание:
Модуль string
До того как у строк появились методы, для операций над строками применялся модуль
string. Приведенный пример демонстрирует, как вместо функции из string использовать
метод (кстати, последнее более эффективно):
В версии Python 3.0 функции, которые доступны через методы, более не будут
дублироваться в модуле string.
88
2 + 3 = 5
>>> del c # удаляется имя c
>>> print tpl.safe_substitute(vars())
2 + 3 = $c
>>> print tpl.substitute(vars(), c=a+b)
2 + 3 = 5
>>> print tpl.substitute(vars())
Traceback (most recent call last):
File "/home/rnd/tmp/Python-2.4b2/Lib/string.py", line 172, in substitute
return self.pattern.sub(convert, self.template)
File "/home/rnd/tmp/Python-2.4b2/Lib/string.py", line 162, in convert
val = mapping[named]
KeyError: 'c'
Методы строк
Метод Описание
center(w) Центрирует строку в поле длины w
count(sub) Число вхождений строки sub в строке
encode([enc[, Возвращает строку в кодировке enc. Параметр errors может
errors]]) принимать значения "strict" (по умолчанию), "ignore",
"replace" или "xmlcharrefreplace"
endswith(suffix) Оканчивается ли строка на suffix
expandtabs([tabsize]) Заменяет символы табуляции на пробелы. По умолчанию
tabsize=8
find(sub [,start Возвращает наименьший индекс, с которого начинается
[,end]]) вхождение подстроки sub в строку. Параметры start и end
ограничивают поиск окном start:end, но возвращаемый индекс
соответствует исходной строке. Если подстрока не найдена,
возвращается -1
index(sub[, start[, Аналогично find(), но возбуждает исключение ValueError в
end]]) случае неудачи
alnum() Возвращает True, если строка содержит только буквы и цифры и
имеет ненулевую длину. Иначе -- False
isalpha() Возвращает True, если строка содержит только буквы и длина
ненулевая
isdecimal() Возвращает True, если строка содержит только десятичные знаки
(только для строк Unicode) и длина ненулевая
isdigit() Возвращает True, если содержит только цифры и длина
ненулевая
islower() Возвращает True, если все буквы строчные (и их более одной),
иначе -- False
isnumeric() Возвращает True, если в строке только числовые знаки (только
для Unicode)
isspace() Возвращает True, если строка состоит только из пробельных
символов. Внимание! Для пустой строки возвращается False
89
join(seq) Соединение строк из последовательности seq через разделитель,
заданный строкой
lower() Приводит строку к нижнему регистру букв
lstrip() Удаляет пробельные символы слева
replace(old, new[, Возвращает копию строки, в которой подстроки old заменены
n]) new. Если задан параметр n, то заменяются только первые n
вхождений
rstrip() Удаляет пробельные символы справа
split([sep[, n]]) Возвращает список подстрок, получающихся разбиением строки a
разделителем sep. Параметр n определяет максимальное
количество разбиений (слева)
startswith(prefix) Начинается ли строка с подстроки prefix
strip() Удаляет пробельные символы в начале и в конце строки
translate(table) Производит преобразование с помощью таблицы перекодировки
table, содержащей словарь для перевода кодов в коды (или в
None, чтобы удалить символ). Для Unicode-строк
translate(table[, То же, но для обычных строк. Вместо словаря - строка
dc]) перекодировки на 256 символов, которую можно сформировать с
помощью функции string.maketrans(). Необязательный
параметр dc задает строку с символами, которые необходимо
удалить
upper() Переводит буквы строки в верхний регистр
import string
text = open(string.__file__[:-1]).read()
start = 0
while 1:
found = text.find("def ", start)
90
if found == -1:
break
print text[found:found + 60].split("(")[0]
start = found + 1
Рекомендации по эффективности
При работе с очень длинными строками или большим количеством строк, применяемые
операции могут по-разному влиять на быстродействие программы.
>>> a = ""
>>> for i in xrange(1000):
... a += str(i) # неэффективно!
...
>>> a = "".join([str(i) for i in xrange(1000)]) # более эффективно
Модуль StringIO
В некоторых случаях желательно работать со строкой как с файлом. Модуль StringIO как
раз дает такую возможность.
import StringIO
my_string = "1234567890"
f1 = StringIO.StringIO()
f2 = StringIO.StringIO(my_string)
Для получения содержимого такого файла в виде строки применяется метод getvalue():
f1.getvalue()
Модуль difflib
где
word
possibilities
cutoff
Коэффициент (из диапазона [0, 1]) необходимого уровня совпадения строк. Строки,
которые при сравнении с word дают меньшее значение, игнорируются.
Регулярные выражения
Рассмотренных стандартных возможностей для работы с текстом достаточно далеко не
всегда. Например, в методах find() и replace() задается всего одна строка. В реальных
задачах такая однозначность встречается довольно редко, чаще требуется найти или
заменить строки, отвечающие некоторому шаблону.
>>> import re
>>> pattern = r"[0-9]+"
>>> number_re = re.compile(pattern)
>>> number_re.findall("122 234 65435")
['122', '234', '65435']
В этом примере шаблон pattern описывает множество строк, которые состоят из одного
или более символов из набора "0", "1" , ..., "9" . Функция re.compile() компилирует
шаблон в специальный Regex-объект, который имеет несколько методов, в том числе метод
findall() для получения списка всех непересекающихся вхождений строк,
удовлетворяющих шаблону, в заданную строку.
92
То же самое можно было сделать и так:
>>> import re
>>> re.findall(r"[0-9]+", "122 234 65435")
['122', '234', '65435']
Примечание:
Синтаксис регулярных выражений в Python почти такой же, как в Perl, grep и некоторых
других инструментах. Часть символов (в основном буквы и цифры) обозначают сами себя.
Строка удовлетворяет (соответствует) шаблону, если она входит во множество строк,
которые этот шаблон описывает.
Здесь стоит также отметить, что различные операции используют шаблон по-разному. Так,
search() ищет первое вхождение строки, удовлетворяющей шаблону, в заданной строке, а
match() требует, чтобы строка удовлетворяла шаблону с самого начала.
93
Скобки, описанные ниже, применяются для задания приоритетов и выделения групп
(фрагментов текста, которые потом можно получить по номеру или из словаря, и даже
сослаться в том же регулярном выражении).
В таблице ниже вместо регвыр может быть записано регулярное выражение, вместо имя -
идентификатор, а флаги будут рассмотрены ниже.
Обозначение Описание
"(регвыр)" Обособляет регулярное выражение в скобках и выделяет группу
"(?:регвыр)" Обособляет регулярное выражение в скобках без выделения группы
"(?=регвыр)" Взгляд вперед: строка должна соответствовать заданному регулярному
выражению, но дальнейшее сопоставление с шаблоном начнется с того
же места
"(?!регвыр)" То же, но с отрицанием соответствия
"(?<=регвыр)" Взгляд назад: строка должна соответствовать, если до этого момента
соответствует регулярному выражению. Не занимает места в строке, к
которой применяется шаблон. Параметр регвыр должен быть
фиксированной длины (то есть, без "+" и "*")
"(?<!регвыр)" То же, но с отрицанием соответствия
"(?P<имя>регвыр)" Выделяет именованную группу с именем имя
"(?P=имя)" Точно соответствует выделенной ранее именованной группе с именем
имя
"(?#регвыр)" Комментарий (игнорируется)
"(?(имя)рв1|рв2)" Если группа с номером или именем имя оказалась определена,
результатом будет сопоставление с рв1, иначе - c рв2. Часть |рв2
может отсутствовать
"(?флаг)" Задает флаг для всего данного регулярного выражения. Флаги
необходимо задавать в начале шаблона
Если этот флаг задан, "^" и "$" соответствуют началу и концу любой строки.
Методы объекта-шаблона
match(s)
search(s)
split(s[, maxsplit=0])
findall(s)
95
finditer(s)
sub(repl, s)
Заменяет в строке s все (или только count, если он задан) вхождения неперекрывающихся
подстрок, удовлетворяющих шаблону, на строку, заданную с помощью repl. В качестве
replможет выступать строка или функция. Возвращает строку с выполненными заменами.
В первом случае строка repl подставляется не просто так, а интерпретируется с заменой
вхождений "\номер" на группу с соответствующим номером и вхождений "\g<имя>" на
группу с номером или именем имя. В случае, когда repl - функция, ей передается объект с
результатом каждого успешного сопоставления, а из нее возвращается строка для замены.
subn(repl, s)
>>> import re
>>> delim_re = re.compile(r"[:,;]")
>>> text = "This,is;example"
>>> print delim_re.split(text)
['This', 'is', 'example']
Примеры шаблонов
r"\b\w+\b"
r"[+-]?\d+"
r"\([+-]?\d+\)"
r"[a-cA-C]{2}"
Соответствует строке из двух букв "a", "b" или "c". Например, "Ac", "CC", "bc".
r"aa|bb|cc|AA|BB|CC"
96
Строка из двух одинаковых букв.
r"([a-cA-C])\1"
r"aa|bb".
r"a(a|b)b"
r"^(?:\d{8}|\d{4}):\s*(.*)$"
r"(\w+)=.*\b\1\b"
Слова слева и справа от знака равенства присутствуют. Операнд "\1" соответствует группе
с номером 1, выделенной с помощью скобок.
r"(?P<var>\w+)=.*\b(?P=var)\b"
r"\bregular(?=\s+expression)".
Соответствует слову "regular" только в том случае, если за ним после пробелов следует
"expression"
r"(?<=regular )expression"
import re
97
return last_good
[A-Za-z]{3}\s+\d+\s+\d\d
Обработка лога
import re
log_re = re.compile(r"""(?P<date>[A-Za-z]{3}\s+\d+\s+\d\d:\d\d:\d\d) \S+
kernel:
PAY: .+ DST=(?P<dst>\S+).* LEN=(?P<len>\d+).* DPT=(?P<dpt>\d+) """)
В результате получается
Хороший пример регулярного выражения можно найти в модуле fpformat. Это регулярное
выражение позволяет разобрать запись числа (в том виде, в каком числовой литерал
принято записывать в Python):
decoder = re.compile(r'^([-+]?)0*(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
# Следующие части числового литерала выделяются с помощью групп:
# \0 - весь литерал
# \1 - начальный знак или пусто
# \2 - цифры слева от точки
# \3 - дробная часть (пустая или начинается с точки)
# \4 - показатель (пустой или начинается с 'e' или 'E')
98
Например:
import re
decoder = re.compile(r'^([-+]?)0*(\d*)((?:\.\d*)?)((?:[eE][-+]?\d+)?)$')
print decoder.match("12.234").groups()
print decoder.match("-0.23e-7").groups()
print decoder.match("1e10").groups()
Получим
Множественная замена
import re
multisub_re = re.compile("|".join(subs_dict.keys()))
return multisub_re.sub(_multisub, text)
Будет выведено
One, 2, 3
import fileinput
for line in fileinput.input():
process(line)
Работа с Unicode
До появления Unicode символы в компьютере кодировались одним байтом (а то и только
семью битами). Один байт охватывает диапазон кодов от 0 до 255 включительно, а это
99
значит, что больше двух алфавитов, цифр, знаков пунктуации и некоторого набора
специальных символов в одном байте не помещается. Каждый производитель использовал
свою кодировку для одного и того же алфавита. Например, до настоящего времени дожили
целых пять кодировок букв кириллицы, и каждый пользователь не раз видел в своем
браузере или электронном письме пример несоответствия кодировок.
Стандарт Unicode - единая кодировка для символов всех языков мира. Это большое
облегчение и некоторое неудобство одновременно. Плюс состоит в том, что в одной
Unicode-строке помещаются символы совершенно различных языков. Минус же в том, что
пользователи привыкли применять однобайтовые кодировки, большинство приложений
ориентировано на них, во многих системах поддержка Unicode осуществляется лишь
частично, так как требует огромной работы по разработке шрифтов. Правда, символы
одной кодировки можно перевести в Unicode и обратно.
>>> sys.byteorder
'little'
Для исключения неоднозначности документ в Unicode может быть в самом начале снабжен
BOM (byte-order mark - метка порядка байтов) - Unicode-символом с кодом 0xfeff. Для
данной платформы строка байтов для BOM будет такой:
>>> codecs.BOM_LE
'\xff\xfe'
Здесь:
filename
Имя файла.
mode
100
enc
Кодировка.
errors
buffer
Заключение
В этой лекции были рассмотрены основные типы для манипулирования текстом: строки и
Unicode-строки. Достаточно подробно описаны регулярные выражения - один из наиболее
эффективных механизмов для анализа текста. В конце приведены некоторые функции для
работы с Unicode.
Ссылки
NLTK
http://nltk.sourceforge.net
101
Лекция #7: Работа с данными в различных форматах
Формат CSV
Файл в формате CSV (comma-separated values - значения, разделенные запятыми) -
универсальное средство для переноса табличной информации между приложениями
(электронными таблицами, СУБД, адресными книгами и т.п.). К сожалению, формат файла
не имеет строго определенного стандарта, поэтому между файлами, порождаемыми
различными приложениями, существуют некоторые тонкие различия. Внутри файл
выглядит примерно так (файл pr.csv):
name,number,text
a,1,something here
b,2,"one, two, three"
c,3,"no commas here"
import csv
input_file = open("pr.csv", "rb")
rdr = csv.reader(input_file)
output_file = open("pr1.csv", "wb")
wrtr = csv.writer(output_file)
for rec in rdr:
try:
rec[1] = int(rec[1]) + 1
except:
pass
wrtr.writerow(rec)
input_file.close()
output_file.close()
name,number,text
a,2,something here
102
b,3,"one, two, three"
c,4,no commas here
Модуль также определяет два класса для более удобного чтения и записи значений с
использованием словаря. Вызовы конструкторов следующие:
Соответствующий пример дан ниже. В файле pr.csv имена полей заданы в первой строке
файла, поэтому можно не задавать fieldnames:
import csv
input_file = open("pr.csv", "rb")
rdr = csv.DictReader(input_file,
fieldnames=['name', 'number', 'text'])
output_file = open("pr1.csv", "wb")
wrtr = csv.DictWriter(output_file,
fieldnames=['name', 'number', 'text'])
for rec in rdr:
try:
rec['number'] = int(rec['number']) + 1
except:
pass
wrtr.writerow(rec)
input_file.close()
output_file.close()
Модуль имеет также другие классы и функции, которые можно изучить по документации.
На примере этого модуля можно увидеть общий подход к работе с файлом в некотором
формате. Следует обратить внимание на следующие моменты:
103
Пакет email
Модули пакета email помогут разобрать, изменить и сгенерировать сообщение в формате
RFC 2822. Наиболее часто RFC 2822 применяется в сообщениях электронной почты в
Интернете.
Message
Модуль определяет класс Message - основной класс для представления сообщения в пакете
email.
Parser
Header
Generator
Utils
Класс Message - центральный во всем пакете email. Он определяет методы для работы с
сообщением, которое состоит из заголовка (header) и тела (payload). Поле заголовка имеет
название и значение, разделенное двоеточием (двоеточие не входит ни в название, ни в
значение). Названия полей нечувствительны к регистру букв при поиске значения, хотя
хранятся с учетом регистра. В классе также определены методы для доступа к некоторым
часто используемым сведениям (кодировке сообщения, типу содержимого и т.п.).
Следует заметить, что сообщение может иметь одну или несколько частей, в том числе
вложенных друг в друга. Например, сообщение об ошибке доставки письма может
содержать исходное письмо в качестве вложения.
104
>>> print msg['from']
"felton olive" <[email protected]>
>>> msg.get_all('received')
['from mail.onego.ru\n\tby localhost with POP3 (fetchmail-6.2.5
polling mail.onego.ru account spam)\n\tfor spam@localhost
(single-drop); Wed, 01 Sep 2004 15:46:33 +0400 (MSD)',
'from thecanadianteacher.com ([222.65.104.100])\n\tby mail.onego.ru
(8.12.11/8.12.11) with SMTP id i817UtUN026093;\n\tWed, 1 Sep 2004
11:30:58 +0400']
Стоит заметить, что в электронном письме может быть несколько полей с именем received
(в этом примере их два).
Некоторые важные данные можно получить в готовом виде, например, тип содержимого,
кодировку:
>>> msg.get_content_type()
'text/plain'
>>> print msg.get_main_type(), msg.get_subtype()
text plain
>>> print msg.get_charset()
None
>>> print msg.get_params()
[('text/plain', ''), ('charset', 'us-ascii')]
>>> msg.is_multipart()
False
Так как сообщение состоит из одной части, можно получить его тело в виде строки:
import email
parts = []
ct_fields = []
filenames = []
f = open("virus.eml")
msg = email.message_from_file(f)
for submsg in msg.walk():
parts.append(submsg.get_content_type())
ct_fields.append(submsg.get('Content-Type', ''))
filenames.append(submsg.get_filename())
if submsg.get_filename():
print "Длина файла:", len(submsg.get_payload())
f.close()
print parts
105
print ct_fields
print filenames
В результате получилось:
Из списка parts можно увидеть, что само сообщение имеет тип multipart/mixed, тогда как
две его части - text/html и application/octet-stream соответственно. Только с
последней частью связано имя файла (price.cpl). Файл читается методом get_payload() и
вычисляется его длина.
Формирование сообщения
# Прикладывается файл
fp = open("archive_file.zip", "rb")
to_attach = MIMEBase("application", "octet-stream")
to_attach.set_payload(fp.read())
encode_base64(to_attach)
to_attach.add_header("Content-Disposition", "attachment",
filename="archive_file.zip")
fp.close()
msg.attach(to_attach)
print msg.as_string()
106
В этом примере видно сразу несколько модулей пакета email. Функция make_header() из
email.Header позволяет закодировать содержимое для заголовка:
Базовый класс для всех использующих MIME сообщений (подклассов Message). Тип
содержимого задается через _maintype и _subtype.
class MIMENonMultipart()
107
Разбор поля заголовка
subj = """=?koi8-r?Q?=FC=D4=CF_=D0=D2=C9=CD=C5=D2_=CF=DE=C5=CE=D8_=C4=CC=C9?=
=?koi8-r?Q?=CE=CE=CF=C7=CF_=28164_bytes=29_=D0=CF=CC=D1_=D3_=D4?=
=?koi8-r?Q?=C5=CD=CF=CA_=D3=CF=CF=C2=DD=C5=CE=C9=D1=2E_=EF=CE=CF_?=
=?koi8-r?Q?=D2=C1=DA=C2=C9=CC=CF=D3=D8_=CE=C1_=CB=D5=D3=CB=C9_=D7?=
=?koi8-r?Q?_=D3=CF=CF=C2=DD=C5=CE=C9=C9=2C_=CE=CF_=CC=C5=C7=CB=CF?=
=?koi8-r?Q?_=D3=CF=C2=C9=D2=C1=C5=D4=D3=D1_=D7_=D4=C5=CB=D3=D4_?=
=?koi8-r?Q?=D3_=D0=CF=CD=CF=DD=D8=C0_email=2EHeader=2Edecode=5Fheader?=
=?koi8-r?Q?=28=29?="""
import email.Header
for text, enc in email.Header.decode_header(subj):
print enc, text
koi8-r Это пример очень длинного (164 bytes) поля с темой сообщения.
Оно разбилось на куски в сообщении, но легко собирается в текст
с помощью email.Header.decode_header()
В общем случае представить поле сообщения можно только в Unicode. Создание функции
для такого преобразования предлагается в качестве упражнения.
Язык XML
В рамках одной лекции довольно сложно объяснить, что такое XML, и то, как с ним
работать. В примерах используется входящий в стандартную поставку пакет xml.
Язык XML (как и HTML) является подмножеством SGML, но его применения не ограничены
системой WWW. В XML можно создавать собственные наборы тегов для конкретной
предметной области. В XML можно хранить и подвергать обработке базы данных и знаний,
протоколы взаимодействия между объектами, описания ресурсов и многое другое.
108
информацию: теги и их атрибуты имеют имена. Немаловажно также, что XML сегодня -
единый общепринятый стандарт, для которого создано немало инструментальных средств.
Для представления букв и других символов XML использует Unicode, что сокращает
проблемы с представлением символов различных алфавитов. Однако это обстоятельство
необходимо помнить и не употреблять в XML восьмибитную кодировку (во всяком случае,
без явного указания).
XML-документ всегда имеет структуру дерева, в корне которого сам документ. Его части,
описываемые вложенными парами тегов, образуют узлы. Таким образом, ребра дерева
обозначают "непосредственное вложение". Атрибуты тега можно считать листьями, как и
наиболее вложенные части, не имеющие в своем составе других частей. Получается, что
документ имеет древесную структуру.
Примечание:
Следует заметить, что в отличие от HTML, в XML одиночные (непарные) теги записываются
с косой чертой: <BR/>, а атрибуты - в кавычках. В XML имеет значение регистр букв в
названиях тегов и атрибутов.
Формирование XML-документа
В первом случае обычно используется SAX (Simple API for XML, простой программный
интерфейс для XML). Работа SAX заключается в чтении источников данных (input source)
XML-анализаторами (XML-reader) и генерации последовательности событий (events),
которые обрабатываются объектами-обработчиками (handlers). SAX дает последовательный
доступ к XML-документу.
Во втором случае анализатор XML строит DOM (Document Object Model, объектная модель
документа), предлагая для XML-документа конкретную объектную модель. В рамках этой
модели узлы DOM-дерева доступны для произвольного доступа,а для переходов между
узлами предусмотрен ряд методов.
Можно применить оба этих подхода для формирования приведенного выше XML-документа.
109
С помощью SAX документ сформируется так:
import sys
from xml.sax.saxutils import XMLGenerator
g = XMLGenerator(sys.stdout)
g.startDocument()
g.startElement("expression", {})
g.startElement("operation", {"type": "+"})
g.startElement("operand", {})
g.characters("2")
g.endElement("operand")
g.startElement("operand", {})
g.startElement("operation", {"type": "*"})
g.startElement("operand", {})
g.characters("3")
g.endElement("operand")
g.startElement("operand", {})
g.characters("4")
g.endElement("operand")
g.endElement("operation")
g.endElement("operand")
g.endElement("operation")
g.endElement("expression")
g.endDocument()
Легко заметить, что при использовании SAX команды на генерацию тегов и других частей
выдаются последовательно, а вот построение одной и той же DOM можно выполнять
различными последовательностями команд формирования узла и его соединения с другими
узлами.
Конечно, указанные примеры носят довольно теоретический характер, так как на практике
строить XML-документы таким образом обычно не приходится.
Анализ XML-документа
110
можно поставить пакет PyXML или альтернативные коммерческие пакеты. Тем не менее,
разработчики стараются придерживаться единого API, который продиктован стандартом
DOM Level 2:
import xml.dom.minidom
dom = xml.dom.minidom.parse("expression.xml")
dom.normalize()
output_tree(dom)
#document
. expression
. . operation type=+
. . . operand
. . . . 2
. . . operand
. . . . operation type=*
. . . . . operand
. . . . . . 3
. . . . . operand
. . . . . . 4
Здесь же применяется метод normalize() для того, чтобы все текстовые фрагменты были
слиты воедино (в противном случае может следовать подряд несколько узлов с текстом).
Все узлы являются экземплярами подклассов класса Node. Они могут быть следующих
типов:
111
ENTITY_NODE Сущность
PROCESSING_INSTRUCTION_NODE Инструкция по createProcessingInstruction(target,
обработке data)
COMMENT_NODE Комментарий createComment(comment)
DOCUMENT_NODE Документ
DOCUMENT_TYPE_NODE Тип документа
DOCUMENT_FRAGMENT_NODE Фрагмент
документа
NOTATION_NODE Нотация
В DOM документ является деревом, в узлах которого стоят объекты нескольких возможных
типов. Узлы могут иметь атрибуты или данные. Доступ к узлам можно осуществлять через
атрибуты вроде childNodes (дочерние узлы), firstChild (первый дочерний узел),
lastChild (последний дочерний узел), (родитель), nextSibling (следующий брат),
previousSibling (предыдущий брат).parentNode
Узел типа ELEMENT_NODE, помимо перечисленных методов "просто" узла, имеет много
других методов. Вот основные из них:
tagName
getElementsByTagName(tagname)
Получает элементы с указанным именем tagname среди всех потомков данного элемента.
getAttribute(attname)
getAttributeNode(attrname)
removeAttribute(attname)
removeAttributeNode(oldAttr)
setAttribute(attname, value)
setAttributeNode(newAttr)
112
Добавляет новый узел-атрибут к элементу. Старый атрибут заменяется, если имеет то же
имя.
Здесь стоит заметить, что атрибуты в рамках элемента повторяться не должны. Их порядок
также не важен с точки зрения информационной модели XML.
Пространства имен
В пакете xml есть методы, понимающие механизм пространств имен. Обычно такие методы
и атрибуты имеют в своем имени буквы NS.
def output_ns(node):
if node.nodeType == node.ELEMENT_NODE:
print node.nodeName, node.namespaceURI
for child in node.childNodes:
output_ns(child)
output_ns(dom)
Пример выведет:
rdf:RDF http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdf:Description http://www.w3.org/1999/02/22-rdf-syntax-ns#
foaf:nick http://xmlns.com/foaf/0.1/
foaf:name http://xmlns.com/foaf/0.1/
rdf:type http://www.w3.org/1999/02/22-rdf-syntax-ns#
Следует заметить, что указание пространства имен может быть сделано для имен не только
элементов, но и атрибутов.
Заключение
В этой лекции были рассмотрены варианты обработки текстовой информации трех
достаточно распространенных форматов: CSV, Unix mailbox и XML. Конечно, форматов
данных, даже основанных на тексте, гораздо больше, однако то, что было представлено,
поможет быстрее разобраться с любым модулем для обработки формата или построить свой
модуль так, чтобы другие могли понять ваши намерения.
114
Лекция #8: Разработка Web-приложений
Под web-приложением будет пониматься программа, основной интерфейс пользователя
которой работает в стандартном WWW-браузере под управлением HTML и XML-документов.
Для улучшение качества интерфейса пользователя часто применяют JavaScript, однако это
несколько снижает универсальность интерфейса. Следует заметить, что интерфейс можно
построить на Java- или Flash-апплетах, однако, такие приложения сложно назвать web-
приложениями, так как Java или Flash могут использовать собственные протоколы для
общения с сервером, а не стандартный для WWW протокол HTTP.
CGI-сценарии
Классический путь создания приложений для WWW - написание CGI-сценариев (иногда
говорят - скриптов). CGI (Common Gateway Interface, общий шлюзовой интерфейс) - это
стандарт, регламентирующий взаимодействие сервера с внешними приложениями. В случае
с WWW, web-сервер может направить запрос на генерацию страницы по определенному
сценарию. Этот сценарий, получив на вход данные от web-сервера (тот, в свою очередь,
мог получить их от пользователя), генерирует готовый объект (изображение, аудиоданные,
таблицу стилей и т.п.).
При вызове сценария Web-сервер передает ему информацию через стандартный ввод,
переменные окружения и, для ISINDEX, через аргументы командной строки (они доступны
через sys.argv).
#!/usr/bin/python
import os
print """Content-Type: text/plain
%s""" % os.environ
115
QUERY_STRING
Строка запроса.
REMOTE_ADDR
IP-адрес клиента.
REMOTE_USER
SCRIPT_NAME
Имя сценария.
SCRIPT_FILENAME
SERVER_NAME
Имя сервера.
HTTP_USER_AGENT
REQUEST_URI
HTTP_USER_AGENT
Имя сервера.
HTTP_ACCEPT_LANGUAGE
{
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'GATEWAY_INTERFACE': 'CGI/1.1',
'HTTP_ACCEPT_LANGUAGE': 'en-us, en;q=0.50',
'REMOTE_ADDR': '127.0.0.1',
'SERVER_NAME': 'rnd.onego.ru',
'HTTP_CONNECTION': 'close',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; U; Linux i586; en-US;
rv:1.0.1) Gecko/20021003',
'HTTP_ACCEPT_CHARSET': 'ISO-8859-1, utf-8;q=0.66, *;q=0.66',
'HTTP_ACCEPT': 'text/xml,application/xml,application/xhtml+xml,
text/html;q=0.9,text/plain;q=0.8,video/x-mng,image/png,image/jpeg,
image/gif;q=0.2,text/css,*/*;q=0.1',
'REQUEST_URI': '/cgi-bin/test.py?a=1',
'PATH': '/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin',
'QUERY_STRING': 'a=1&b=2',
116
'SCRIPT_FILENAME': '/var/www/cgi-bin/test.py',
'HTTP_KEEP_ALIVE': '300',
'HTTP_HOST': 'localhost',
'REQUEST_METHOD': 'GET',
'SERVER_SIGNATURE': 'Apache/1.3.23 Server at rnd.onego.ru Port 80',
'SCRIPT_NAME': '/cgi-bin/test.py',
'SERVER_ADMIN': 'root@localhost',
'SERVER_SOFTWARE': 'Apache/1.3.23 (Unix) (Red-Hat/Linux)
mod_python/2.7.8 Python/1.5.2 PHP/4.1.2',
'SERVER_PROTOCOL': 'HTTP/1.0',
'REMOTE_PORT': '39251'
}
Следующий CGI-сценарий выдает черный квадрат (в нем используется модуль Image для
обработки изображений):
#!/usr/bin/python
import sys
print """Content-Type: image/jpeg
"""
import Image
i = Image.new("RGB", (10,10))
i.im.draw_rectangle((0,0,10,10), 1)
i.save(sys.stdout, "jpeg")
Модуль cgi
В Python имеется поддержка CGI в виде модуля cgi. Следующий пример показывает
некоторые из его возможностей:
#!/usr/bin/python
# -*- coding: cp1251 -*-
import cgi, os
# анализ запроса
f = cgi.FieldStorage()
if f.has_key("a"):
a = f["a"].value
else:
a = "0"
# обработка запроса
b = str(int(a)+1)
mytext = open(os.environ["SCRIPT_FILENAME"]).read()
mytext_html = cgi.escape(mytext)
# формирование ответа
print """Content-Type: text/html
117
В этом примере к заданному в форме числу прибавляется 1. Кроме того, выводится
исходный код самого сценария. Следует заметить, что для экранирования символов >, <,
& использована функция cgi.escape(). Для формирования Web-страницы применена
операция форматирования. В качестве словаря для выполнения подстановок использован
словарь vars() со всеми локальными переменными. Знаки процента пришлось удвоить,
чтобы они не интерпретировались командой форматирования. Стоит обратить внимание на
то, как получено значение от пользователя. Объект FieldStorage "почти" словарь, с тем
исключением, что для получения обычного значения нужно дополнительно посмотреть
атрибут value. Дело в том, что в сценарий могут передаваться не только текстовые
значения, но и файлы, а также множественные значения с одним и тем же именем.
Осторожно!
В примере выше проверка на допустимость произведена при вызове функции int(): если
было бы задано нечисловое значение, сценарий аварийно завершился, а пользователь
увидел Internal Server Error.
После анализа входных данных можно выделить фазу их обработки. В этой части CGI-
сценария вычисляются переменные для дальнейшего вывода. Здесь необходимо учитывать
не только значения переданных переменных, но и факт их присутствия или отсутствия, так
как это тоже может влиять на логику сценария.
В приведенных примерах имена появлялись в строке запроса только один раз. Некоторые
формы порождают несколько значений для одного имени. Получить все значения можно с
помощью метода getlist():
lst = form.getlist("fld")
Список lst будет содержать столько значений, сколько полей с именем fld получено из
web-формы (он может быть и пустым, если ни одно поле с заданным именем не было
заполнено).
#!/usr/bin/env python
118
import cgi
form = cgi.FieldStorage()
file_contents = ""
if form.has_key("filename"):
fileitem = form["filename"]
if fileitem.file:
file_contents = """<P>Содержимое переданного файла:
<PRE>%s</PRE>""" % fileitem.file.read()
<HTML><HEAD><TITLE>Загрузка файла</TITLE></HEAD>
<BODY><H1>Загрузка файла</H1>
<P><FORM ENCTYPE="multipart/form-data"
ACTION="getfile.cgi" METHOD="POST">
<br>Файл: <INPUT TYPE="file" NAME="filename">
<br><INPUT TYPE="submit" NAME="button" VALUE="Передать файл">
</FORM>
%s
</BODY></HTML>""" % file_contents
Для отладки CGI-сценария можно использовать модуль cgitb. При возникновении ошибки
этот модуль выдаст красочную HTML-страницу с указанием места возбуждения исключения.
В начале отлаживаемого сценария нужно поставить
import cgitb
cgitb.enable(1)
import cgitb
cgitb.enable(0, logdir="/tmp")
Только необходимо помнить, что следует убрать эти строки, когда сценарий будет отлажен,
так как он выдает кусочки кода сценария. Это может быть использовано
злоумышленниками, с тем чтобы найти уязвимости в CGI-сценарии или подсмотреть пароли
(если таковые присутствуют в сценарии).
119
Для ускорения работы CGI используются различные схемы, например, FastCGI или PCGI
(Persistent CGI). В данной лекции предлагается к рассмотрению специальным модуль для
web-сервера Apache, называемый mod_python.
<Directory "/var/www/html/mywebdir>
AddHandler python-program .py
PythonHandler mprocess
</Directory>
После этого необходимо перезапустить web-сервер и, если все прошло без ошибок, можно
приступать к написанию обработчика mprocess.py. Этот сценарий будет реагировать на
любой запрос вида http://localhost/*.py.
def handler(req):
req.content_type = "text/html"
req.send_http_header()
req.write("""<HTML><HEAD><TITLE>Hello, world!</TITLE></HEAD>
<BODY>Hello, world!</BODY></HTML>""")
return apache.OK
120
8. Web-сервер замечает, что сценарий-обработчик изменился, но не заметит
изменений в импортируемых в него модулях. Команда touch mprocess.py обновит
дату изменения файла сценария.
9. Отображение os.environ в обработчике может быть обрезанным. Кроме того,
вызываемые из сценария-обработчика другие программы его не наследуют, как это
происходит при работе с CGI-сценариями. Переменные можно получить другим
путем: req.add_common_vars(); params = req.subprocess_env.
10. Так как сценарий-обработчик не является "одноразовым", как CGI-сценарий, из-за
ошибок программирования (как самого сценария, так и других компонентов) могут
возникать утечки памяти (программа не освобождает ставшую ненужной память).
Следует установить значение параметра MaxRequestsPerChild (максимальное число
запросов, обрабатываемое одним процессом-потомком) больше нуля.
def authenhandler(req):
password = req.get_basic_auth_pw()
user = req.connection.user
if user == "user1" and password == "secret":
return apache.OK
else:
return apache.HTTP_UNAUTHORIZED
Эту функцию следует добавить в модуль mprocess.py, который был рассмотрен ранее.
Кроме того, нужно дополнить конфигурацию, назначив обработчик для запросов
идентификации (PythonAuthenHandler), а также обычные для Apache директивы AuthType,
AuthName, require, определяющие способ авторизации:
<Directory "/var/www/html/mywebdir>
AddHandler python-program .py
PythonHandler mprocess
PythonAuthenHandler mprocess
AuthType Basic
AuthName "My page"
require valid-user
</Directory>
Разумеется, это - всего лишь пример. В реальности идентификация может быть устроена
намного сложнее.
PythonPostReadRequestHandler
PythonTransHandler
PythonHeaderParserHandler
PythonAccessHandler
PythonAuthenHandler
121
Идентификация пользователя.
PythonTypeHandler
PythonFixupHandler
PythonHandler
PythonInitHandler
PythonLogHandler
PythonCleanupHandler
Некоторые из этих обработчиков работают только глобально, так как при вызове даже
каталог их приложения может быть неизвестен (таков, например,
PythonPostReadRequestHandler).
Среды разработки
Для создания Web-приложений применяются и более сложные средства, чем web-сервер с
расположенными на нем статическими документами и CGI-сценариями. В зависимости от
назначения такие программные системы называются серверами web-приложений,
системами управления содержимым (CMS, Content Management System), системы web-
публикации и средствами для создания WWW-порталов. Причем CMS-система может быть
выполнена как web-приложение, а средства для создания порталов могут базироваться на
системах web-публикации, для которых CMS-система является подсистемой. Поэтому,
выбирая систему для конкретных нужд, стоит уточнить, какие функции она должна
выполнять.
Язык Python, хотя и уступает PHP по количеству созданных на нем web-систем, имеет
несколько достаточно популярных приложений. Самым ярким примером средства для
создания сервера web-приложений является Zope (произносится "зоп") (см.
http://zope.org) (Z Object Publishing Environment, среда публикации объектов). Zope
имеет встроенный web-сервер, но может работать и с другими Web-серверами, например,
Apache. На основе Zope можно строить web-порталы, например, с помощью Plone/Zope, но
можно разрабатывать и собственные web-приложения. При этом Zope позволяет разделить
Форму, Содержание и Логику до такой степени, что Содержанием могут заниматься одни
люди (менеджеры по содержимому), Формой - другие (web-дизайнеры), а Логикой - третьи
(программисты). В случае с Zope Логику можно задать с помощью языка Python (или, как
вариант, Perl), Форма может быть создана в графических или специализированных web-
редакторах, а работа с содержимым организована через Web-формы самого Zope.
122
Zope и его объектная модель
В рамках этой лекции невозможно детально рассмотреть такой инструмент как Zope,
поэтому стоит лишь заметить, что он достаточно интересен не только в качестве среды
разработки web-приложений, но и с точки зрения подходов. Например, уникальная
объектно-ориентированная модель Zope позволяет довольно гибко описывать требуемое
приложение.
В ряду других подобных систем Zope на первый взгляд кажется странным и неприступным,
однако тем, кто с ним "на ты", он открывает большие возможности.
Документы Zope можно писать на языке DTML - дополнении HTML с синтаксисом для
включения значений подобно SSI (Server-Side Include). Например, для вставки переменной
с названием документа можно использовать
Следует заметить, что объекты Zope могут иметь свои атрибуты, а также методы, в
частности, написанные на языке Python. Переменные же могут появляться как из заданных
пользователем значений, так и из других источников данных (например, из базы данных
посредством выполнения выборки функцией SELECT).
Сейчас для описания документа Zope все чаще применяется ZPT (Zope Page Templates,
шаблоны страниц Zope), которые в свою очередь используют TAL (Template Attribute
Language, язык шаблонных атрибутов). Он позволяет заменять, повторять или пропускать
элементы документа описываемого шаблоном документа. "Операторами" языка TAL
являются XML-атрибуты из пространства имен TAL. Пространство имен сегодня описывается
следующим идентификатором:
xmlns:tal="http://xml.zope.org/namespaces/tal"
Оператор TAL имеет имя и значение (что выражается именем и значением атрибута).
Внутри значения обычно записано TAL-выражение, синтаксис которого описывается другим
языком - TALES (Template Attribute Language Expression Syntax, синтаксис выражений TAL).
123
<title tal:content="here/title">Doc Title</title>
Стоит заметить, что приведенный код сойдет за код на HTML, то есть, Web-дизайнер может
на любом этапе работы над проектом редактировать шаблон в HTML-редакторе (при
условии, что тот сохраняет незнакомые атрибуты из пространства имен tal). В этом
примере here/titleявляется выражением TALES. Текст Doc Title служит ориентиром для
web-дизайнера и заменяется значением выражения here/title, то есть, будет взято
свойство title документа Zope.
Примечание:
В Zope объекты имеют свойства. Набор свойств зависит от типа объекта, но может быть
расширен в индивидуальном порядке. Свойство id присутствует всегда, свойство title
обычно тоже указывается.
<ul>
<li tal:define="s modules/string"
tal:repeat="el python:s.digits">
<a href="DUMMY"
tal:attributes="href string:/digit/$el"
tal:content="el">SELECTION</a>
</li>
</ul>
<ul>
<li><a href="/digit/0">0</a></li>
<li><a href="/digit/1">1</a></li>
<li><a href="/digit/2">2</a></li>
<li><a href="/digit/3">3</a></li>
<li><a href="/digit/4">4</a></li>
<li><a href="/digit/5">5</a></li>
<li><a href="/digit/6">6</a></li>
<li><a href="/digit/7">7</a></li>
<li><a href="/digit/8">8</a></li>
<li><a href="/digit/9">9</a></li>
</ul>
Детали можно узнать по документации. Стоит лишь заметить, что итерация может
происходить по самым разным источникам данных: содержимому текущей папки, выборке
из базы данных или, как в приведенном примере, по объекту Python.
Любой программист знает, что программирование тем эффективнее, чем лучше удалось
"расставить скобки", выведя "общий множитель за скобки". Другими словами, хорошие
программисты должны быть достаточно "ленивы", чтобы найти оптимальную декомпозицию
решаемой задачи. При проектировании динамического web-сайта Zope позволяет
разместить "множители" и "скобки" так, чтобы достигнуть максимального повторного
124
использования кода (как разметки, так и сценариев). Помогает этому уникальный подход к
построению взаимоотношений между объектами: заимствование (acquisition).
Самое интересное, что заимствовать объекты можно также из параллельных папок. Пусть,
например, рядом с папкой Example находится папка Zigzag, в которой лежит нужный
объект (его наименование note). При этом в папке Example программиста интересует
объект index_html, внутри которого вызывается note. Обычный путь к объекту index_html
будет происходить по URI вроде http://zopeserver/Example/. А вот если нужно
использовать note из Zigzag (и в папке Example его нет), то URI будет:
http://zopeserver/Zigzag/Example/. Таким образом, указание пути в Zope отличается от
традиционного пути, скажем, в Unix: в пути могут присутствовать "зигзаги" через
параллельные папки, дающие возможность заимствовать объекты из этих папок. Таким
образом, можно сделать конкретную страницу, комбинируя один или несколько
независимых аспектов.
Заключение
125
Лекция #9: Сетевые приложения на Python
Работа с сокетами
Применяемая в IP-сетях архитектура клиент-сервер использует IP-пакеты для
коммуникации между клиентом и сервером. Клиент отправляет запрос серверу, на который
тот отвечает. В случае с TCP/IP между клиентом и сервером устанавливается соединение
(обычно с двусторонней передачей данных), а в случае с UDP/IP - клиент и сервер
обмениваются пакетами (дейтаграммамми) с негарантированной доставкой.
Каждый сетевой интерфейс IP-сети имеет уникальный в этой сети адрес (IP-адрес).
Упрощенно можно считать, что каждый компьютер в сети Интернет имеет собственный IP-
адрес. При этом в рамках одного сетевого интерфейса может быть несколько сетевых
портов. Для установления сетевого соединения приложение клиента должно выбрать
свободный порт и установить соединение с серверным приложением, которое слушает
(listen) порт с определенным номером на удаленном сетевом интерфейсе. Пара IP-адрес и
порт характеризуют сокет (гнездо) - начальную (конечную) точку сетевой коммуникации.
Для создания соединения TCP/IP необходимо два сокета: один на локальной машине, а
другой - на удаленной. Таким образом, каждое сетевое соединение имеет IP-адрес и порт
на локальной машине, а также IP-адрес и порт на удаленной машине.
Физический
Сетевой (IP)
Сеансовый
Представления
Сервер:
def do_something(x):
lst = map(None, x);
lst.reverse();
return string.join(lst, "")
Клиент:
import socket
Прежде всего, нужно запустить сервер. Сервер открывает сокет на локальной машине на
порту 33333, и адресе 127.0.0.1. После этого он слушает (listen()) порт. Когда на порту
появляются данные, принимается (accept()) входящее соединение. Метод accept()
возвращает пару - Socket-объект и адрес удаленного компьютера, устанавливающего
соединение (пара - IP-адрес, порт на удаленной машине). После этого можно применять
127
методы recv() и send() для общения с клиентом. В recv() задается число байтов в
очередной порции. От клиента может прийти и меньшее количество данных.
Модуль также содержит большое количество констант для указания протоколов, типов
сокетов, коммуникационных доменов и т.п. Другие функции модуля socket можно при
необходимости изучить по документации.
Модуль smtplib
smtplib.SMTP([host[, port]])
Параметры host и port задают адрес и порт SMTP-сервера, через который будет
отправляться почта. По умолчанию, port=25. Если host задан, конструктор сам установит
соединение, иначе придется отдельно вызывать метод connect(). Экземпляры класса SMTP
имеют методы для всех распространенных команд SMTP-протокола, но для отправки почты
достаточно вызова конструктора и методов sendmail() и quit():
128
Subject: From Python course student
MIME-Version: 1.0
Content-Type: text/plain; charset=Windows-1251
Content-Transfer-Encoding: 8bit
Следует заметить, что toaddr в сообщении (в поле To) и при отправке могут не совпадать.
Дело в том, что получатель и отправитель в ходе SMTP-сессии передается командами
SMTP-протокола. При запуске указанного выше примера на экране появится отладочная
информация (ведь уровень отладки задан равным 1):
В ходе одной SMTP-сессии можно отправить сразу несколько писем подряд, если не
вызывать quit().
129
smtplib.SMTPException
smtplib.SMTPServerDisconnected
smtplib.SMTPResponseException
Базовый класс для всех исключений, которые имеют код ответа SMTP-сервера.
smtplib.SMTPSenderRefused
Отправитель отвергнут
smtplib.SMTPRecipientsRefused
smtplib.SMTPDataError
smtplib.SMTPConnectError
smtplib.SMTPHeloError
Модуль poplib
Еще один протокол - POP3 (Post Office Protocol, почтовый протокол) - служит для приема
почты из почтового ящика на сервере (протокол определен в RFC 1725).
poplib.POP3(host[, port])
Где host - адрес POP3-сервера, port - порт на сервере (по умолчанию 110), pop_obj -
объект для управления сеансом работы с POP3-сервером.
p = poplib.POP3(SERVER)
print p.getwelcome()
# этап идентификации
print p.user(USERNAME)
130
print p.pass_(USERPASSWORD)
# этап транзакций
response, lst, octets = p.list()
print response
for msgnum, msgsize in [i.split() for i in lst]:
print "Сообщение %(msgnum)s имеет длину %(msgsize)s" % vars()
print "UIDL =", p.uidl(int(msgnum)).split()[2]
if int(msgsize) > 32000:
(resp, lines, octets) = p.top(msgnum, 0)
else:
(resp, lines, octets) = p.retr(msgnum)
msgtxt = "\n".join(lines)+"\n\n"
msg = email.message_from_string(msgtxt)
print "* От: %(from)s\n* Кому: %(to)s\n* Тема: %(subject)s\n" % msg
# msg содержит заголовки сообщения или все сообщение (если оно небольшое)
# этап обновления
print p.quit()
Примечание:
...
+OK Sayonara
Команда
Метод Описание
POP3
getwelcome() Получает строку s с приветствием POP3-сервера
user(name) USER Посылает команду USER с указанием имени пользователя name.
name Возвращает строку с ответом сервера
pass_(pwd) PASS pwd Отправляет пароль пользователя в команде PASS. После этой
команды и до выполнения команды QUIT почтовый ящик
блокируется
apop(user, APOP user Идентификация на сервере по APOP
secret) secret
rpop(user) RPOP user Идентификация по методу RPOP
stat() STAT Возвращает кортеж с информацией о почтовом ящике. В нем m
- количество сообщений, l - размер почтового ящика в байтах
list([num]) LIST [num] Возвращает список сообщений в формате (resp, ['num
octets', ...]), если не указан num, и "+OK num octets", если
указан. Список lst состоит из строк в формате "num octets".
retr(num) RETR num Загружает с сервера сообщение с номером num и возвращает
131
кортеж с ответом сервера (resp, lst, octets)
dele(num) DELE num Удаляет сообщение с номером num
rset() RSET Отменяет пометки удаления сообщений
noop() NOOP Ничего не делает (поддерживает соединение)
quit() QUIT Отключение от сервера. Сервер выполняет все необходимые
изменения (удаляет сообщения) и снимает блокировку
почтового ящика
top(num, TOP num Команда аналогична RETR, но загружает только заголовок и
lines) lines lines строк тела сообщения. Возвращает кортеж (resp, lst,
octets)
uidl([num]) UIDL Сокращение от "unique-id listing" (список уникальных
[num] идентификаторов сообщений). Формат результата: (resp, lst,
octets), если num не указан, и "+OK num uniqid", если указан.
Список lst состоит из строк вида "+OK num uniqid"
В этой таблице num обозначает номер сообщения (он не меняется на протяжении всей
сессии), resp -- ответ сервера, возвращается для любой команды, начинается с "+OK " для
успешных операций (при неудаче возбуждается исключение poplib.proto_error).
Параметр octets обозначает количество байт в принятых данных. uniqid - идентификатор
сообщения, генерируемый сервером.
Стоит отметить, что при работе с WWW используется в основном протокол HTTP, однако
WWW охватывает не только HTTP, но и многие другие схемы (FTP, gopher, HTTPS и т.п.).
Используемая схема обычно указана в самом начале URL.
import urllib
doc = urllib.urlopen("http://python.onego.ru").read()
print doc[:40]
import urllib
data = {"search": "Python"}
enc_data = urllib.urlencode(data)
# метод GET
f = urllib.urlopen("http://searchengine.com/search" + "?" + enc_data)
print f.read()
# метод POST
f = urllib.urlopen("http://searchengine.com/search", enc_data)
print f.read()
Функция urlretrieve() позволяет записать заданный URL сетевой объект в файл. Она
имеет следующие параметры:
Здесь url - URL сетевого объекта, filename - имя локального файла для помещения
объекта, reporthook - функция, которая будет вызываться для сообщения о состоянии
загрузки, data - данные для метода POST (если он используется). Функция возвращает
133
кортеж (filepath, headers) , где filepath - имя локального файла, в который закачан
объект, headers - результат метода info() для объекта, возвращенного urlopen().
FILE = 'boost-1.31.0-9.src.rpm'
URL = 'http://download.fedora.redhat.com/pub/fedora/linux/core/3/SRPMS/' + FILE
download(URL, FILE)
scheme://netloc/path;parameters?query#fragment
где
scheme
134
netloc
Местонахождение в сети.
path
Путь к ресурсу.
params
Параметры.
query
Строка запроса.
frag
Идентификатор фрагмента.
quote(s, safe='/')
Функция экранирует символы в URL, чтобы их можно было отправлять на web-сервер. Она
предназначена для экранирования пути к ресурсу, поэтому оставляет '/' как есть.
Например:
>>> urllib.quote("[email protected]")
'rnd%40onego.ru'
>>> urllib.quote("a = b + c")
'a%20%3D%20b%20%2B%20c'
>>> urllib.quote("0/1/1")
'0/1/1'
>>> urllib.quote("0/1/1", safe="")
'0%2F1%2F1'
quote_plus(s, safe='')
Функция экранирует некоторые символы в URL (в строке запроса), чтобы их можно было
отправлять на web-сервер. Аналогична quote(), но заменяет пробелы на плюсы.
unquote(s)
>>> urllib.unquote('a%20%3D%20b%20%2B%20c')
'a = b + c'
unquote_plus(s)
>>> urllib.unquote_plus('a+=+b+%2B+c')
'a = b + c'
135
Разбирает URL в 6 компонентов (сохраняя экранирование символов):
scheme://netloc/path;params?query#frag
Пример:
Еще одна функция того же модуля urlparse позволяет корректно соединить две части URL
- базовую и относительную:
Возможности urllib2
import urllib2
136
opener = urllib2.build_opener(proxy_support,
authinfo,
urllib2.CacheFTPHandler)
# Установка поля с названием клиента
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
# Использование открывателя
f = urllib2.urlopen('http://localhost/mywebdir/')
print f.read()[:100]
Модуль urllib2 имеет и специальный класс для воплощения запроса на открытие URL.
Называется этот класс urllib2.Request. Его экземпляр содержит состояние запроса.
Следующий пример показывает, как получить доступ к каталогу с авторизацией, используя
добавление заголовка в HTTP-запрос:
Как видно из этого примера, ничего загадочного в авторизации нет: web-клиент вносит
(закодированные base64) идентификационные данные в поле Authorization HTTP-
запроса.
Примечание:
XML-RPC сервер
До сих пор высокоуровневые протоколы рассматривались с точки зрения клиента. Не
менее просто создавать на Python и их серверные части. Для иллюстрации того, как
разработать программу на Python, реализующую сервер, был выбран протокол XML-RPC.
Несмотря на свое название, конечному пользователю необязательно знать XML (об этом
языке разметки говорилось на одной из предыдущих лекций), так как он скрыт от него.
Сокращение RPC (Remote Procedure Call, вызов удаленной процедуры) объясняет суть
дела: с помощью XML-RPC можно вызывать процедуры на удаленном хосте. Причем при
помощи XML-RPC можно абстрагироваться от конкретного языка программирования за счет
использования общепринятых типов данных (строки, числа, логические значения и т.п.). В
137
языке Python вызов удаленной функции по синтаксису ничем не отличается от вызова
обычной функции:
import xmlrpclib
# Установить соединение
req = xmlrpclib.ServerProxy("http://localhost:8000")
try:
# Вызвать удаленную функцию
print req.add(1, 3)
except xmlrpclib.Error, v:
print "ERROR",
А вот как выглядит XML-RPC-сервер (для того чтобы попробовать пример выше,
необходимо сначала запустить сервер):
Внимание!
Заключение
В этой лекции на практических примерах и сведениях из документации были показаны
возможности, которые дает стандартный Python для работы в Интернете. Из сценария на
Python можно управлять соединением на уровне сокетов, а также использовать модули для
конкретного сетевого протокола или набора протоколов. Для работы с сокетами служит
модуль socket, а модули для высокоуровневых протоколов имеют такие названия как
smtplib, poplib, httplib и т.п. Для работы с системой WWW можно использовать модули
urllib, urllib2, urlparse. Указанные модули рассмотрены с точки зрения типичного
применения. Для решения нестандартных задач лучше обратиться к другим источникам:
документации, исходному коду модулей, поиску в Интернете. В этой лекции говорилось и о
серверной составляющей высокоуровневых сетевых протоколов. В качестве примера
приведена клиент-серверная пара для протокола XML-RPC. Этот протокол создан на основе
HTTP, но служит специальной цели.
138
Лекция #10: Работа с базой данных
Основные понятия реляционной СУБД
Реляционная база данных - это набор таблиц с данными.
Таким образом, DB-API определяет интерфейс прикладной программы с базой данных. Этот
интерфейс, описываемый ниже, должен реализовывать все модули расширения, которые
служат для связи Python-программ с базами данных. Единый API (в настоящий момент его
вторая версия) позволяет абстрагироваться от марки используемой базы данных, при
необходимости довольно легко менять одну СУБД на другую, изучив всего один набор
функций и методов.
Интерфейс модуля
Здесь необходимо сказать о том, что должен предоставлять модуль для удовлетворения
требований DB-API 2.0.
139
Методы объекта-соединения будут рассмотрены чуть позже.
Объект-соединение
140
Объект-курсор
Курсор (от англ. cursor - CURrrent Set Of Records, текущий набор записей) служит для
работы с результатом запроса. Результатом запроса обычно является одна или несколько
прямоугольных таблиц со столбцами-полями и строками-записями. Приложение может
читать и обрабатывать полученные таблицы и записи в таблице по одной, поэтому в
курсоре хранится информация о текущей таблице и записи. Конкретный курсор в любой
момент времени связан с выполнением одной SQL-инструкции.
141
размер для всех больших выходных параметров. Может использоваться, например,
для получения больших бинарных объектов (Binary Large Object, BLOB).
Объекты-типы
Объект Тип
STRING Строка и символ
BINARY Бинарный объект
NUMBER Число
DATETIME Дата и время
ROWID Идентификатор записи
None NULL-значение (отсутствующее значение)
Именно поэтому для демонстрации выбрана СУБД SQLite, работающая как под Unix, так и
под Windows. Кроме установки собственно SQLite (сайт http://sqlite.org) и модуля
сопряжения с Python (http://pysqlite.org), каких-либо дополнительных настроек проводить
не требуется, так как SQLite хранит данные базы в отдельном файле: сразу приступать к
созданию таблиц, занесению в них данных и произведению запросов нельзя. Выбранная
СУБД (в силу своей "легкости") имеет одну существенную особенность: за одним
небольшим исключением, СУБД SQLite не обращает внимания на типы данных (она хранит
все данные в виде строк), поэтому модуль расширения sqlite для Python проделывает
дополнительную работу по преобразованию типов. Кроме того, СУБД SQLite поддерживает
достаточно большое подмножество свойств стандарта SQL92, оставаясь при этом
небольшой и быстрой, что немаловажно, например, для web-приложений. Достаточно
сказать, что SQLite поддерживает даже транзакции.
Еще раз стоит повторить, что выбор учебной базы данных не влияет на синтаксис
использованных средств, так как модуль sqlite, который будет использоваться,
поддерживает DB-API 2.0, а значит, переход на любую другую СУБД потребует
минимальных изменений в вызове функции connect() и, возможно, использования более
удачных типов данных, свойственных целевой СУБД.
Знакомство с СУБД
Для создания базы данных нужно установить, какие таблицы (и другие объекты, например
индексы) в ней будут храниться, а также определить структуры таблиц (имена и типы
полей).
Задача - создание базы данных, в которой будет храниться телепрограмма. В этой базе
будет таблица со следующими полями:
• tvdate,
• tvweekday,
• tvchannel,
• tvtime1,
• tvtime2,
• prname,
• prgenre.
Здесь tvdate - дата, tvchannel - канал, tvtime1 и tvtime2 - время начала и конца
передачи, prname - название, prgenre - жанр. Конечно, в этой таблице есть
функциональная зависимость (tvweekday вычисляется на основе tvdate и tvtime1), но из
практических соображений БД к нормальным формам приводиться не будет. Кроме того,
таблица будет создана с названиями дней недели (устанавливает соответствие между
номером дня и днем недели):
• weekday,
• wdname.
143
необходимо перед этим создать базу данных, например, SQL-инструкцией CREATE
DATABASE):
import sqlite as db
c = db.connect(database="tvprogram")
cu = c.cursor()
try:
cu.execute("""
CREATE TABLE tv (
tvdate DATE,
tvweekday INTEGER,
tvchannel VARCHAR(30),
tvtime1 TIME,
tvtime2 TIME,
prname VARCHAR(150),
prgenre VARCHAR(40)
);
""")
except db.DatabaseError, x:
print "Ошибка: ", x
c.commit()
try:
cu.execute("""
CREATE TABLE wd (
weekday INTEGER,
wdname VARCHAR(11)
);
""")
except db.DatabaseError, x:
print "Ошибка: ", x
c.commit()
c.close()
import sqlite as db
c = db.connect(database="tvprogram")
cu = c.cursor()
try:
cu.execute("""DROP TABLE tv;""")
except db.DatabaseError, x:
print "Ошибка: ", x
c.commit()
try:
cu.execute("""DROP TABLE wd;""")
except db.DatabaseError, x:
print "Ошибка: ", x
c.commit()
c.close()
144
Наполнение базы данных
import sqlite as db
c = db.connect(database="tvprogram")
cu = c.cursor()
cu.execute("""DELETE FROM wd;""")
cu.executemany("""INSERT INTO wd VALUES (%s, %s);""",
enumerate(weekdays))
c.commit()
c.close()
Стоит напомнить, что встроенная функция enumerate() создает список пар номер-
значение, например:
Предположим, что телепрограмма задана в файле tv.csv в формате CSV (он уже
обсуждался):
10.02.2003 9.00|ОРТ|Новости|Новости|9.15
10.02.2003 9.15|ОРТ|"НЕЖНЫЙ ЯД"|Сериал|10.15
10.02.2003 10.15|ОРТ|"Маски-шоу"|Юмористическая программа|10.45
10.02.2003 10.45|ОРТ|"Человек и закон"||11.30
10.02.2003 11.30|ОРТ|"НОВЫЕ ПРИКЛЮЧЕНИЯ СИНДБАДА"|Сериал|12.00
c = db.connect(database="tvprogram")
cu = c.cursor()
cu.execute("""INSERT INTO tv
(tvdate, tvweekday, tvchannel, tvtime1, tvtime2, prname,
prgenre)
VALUES (
%(begd)s, %(wd)s, %(channel)s, %(begt)s, %(endt)s,
%(prname)s, %(prgenre)s);""", rec)
input_file.close()
c.commit()
Примечание:
Из-за небольшой ошибки в пакете sqlite конструкторы Date, Time и т.д. не попадают из
модуля sqlite.main при импорте из sqlite, поэтому пришлось добавить две строки,
специфичные для sqlite, в универсальный "модуль" с именем db.
import sqlite as db
c = db.connect(database="tvprogram")
cu = c.cursor()
cu.execute("SELECT weekday, wdname FROM wd ORDER BY weekday;")
for i, n in cu.fetchall():
print i, n
0 Воскресенье
1 Понедельник
2 Вторник
3 Среда
4 Четверг
5 Пятница
6 Суббота
7 Воскресенье
146
import sqlite as db
c = db.connect(database="tvprogram")
cu = c.cursor()
cu.execute("""
SELECT tvdate, tvtime1, wd.wdname, tvchannel, prname, prgenre
FROM tv, wd
WHERE wd.weekday = tvweekday
ORDER BY tvdate, tvtime1;""")
for rec in cu.fetchall():
dt = rec[0] + rec[1]
weekday = rec[2]
channel = rec[3]
name = rec[4]
genre = rec[5]
print "%s, %02i.%02i.%04i %s %02i:%02i %s (%s)" % (
weekday, dt.day, dt.month, dt.year, channel,
dt.hour, dt.minute, name, genre)
В этом примере в качестве типа для даты и времени используется тип из mx.DateTime.
Именно поэтому стало возможным получить год, месяц, день, час и минуту обращением к
атрибуту. Кстати, datetime-объект стандартного модуля datetime имеет те же атрибуты. В
общем случае для даты и времени может использоваться другой тип, поэтому если
получаемые из базы даты будут проходить более глубокую обработку, их следует
переводить во внутреннее представление сразу после получения по запросу. Тем самым
тип даты из модуля DB-API не будет влиять на другие части программы.
Выше везде импортировался модуль sqlite, с изменением его имени на db. Это было
сделано не случайно. Дело в том, что подобные модули, поддерживающие DB-API 2.0, есть
и для других СУБД, и даже не в единственном числе. Согласно информации на сайте
www.python.org DB-API 2.0-совместимые модули для Python имеют следующие СУБД или
протоколы доступа к БД:
Примечание:
Для СУБД PostgreSQL нужно взять не PyGreSQL, а psycopg, так как в первом есть
небольшие проблемы с типом для даты и времени при вставке параметров в методе
execute(). Кроме того, psycopg оптимизирован для скорости и многопоточности
(psycopg.threadsafety=2).
147
Таким образом, в примерах, используемых в этой лекции, вместо sqlite можно применять,
например, psycopg: результат должен быть тем же, если, конечно, соответствующий
модуль был установлен.
Однако в общем случае при переходе с одной СУБД на другую могут возникать нестыковки,
даже, несмотря на поддержку одной версии DB-API. Например, у модулей могут
различаться paramstyle. В этом случае придется немного переделать параметры к вызову
execute(). Могут быть и другие причины, поэтому переход на другую СУБД следует
тщательно тестировать.
Иметь интерфейс DB-API могут не только базы данных. Например, разработчики проекта
fssdb стремятся построить DB-API 2.0 интерфейс к... файловой системе.
Заключение
В рамках данной лекции были рассмотрены возможности связи Python с системами управления
реляционными базами данных. Для Python разработан стандарт, называемый DB-API (версия
2.0), которого должны придерживаться все разработчики модулей сопряжения с реляционными
базами данных. Благодаря этому API код прикладной программы становится менее зависимым
от марки используемой базы данных, его могут понять разработчики, использующие другие
базы данных. Фактически DB-API 2.0 описывает имена функций и классов, которые должен
содержать модуль сопряжения с базой данных, и их семантику. Модуль сопряжения должен
содержать класс объектов-соединений с базой данных и класс для курсоров - специальных
объектов, через которые происходит коммуникация с СУБД на прикладном уровне.
Здесь была использована СУБД SQLite и соответствующий модуль расширения Python для
сопряжения с этой СУБД - sqlite, так как он поддерживает DB-API 2.0 и достаточно прост в
установке. С его помощью были продемонстрированы основные приемы работы с базой данных:
создание и наполнение таблиц, выполнение выборок и анализ полученных данных.
В конце лекции дан список других пакетов и модулей, которые позволяют Python-программе
работать со многими современными СУБД.
Ссылки
148
Лекция #11: Многопоточные вычисления
О потоках управления
В современной операционной системе, даже не выполняющей ничего особенного, могут
одновременно работать несколько процессов (processes). Например, при запуске
программы запускается новый процесс. Функции для управления процессами можно найти
в стандартном модуле os языка Python. Здесь же речь пойдет о потоках.
Теперь можно представить себе, что в некоторой точке программы ниточка раздваивается,
и каждый поток идет своим путем. Каждый из образовавшихся потоков может в
дальнейшем еще несколько раз раздваиваться. (При этом один из потоков всегда остается
главным, и его завершение означает завершение всей программы.) В каждый момент
времени интерпретатор знает, какую команду какой поток должен выполнить, и уделяет
кванты времени каждому потоку. Такое, казалось бы, незначительное усложнение
механизма выполнения программы на самом деле требует качественных изменений в
программе - ведь деятельность потоков должна быть согласована. Нельзя допускать, чтобы
потоки одновременно изменяли один и тот же объект, результат такого изменения, скорее
всего, нарушит целостность объекта.
import threading
def proc(n):
print "Процесс", n
149
p1 = threading.Thread(target=proc, name="t1", args=["1"])
p2 = threading.Thread(target=proc, name="t2", args=["2"])
p1.start()
p2.start()
Класс Thread
Здесь group - группа потоков (пока что не используется, должен быть равен None), target
- объект, который будет вызван в методе run(), name - имя потока, args и kwargs -
последовательность и словарь позиционных и именованных параметров (соответственно)
для вызова заданного в параметре target объекта. В примере выше были использованы
только позиционные параметры, но то же самое можно было выполнить и с применением
именованных параметров:
import threading
def proc(n):
print "Процесс", n
150
То же самое можно проделать через наследование от класса threading.Thread с
определением собственного конструктора и метода run():
import threading
class T(threading.Thread):
def __init__(self, n):
threading.Thread.__init__(self, name="t" + n)
self.n = n
def run(self):
print "Процесс", self.n
p1 = T("1")
p2 = T("2")
p1.start()
p2.start()
Таймер
def hello():
print "Hello, world!"
151
t = Timer(30.0, hello)
t.start()
Замки
Простейший замок может быть реализован на основе класса Lock модуля threading. Замок
имеет два состояния: он может быть или открыт, или заперт. В последнем случае им
владеет некоторый поток. Объект класса Lock имеет следующие методы:
Замки позволяют ограничивать вход в некоторую область программы одним потоком. Замки
могут потребоваться для обеспечения целостности структуры данных. Например, если для
корректной работы программы требуется добавление определенного элемента сразу в
несколько списков или словарей, такие операции в многопоточном приложении следует
обставить замками. Вокруг атомарных операций над встроенными типами (операций,
которые не вызывают исполнение какого-то другого кода на Python) замки ставить
необязательно. Например, метод append() (встроенного) списка является атомарной
операцией, а тот же метод, реализованный пользовательским классом, может требовать
блокировок. В случае сомнений, конечно, лучше перестраховаться и поставить замки,
однако следует минимизировать общее время действия замка, так как замок останавливает
другие потоки, пытающиеся попасть в ту же область программы. Отсутствие замка в
критической части программы, работающей над общими для двух и более потоков
ресурсами, может привести к случайным, трудноуловимым ошибкам.
В этом примере два потока (t1 и t2) запрашивают замки к одним и тем же ресурсам (A и B),
но в разном порядке, отчего получается, что ни у того, ни у другого не хватает ресурсов
для дальнейшей работы, и они оба безнадежно повисают, ожидая освобождения нужного
ресурса. Благодаря операторам print можно увидеть последовательность событий:
Семафоры
Семафор может применяться для охраны ограниченного ресурса. Например, с его помощью
можно вести пул соединений с базой данных. Пример такого использования семафора
(заимствован из документации к Python) дан ниже:
153
from threading import BoundedSemaphore
maxconnections = 5
# Подготовка семафора
pool_sema = BoundedSemaphore(value=maxconnections)
# Внутри потока:
pool_sema.acquire()
conn = connectdb()
# ... использование соединения ...
conn.close()
pool_sema.release()
События
Условия
154
notify() и notifyAll() другими потоками. Необязательный аргумент timeout
задает таймаут ожидания в секундах. При выходе из ожидания поток снова
запрашивает замок и возвращается из метода wait().
• notify()Выводит из режима ожидания один из потоков, ожидающих данные
условия. Метод можно вызвать, только овладев замком, ассоциированным с
условием. Документация предупреждает, что в будущих реализациях модуля из
целей оптимизации этот метод будет прерывать ожидание сразу нескольких потоков.
Сам по себе метод notify() не приводит к продолжению выполнения ожидавших
условия потоков, так как этому препятствует занятый замок. Потоки получают
управление только после снятия замка потоком, вызвавшим метод notify().
• notifyAll()Этот метод аналогичен методу notify(), но прерывает ожидание всех
ждущих выполнения условия потоков.
import threading
cv = threading.Condition()
class Item:
"""Класс-контейнер для элементов, которые будут потребляться
в потоках"""
def __init__(self):
self._items = []
def is_available(self):
return len(self._items) > 0
def get(self):
return self._items.pop()
def make(self, i):
self._items.append(i)
item = Item()
def consume():
"""Потребление очередного элемента (с ожиданием его появления)"""
cv.acquire()
while not item.is_available():
cv.wait()
it = item.get()
cv.release()
return it
def consumer():
while True:
print consume()
def produce(i):
"""Занесение нового элемента в контейнер и оповещение потоков"""
cv.acquire()
item.make(i)
cv.notify()
cv.release()
p1 = threading.Thread(target=consumer, name="t1")
p1.setDaemon(True)
p2 = threading.Thread(target=consumer, name="t2")
p2.setDaemon(True)
p1.start()
p2.start()
produce("ITEM1")
produce("ITEM2")
produce("ITEM3")
produce("ITEM4")
155
p1.join()
p2.join()
Очередь
Собственно, здесь можно привести аналог примера выше, но уже с использованием класса
Queue.Queue:
item = Queue.Queue()
def consume():
"""Потребление очередного элемента (с ожиданием его появления)"""
return item.get()
def consumer():
while True:
print consume()
def produce(i):
"""Занесение нового элемента в контейнер и оповещение потоков"""
item.put(i)
p1 = threading.Thread(target=consumer, name="t1")
p1.setDaemon(True)
p2 = threading.Thread(target=consumer, name="t2")
p2.setDaemon(True)
p1.start()
p2.start()
produce("ITEM1")
produce("ITEM2")
produce("ITEM3")
produce("ITEM4")
p1.join()
p2.join()
Следует отметить, что все блокировки спрятаны в реализации очереди, поэтому в коде они
явным образом не присутствуют.
Модуль thread
По сравнению с модулем threading, модуль thread предоставляет низкоуровневый доступ
к потокам. Многие функции модуля threading, который рассматривался до этого,
156
реализованы на базе модуля thread. Здесь стоит сделать некоторые замечания по
применению потоков вообще. Документация по Python предупреждает, что использование
потоков имеет особенности:
global champion
def run(n):
"""Программа бега n-го участника (потока)"""
global champion
while 1:
for i in range(10000): # интенсивные вычисления
pass
graph_lock.acquire()
positions[n] += 1 # передвижение на шаг
if positions[n] == distance: # если уже финиш
if champion is None: # и чемпион еще не определен,
champion = colors[n] # назначается чемпион
graph_lock.release()
break
graph_lock.release()
def ready_steady_go():
"""Инициализация начальных позиций и запуск потоков"""
graph_lock.acquire()
for i in range(nrunners):
positions[i] = 0
threading.Thread(target=run, args=[i,]).start()
graph_lock.release()
def update_positions():
157
"""Обновление позиций"""
graph_lock.acquire()
for n in range(nrunners):
c.coords(rects[n], 0, n*h, positions[n], n*h+h2)
tk.update_idletasks() # прорисовка изменений
graph_lock.release()
def quit():
"""Выход из программы"""
tk.quit()
sys.exit(0)
Примечание:
Эта программа использует некоторые возможности языка Python 2.3 (встроенную функцию
sum() и списковые включения), поэтому для ее выполнения нужен Python версии не
меньше 2.3.
Заключение
Навыки параллельного программирования необходимы любому профессиональному
программисту. Одним из вариантов организации (псевдо) параллельного
программирования является многопоточное программирование (другой вариант, более
свойственный Unix-системам - многопроцессное программирование - здесь не
рассматривается). В обычной (однопоточной) программе действует всего один поток
управления, а в многопоточной одновременно могут работать несколько потоков.
158
использования двумя различными потоками, дабы не нарушить целостность изменяемых
структур данных или логику работы с внешними ресурсами. Для ограждения участков кода
используются замки и семафоры.
159
Лекция #12: Создание приложений с графическим
интерфейсом пользователя
Обзор графических библиотек
Строить графический интерфейс пользователя (GUI, Graphical User Interface) для
программ на языке Python можно при помощи соответствующих библиотек компонентов
графического интерфейса или, используя кальку с английского, библиотек виджетов.
В лекции будет рассматриваться пакет Tkinter, который по сути является оберткой для
Tcl/Tk - известного графического пакета для сценарного языка Tcl. На примере этого
пакета легко изучить основные принципы построения графического интерфейса
пользователя.
О графическом интерфейсе
Почти все современные графические интерфейсы общего назначения строятся по модели
WIMP - Window, Icon, Menu, Pointer (окно, иконка, меню, указатель). Внутри окон рисуются
160
элементы графического интерфейса, которые для краткости будут называться
виджетами (widget - штучка). Меню могут располагаться в различных частях окна, но их
поведение достаточно однотипно: они служат для выбора действия из набора
предопределенных действий. Пользователь графического интерфейса "объясняет"
компьютерной программе требуемые действия с помощью указателя. Обычно указателем
служит курсор мыши или джойстика, однако есть и другие "указательные" устройства. С
помощью иконок графический интерфейс приобретает независимость от языка и в
некоторых случаях позволяет быстрее ориентироваться в интерфейсе.
Основы Tk
Основная черта любой программы с графическим интерфейсом - интерактивность.
Программа не просто что-то считает (в пакетном режиме) от начала своего запуска до
конца: ее действия зависят от вмешательства пользователя. Фактически, графическое
приложение выполняет бесконечный цикл обработки событий. Программа, реализующая
графический интерфейс, событийно-ориентирована. Она ждет от интерфейса событий,
которые и обрабатывает сообразно своему внутреннему состоянию.
Но этого для работы графической программы недостаточно. Дело в том, что некоторые
виджеты в графической программе должны быть взаимосвязаны определенным образом.
Например, полоска прокрутки может быть взаимосвязана с текстовым виджетом: при
использовании полоски текст в виджете должен двигаться, и наоборот, при перемещении
по тексту полоска должна показывать текущее положение. Для связи между виджетами в
Tk используются переменные, через которые виджеты и передают друг другу параметры.
Классы виджетов
Все эти классы не имеют отношений наследования друг с другом - они равноправны. Этот
набор достаточен для построения интерфейса в большинстве случаев.
События
162
Reparent Изменение родителя окна
Visibility Изменение видимости окна
"<ButtonPress-3>" или просто "<3>" - щелчок правой кнопки мыши (то есть, третьей, если
считать на трехкнопочной мыши слева-направо). "<Shift-Double-Button-1>" - двойной
щелчок мышью (левой кнопкой) с нажатой кнопкой Shift. В качестве модификаторов могут
быть использованы следующие (список неполный):
Просто символ обозначает событие - нажатие клавиши. Например, "k" - тоже, что
"<KeyPress-k>". Для неалфавитно-цифровых клавиш есть специальные названия:
Здесь <space> обозначает пробел, а <less> - знак меньше. <Left>, <Right>, <Up>, <Down> -
стрелки. <Prior>, <Next> - это PageUp и PageDown. Остальные клавиши более или менее
соответствуют надписям на стандартной клавиатуре.
Примечание:
Следует заметить, что Shift_L, в отличие от Shift, нельзя использовать как модификатор.
163
При нажатии клавиши Esc в окне можно увидеть примерно следующее:
char: '\x1b'
delta: 9
height: 0
keycode: 9
keysym: 'Escape'
keysym_num: 65307
num: 9
send_event: False
serial: 159
state: 0
time: -1072960858
type: '2'
widget: <Tkinter.Text instance at 0x401e268c>
width: 0
x: 83
x_root: 448
y: 44
y_root: 306
164
widget.config(option=value, ...)
widget["option"] = value
value = widget["option"]
widget.keys()
В случае, когда имя свойства совпадает с ключевым словом языка Python, принято
использовать после имени одиночное подчеркивание. Так, свойство class нужно задавать
как class_, а to как to_.
Следующий пример показывает окно с двумя виджетами внутри - полем ввода и надписью.
С помощью переменной надпись напрямую связана с полем ввода. Этот пример нарочно
использует очень много свойств, чтобы продемонстрировать возможности по
конфигурированию:
При желании можно задать стилевые опции для всех виджетов сразу: с помощью метода
tk_setPalette(). Помимо использованных выше свойств в этом методе можно
использовать selectForeground и selectBackground (передний план и фон выделения),
selectColor (цвет в выбранном состоянии, например, у Checkbutton), insertBackground
(цвет точки вставки) и некоторые другие.
Примечание:
Получить значение из поля ввода можно и при помощи метода get(). Например, если
назвать объект класса Entry именем e, получить значение можно так: e.get(). Правда,
этот метод не обладает той же гибкостью, что метод get() экземпляров класса для
форматированного текста Text: можно взять только все значение целиком.
Для того чтобы показать работу с нетривиальным виджетом, можно взять виджет
ScrolledText из одноименного модуля Python. Этот виджет аналогичен рамке с
форматированным текстом и вертикальной полосой прокрутки:
166
for x in range(1, 1024): # виджет наполняется текстовым
содержимым
txt.insert(END, str(2L**x)+"\n")
tk.mainloop()
167
page=Text(tk, background="White", width=64) # поле с html-
кодом
page.grid(row=1, column=1)
def fetch_url(event):
click_point = "@%s,%s" % (event.x, event.y)
trs = txt.tag_ranges("href") # список областей текста,
отмеченных как href
url = ""
# определяется, на какой участок пришелся щелчок мыши, и
берется
# соответствующий ему URL
for i in range(0, len(trs), 2):
if txt.compare(trs[i], "<=", click_point) and \
txt.compare(click_point, "<=", trs[i+1]):
url = txt.get(trs[i], trs[i+1])
html_doc = urllib.urlopen(url).read()
addr.delete("1.0", END)
addr.insert("1.0", url) # URL помещается в поле адреса
page.delete("1.0", END)
page.insert("1.0", html_doc) # показывается HTML-документ
168
Для придания некоторым участкам текста особых свойств необходимо их отметить тегом. В
данном случае URL отмечается тегом href. Позднее с помощью метода tag_config()
задаются свойства отображения текста, отмеченного таким тегом. Методом tag_bind()
привязывается некоторое событие (щелчок мыши) с вызовом заданной функции
(fetch_url()).
Менеджеры расположения
# Первая рамка:
# Сначала две кнопки прикрепляются к левому краю
b[1, 1].pack(side=LEFT, fill=BOTH, expand=1)
b[1, 2].pack(side=LEFT, fill=BOTH, expand=1)
# Еще две - к нижнему
b[1, 3].pack(side=BOTTOM, fill=Y)
b[1, 4].pack(side=BOTTOM, fill=BOTH)
# Вторая рамка:
# Две кнопки сверху
b[2, 1].grid(row=0, column=0, sticky=NW+SE)
b[2, 2].grid(row=0, column=1, sticky=NW+SE)
# и одна на две колонки в низу
b[2, 3].grid(row=1, column=0, columnspan=2, sticky=NW+SE)
# Третья рамка:
# Кнопки высотой и шириной в 40% рамки, якорь в левом верхнем
углу.
# Координаты якоря 1/10 от ширины и высоты рамки
b[3, 1].place(relx=0.1, rely=0.1, relwidth=0.4, relheight=0.4,
anchor=NW)
# Кнопка строго по центру. Якорь в центре кнопки
b[3, 2].place(relx=0.5, rely=0.5, relwidth=0.4, relheight=0.4,
anchor=CENTER)
# Якорь по центру кнопки. Координаты якоря 9/10 от ширины и
высоты рамки
169
b[3, 3].place(relx=0.9, rely=0.9, relwidth=0.4, relheight=0.4,
anchor=CENTER)
tk.mainloop()
Результат следующий:
Менеджер grid помещает виджеты в клетки сетки (это очень похоже на способ верстки
таблиц в HTML). Каждому располагаемому виджету даются координаты в одной из ячеек
сетки (row - строка, column - столбец), а также, если нужно, столько последующих ячеек (в
строках ниже или в столбцах правее) сколько он может занять (свойства rowspan или
columnspan). Это самый гибкий из всех менеджеров.
Изображения в Tkinter
tk = Tkinter.Tk()
c = Tkinter.Canvas(tk, width=128, height=128)
src_img = Image.open(FILENAME)
img = ImageTk.PhotoImage(src_img)
c.create_image(0, 0, image=img, anchor="nw")
c.pack()
170
Tkinter.Label(tk, text=FILENAME).pack()
tk.mainloop()
В результате получается:
tk = Tk()
# Рисунок 300x300 пикселей, фон - белый
c = Canvas(tk, width=300, height=300, bg="white")
171
c.create_text((205, 205), text="Hello,\nmy friend!",
justify=CENTER, anchor="se")
c.create_oval((205, 205, 206, 206), outline="red")
c.pack()
tk.mainloop()
Следует заметить, что методы create_* создают объекты, свойства которых можно менять
в дальнейшем: переместить в другое место, перекрасить, удалить, изменить порядок и т.д.
В следующем примере можно нарисовать кружок, меняющий цвет по щелчку мыши:
tk = Tk()
c = Canvas(tk, bg="White", width="4i", height=300,
relief=SUNKEN)
c.pack(expand=1, fill=BOTH)
def change_ball(event):
c.coords(CURRENT, (event.x-R, event.y-R, event.x+R,
event.y+R))
c.itemconfigure(CURRENT, fill=choice(colors))
172
расположен в месте щелчка мыши) и затем изменен цвет случайным образом методом
itemconfigure(). Тег CURRENT в Tkinter использован для указания объекта, который принял
событие.
def show():
global img, imgobj
# Запрос на имя файла
filename = tkFileDialog.askopenfilename()
if filename != (): # Если имя файла было задано
пользователем
# рисуется изображение из файла
src_img = Image.open(filename)
img = ImageTk.PhotoImage(src_img)
# конфигурируется изображение на рисунке
c.itemconfigure(imgobj, image=img, anchor="nw")
tk = Tk()
main_menu = Menu(tk) # формируется меню
tk.config(menu=main_menu) # меню добавляется к окну
file_menu = Menu(main_menu) # создается подменю
main_menu.add_cascade(label="File", menu=file_menu)
# Заполняется меню File
file_menu.add_command(label="Open", command=show)
file_menu.add_separator() # черта для отделения пунктов меню
file_menu.add_command(label="Exit", command=tk.destroy)
tk.mainloop()
173
Стоит отметить, что здесь пришлось применить две глобальные переменные. Это не очень
хорошо. Существует другой подход, когда приложение создается на основе окна верхнего
уровня. Таким образом, само приложение становится особым виджетом. Переделанная
программа представлена ниже:
class App(Tk):
def __init__(self):
Tk.__init__(self)
main_menu = Menu(self)
self.config(menu=main_menu)
file_menu = Menu(main_menu)
main_menu.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Open", command=self.show_img)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=self.destroy)
def show_img(self):
filename = tkFileDialog.askopenfilename()
if filename != ():
src_img = Image.open(filename)
self.img = ImageTk.PhotoImage(src_img)
self.c.itemconfigure(self.imgobj, image=self.img,
anchor="nw")
app = App()
app.mainloop()
Заключение
В этой лекции было дано представление о (невизуальном) программировании графического
интерфейса для Python на примере пакета Tkinter. Программа с графическим интерфейсом
- событийно-управляемая программа, проводящая время в цикле обработки событий.
События могут быть вызваны функционированием графического интерфейса или другими
причинами (например, по таймеру). Обычно события возникают в виджетах и некоторые из
них должны обрабатываться приложением. В Tkinter событие представлено отдельным
объектом, из атрибутов которого можно установить, каково было положение указателя
(курсора мыши), в каком виджете произошло событие и т.п.
Нужно отметить, что для построения интерфейса можно использовать не только чистый
Tkinter. Например, в Python доступны модули ScrolledText и Tix, пополняющие набор
виджетов. Кроме того, можно найти пакеты для специальных виджетов (например, для
отображения дерева).
Ссылки
Список актуальных ссылок на различные графические библиотеки можно найти по
следующему адресу:
http://phaseit.net/claird/comp.lang.python/python_GUI.html
175
Лекция #13: Интеграция Python с другими языками
программирования
C API
Доступные из языка Python модули расширяются за счет модулей расширения
(extension modules). Модули расширения можно писать на языке C или C++ и вызывать
из программ на Python. В этой лекции речь пойдет о реализации Python, называемой
CPython(Jython, реализация Python на платформе Java не будет рассматриваться).
Через C API доступны все встроенные возможности языка Python (при необходимости,
детальнее изучить этот вопрос можно по документации):
176
• поддержка протоколов абстрактных объектов: + Протокол объекта
(PyObject_Print(), PyObject_HasAttrString(), PyObject_GetAttrString(),
PyObject_HasAttr(), PyObject_GetAttr(), PyObject_RichCompare(), ...,
PyObject_IsInstance(), PyCallable_Check(), PyObject_Call(), PyObject_Dir() и
другие). То, что должен уметь делать любой объект Python + Протокол числа
(PyNumber_Check(), PyNumber_Add(), ..., PyNumber_And(), ...,
PyNumber_InPlaceAdd(), ..., PyNumber_Coerce(), PyNumber_Int(), ...). То, что
должен делать любой объект, представляющий число + Протокол
последовательности (PySequence_Check(), PySequence_Size(),
PySequence_Concat(), PySequence_Repeat(), PySequence_InPlaceConcat(), ...,
PySequence_GetItem(), ..., PySequence_GetSlice(), PySequence_Tuple(),
PySequence_Count(), ...) + Протокол отображения (например, словарь является
отображением) (функции: PyMapping_Check(), PyMapping_Length(),
PyMapping_HasKey(), PyMapping_Keys(), ..., PyMapping_SetItemString(),
PyMapping_GetItemString() и др.) + Протокол итератора (PyIter_Check(),
PyIter_Next()) + Протокол буфера (PyObject_AsCharBuffer(),
PyObject_AsReadBuffer(), PyObject_AsWriteBuffer(),
PyObject_CheckReadBuffer())
• поддержка встроенных типов данных. Аналогично описанному в предыдущем
пункте, но уже для конкретных встроенных типов данных. Например: + Булевский
объект (PyBool_Check() - проверка принадлежности типу PyBool_Type, Py_False -
объект False, Py_True - объект True,
• управление памятью (то есть кучей интерпретатора Python) (функции
PyMem_Malloc(), PyMem_Realloc(), PyMem_Free(), PyMem_New(), PyMem_Resize(),
PyMem_Del()). Разумеется, можно применять и средства выделения памяти C/C++,
однако, в этом случае не будут использоваться преимущества управления памятью
интерпретатора Python (сборка мусора и т.п.). Кроме того, освобождение памяти
нужно производить тем же способом, что и ее выделение. Еще раз стоит напомнить,
что повторное освобождение одной и той же области памяти (а равно использование
области памяти после ее освобождения) чревато серьезными ошибками, которые
компилятор C не имеет возможности распознать.
• структуры для определения объектов встроенных типов (PyObject, PyVarObject и
много других)
Примечание
Под протоколом здесь понимается набор методов, которые должен поддерживать тот или
иной класс для организации операций со своими экземплярами. Эти методы доступны не
только из Python (например, len(a) дает длину последовательности), но и из кода на C
(PySequence_Length()).
Привязка к Python, хотя и может быть несколько автоматизирована, все же это процесс
творческий. Дело в том, что если предполагается интенсивно использовать библиотеку в
Python, ее привязку желательно сделать как можно более тщательно. Возможно, в ходе
привязки будет сделана объектно-ориентированная надстройка или другие архитектурные
изменения, которые позволят упростить использование библиотеки.
В качестве примера можно привести выдержку из исходного кода модуля md5, который
реализует функцию для получения md5-дайджеста. Модуль приводится в целях
иллюстрации (то есть, с сокращениями). Модуль вводит собственный тип данных, MD5Type,
поэтому можно увидеть не только реализацию функций, но и способ описания встроенного
177
типа. В рамках этого курса не изучить все тонкости программирования модулей
расширения, главное понять дух этого занятия. На комментарии автора курса лекций
указывает двойной слэш //:
// заголовочные файлы
#include "Python.h"
#include "md5.h"
// typedef struct {
// UINT4 state[4]; /* state (ABCD) */
// UINT4 count[2]; /* number of bits, modulo 2^64 (lsb
first) */
// unsigned char buffer[64]; /* input buffer */
// } MD5_CTX;
// Определения методов
static PyObject *
md5_update(md5object *self, PyObject *args)
{
unsigned char *cp;
int len;
178
// Даже возврат None требует увеличения счетчика ссылок
Py_INCREF(Py_None);
return Py_None;
}
// Метод digest
static PyObject *
md5_digest(md5object *self)
{
MD5_CTX mdContext;
unsigned char aDigest[16];
// и строка документации
PyDoc_STRVAR(digest_doc, "digest() -> string\n\ ...");
static PyObject *
md5_hexdigest(md5object *self)
{
// Реализация метода на C
}
180
return (PyObject *)md5p;
}
// Инициализация модуля
PyMODINIT_FUNC
initmd5(void)
{
PyObject *m, *d;
MD5type.ob_type = &PyType_Type;
// Инициализируется модуль
m = Py_InitModule3("md5", md5_functions, module_doc);
// Получается словарь с именами модуля
d = PyModule_GetDict(m);
// Добавляется атрибут MD5Type (тип md5-объекта) к словарю
PyDict_SetItemString(d, "MD5Type", (PyObject *)&MD5type);
// Добавляется целая константа digest_size к модулю
PyModule_AddIntConstant(m, "digest_size", 16);
}
/* File : demo.c */
/* Пример встраивания интерпретатора Python в другую программу */
#include "Python.h"
/* Инициализация интерпретатора */
Py_Initialize();
/* ... */
181
/* Выполнение операторов Python (как бы модуль __main__) */
PyRun_SimpleString("import time\n");
PyRun_SimpleString("print time.localtime(time.time())\n");
/* ... */
Компиляция этого примера с помощью компилятора gcc может быть выполнена, например,
так:
ver="2.3"
gcc -fpic demo.c -DHAVE_CONFIG_H -lm -lpython${ver} \
-lpthread -lutil -ldl \
-I/usr/local/include/python${ver} \
-L/usr/local/lib/python${ver}/config \
-Wl,-E \
-o demo
Использование SWIG
SWIG (Simplified Wrapper and Interface Generator, упрощенный упаковщик и генератор
интерфейсов) - это программное средства, сильно упрощающее (во многих случаях -
автоматизирующее) использование библиотек, написанных на C и C++, а также на других
языках программирования, в том числе (не в последнюю очередь!) на Python. Нужно
отметить, что SWIG обеспечивает достаточно полную поддержку практически всех
возможностей C++, включая предобработку, классы, указатели, наследование и даже
шаблоны C++. Последнее очень важно, если необходимо создать интерфейс к библиотеке
шаблонов.
182
Простой пример использования SWIG
Предположим, что есть программа на C, реализующая некоторую функцию (пусть это будет
вычисление частоты появления различных символов в строке):
/* File : freq.c */
#include <stdlib.h>
Для того чтобы можно было воспользоваться этой функцией из Python, нужно написать
интерфейсный файл (расширение .i) примерно следующего содержания:
/* File : freq.i */
%module freq
%typemap(out) int * {
int i;
$result = PyTuple_New(256);
for(i=0; i<256; i++)
PyTuple_SetItem($result, i, PyLong_FromLong($1[i]));
free($1);
}
После этого в рабочем каталоге появляется файлы _freq.so и freq.py, которые вместе и
дают доступ к требуемой функции:
183
Помимо этого, можно посмотреть на содержимое файла freq_wrap.c, который был порожден
SWIG: в нем, среди прочих вспомогательных определений, нужных самому SWIG, можно
увидеть что-то подобное проиллюстрированному выше примеру модуля md5. Вот фрагмент
этого файла с определением обертки для функции frequency():
if(!PyArg_ParseTuple(args,(char *)"s:frequency",&arg1))
goto fail;
result = (int *)frequency(arg1);
{
int i;
resultobj = PyTuple_New(256);
for(i=0; i<256; i++)
PyTuple_SetItem(resultobj, i,
PyLong_FromLong(result[i]));
free(result);
}
return resultobj;
fail:
return NULL;
}
Стоит еще раз напомнить, что в отличие от Python, в языке C/C++ управление памятью
должно происходить в явном виде. Именно поэтому добавлена функция free() при
преобразовании типа. Если этого не сделать, возникнут утечки памяти. Эти утечки можно
обнаружить, при многократном выполнении функции:
Если функция freq.frequency() имеет утечки памяти, выполняемый процесс очень быстро
займет всю имеющуюся память.
184
Современное состояние дел по излагаемому вопросу можно узнать по адресу:
http://www.python.org/moin/IntegratingPythonWithOtherLanguages
Java
185
jythonс -d -c -j lns.jar lines.py
Prolog
Для тех, кто хочет использовать Prolog из Python, существует несколько возможностей:
Эти три варианта реализуют различные способы интеграции возможностей Prolog в Python.
Первый вариант использует SWIG, второй организует общение с Prolog-системой через
конвейер, а третий является специализированной реализацией Prolog.
exec(compile(r"""
man('Socrates').
man('Democritus').
mortal(X) :- man(X).
"""))
WHO = Var()
queries = [mortal('Socrates'),
man(WHO),
mortal(WHO)]
for query in queries:
print "?", query
for _ in query():
print " yes:", query
? mortal(Socrates)
yes: mortal(Socrates)
? man(_)
yes: man(Socrates)
yes: man(Democritus)
? mortal(_)
yes: mortal(Socrates)
yes: mortal(Democritus)
186
Разумеется, это не "настоящий" Prolog, но с помощью модуля pylog любой, кому требуются
логические возможности Prolog в Python, может написать программу с использованием
Prolog-синтаксиса.
OCaml
Следующий пример (из Pycaml) показывает программу для OCaml, которая определяет
модуль для Python на OCaml и вызывает встроенный интерпретатор Python:
Pyrex
pyrexc primes.pyx
gcc primes.c -c -fPIC -I /usr/local/include/python2.3
gcc -shared primes.o -o primes.so
Разумеется, в Pyrex можно использовать C-библиотеки, именно поэтому он, как и SWIG,
может служить для построения оберток C-библиотек для Python.
Следует отметить, что для простых операций Pyrex применяет C, а для обращения к
объектам Python - вызовы Python/C API. Таким образом, объединяется выразительность
Python и эффективность C. Конечно, некоторые вещи в Pyrex не доступны, например,
генераторы, списковые включения и Unicode, однако, цель Pyrex - создание
быстродействующих модулей расширения, и для этого он превосходно подходит.
Ознакомится с Pyrex можно по документации (которая, к сожалению, есть пока только на
английском языке).
Заключение
В этой лекции кратко рассматривались основные возможности интеграции интерпретатора
Python и других систем программирования. Базовая реализация языка Python написана на
C, поэтому Python имеет программный интерфейс Python/C API, который позволяет
программам на C/C++ обращаться к интерпретатору Python, отдельным объектам, модулям
и типам данных. Состав Python/C API достаточно обширен, поэтому речь шла лишь о
некоторых основных его элементах.
Ссылки
Библиотека Boost Python для C++ http://www.boost.org
189
Лекция #14: Устройство интерпретатора языка Python
Лексический анализ
Лексический анализатор языка программирования разбивает исходный текст программы
(состоящий из одиночных символов) на лексемы - неделимые "слова" языка.
prog_example = """
for i in range(100): # comment
if i % 1 == 0: \
print ":", t**2
""".strip()
rl = StringIO.StringIO(prog_example).readline
А вот что выведет эта программа, разбив на лексемы исходный код примера:
prog_example:
1 NAME : 'for'
1 NAME : 'i'
1 NAME : 'in'
1 NAME : 'range'
50 OP : '('
2 NUMBER : '100'
50 OP : ')'
50 OP : ':'
52 COMMENT : '# comment'
4 NEWLINE : '\n'
5 INDENT : ' '
1 NAME : 'if'
1 NAME : 'i'
50 OP : '%'
2 NUMBER : '1'
50 OP : '=='
2 NUMBER : '0'
50 OP : ':'
1 NAME : 'print'
3 STRING : '":"'
50 OP : ','
1 NAME : 't'
50 OP : '**'
2 NUMBER : '2'
6 DEDENT : ''
0 ENDMARKER : ''
190
Фактически получен поток лексем, который может использоваться для различных целей.
Например, для синтаксического "окрашивания" кода на языке Python. Словарь
token.tok_name позволяет получить мнемонические имена для типа лексемы по номеру.
Синтаксический анализ
Вторая стадия преобразования исходного текста программы в байт-код интерпретатора
состоит в синтаксическом анализе исходного текста. Модуль parser содержит функции
suite() и expr() для построения деревьев синтаксического разбора соответственно
для кода программ и выражений Python. Модуль symbol содержит номера символов
грамматики Python, словарь для получения названия символа из грамматики Python.
pprint.pprint(parser.suite(prg).totuple())
print
pprint_ast(parser.suite(prg).totuple())
(257,
(264,
(265,
(266,
(269,
(1, 'print'),
(292,
(293,
(294,
(295,
(297,
(298,
(299,
(300,
(301,
(302,
(303, (304, (305, (2, '2')))),
(16, '*'),
(303, (304, (305, (2, '2')))))))))))))))),
(4, ''))),
191
(0, ''))
file_input
stmt
simple_stmt
small_stmt
print_stmt
token.NAME 'print'
test
and_test
not_test
comparison
expr
xor_expr
and_expr
shift_expr
arith_expr
term
factor
power
atom
token.NUMBER '2'
token.STAR '*'
factor
power
atom
token.NUMBER '2'
token.NEWLINE ''
token.ENDMARKER ''
Получение байт-кода
После того как получено дерево синтаксического разбора, компилятор должен превратить
его в байт-код, подходящий для исполнения интерпретатором. В следующей программе
проводятся отдельно синтаксический анализ, компиляция и выполнение (вычисление) кода
(и выражения) в языке Python:
import parser
prg = """2*2"""
ast = parser.expr(prg)
code = ast.compile('filename1.py')
print eval(code)
Здесь необходимо заметить, что недавно в Python появился пакет compiler, который
объединяет модули для работы анализа исходного кода на Python и генерации кода. В
данной лекции он не рассматривается, но те, кто хочет глубже изучить эти процессы,
может обратиться к документации по Python.
192
Изучение байт-кода
Для изучения байт-кода Python-программы можно использовать модуль dis (сокращение от
"дизассемблер"), который содержит функции, позволяющие увидеть байт-код в
мнемоническом виде. Следующий пример иллюстрирует эту возможность:
Легко догадаться, что LOAD означает загрузку значения в стек, STORE - выгрузку, PRINT -
печать, BINARY - бинарную операцию и т.п.
Отладка
В интерпретаторе языка Python заложены возможности отладки программ, а в стандартной
поставке имеется простейший отладчик - pdb. Следующий пример показывает программу,
которая подвергается отладке, и типичную сессию отладки:
# File myfun.py
def fun(s):
lst = []
for i in s:
lst.append(ord(i))
return lst
Профайлер
Для определения мест в программе, на выполнение которых уходит значительная часть
времени, обычно применяется профайлер.
Модуль profile
def print_close_matches(word):
print "\n".join(difflib.get_close_matches(word + "\n",
open("russian.txt")))
profile.run(r'print_close_matches("профайлер")')
провайдер
трайлер
195
бройлер
Модуль timeit
t = Timer("""
res = ""
196
for k in range(1000000,1010000):
res += str(k)
""")
print t.timeit(200)
t = Timer("""
res = []
for k in range(1000000,1010000):
res.append(str(k))
res = ",".join(res)
""")
print t.timeit(200)
t = Timer("""
res = ",".join([str(k) for k in range(1000000,1010000)])
""")
print t.timeit(200)
# Python 2.3
77.6665899754
10.1372740269
9.07727599144
# Python 2.4
9.26631307602
9.8416929245
7.36629199982
Оптимизация
Основная реализация языка Python пока что не имеет оптимизирующего компилятора,
поэтому разговор об оптимизации касается только оптимизации кода самим
программистом. В любом языке программирования имеются свои характерные приемы
оптимизации кода. Оптимизация (улучшение) кода может происходить в двух (зачастую
конкурирующих) направлениях: скорость и занимаемая память. В условиях достатка
оперативной памяти приложения обычно оптимизируют по скорости. При оптимизации по
времени программы для одноразового вычисления следует иметь в виду, что в общее время
решения задачи входит не только выполнение программы, но и время ее написания. Не
стоит тратить усилия на оптимизацию программы, если она будет использоваться очень
редко.
Если скорость работы программы при большой длине данных не устраивает, следует
поискать более эффективный алгоритм. Если же более эффективный алгоритм практически
нецелесообразен, можно попытаться провести оптимизацию кода.
Собственно, в данном примере для модуля timeit уже показан практический способ
нахождения оптимального кода. Стоит также отметить, что с помощью профайлера нужно
определить места кода, отнимающие наибольшую часть времени. Обычно это действия,
выполняемые в самом вложенном цикле. Можно попытаться вынести из цикла все, что
можно вычислить в более внешнем цикле или вообще вне цикла.
import psyco
psyco.full()
Pychecker
test(['d'], 'e')
198
Pychecker выдаст следующие предупреждения:
Можно переписать этот пример так, чтобы Pychecker выдавал меньше предупреждений:
import string
a = "a b c"
test(['d'], 'e')
Такое тоже бывает. Программист должен лишь убедиться, что он не сделал ошибки.
Исследование объекта
Даже самые примитивные объекты в языке программирования Python имеют возможности,
общие для всех объектов: можно получить их уникальный идентификатор (с помощью
функции id()), представление в виде строки - даже в двух вариантах (функции str() и
repr()); можно узнать атрибуты объекта с помощью встроенной функции dir() и во
многих случаях пользоваться атрибутом __dict__ для доступа к словарю имен объекта.
Также можно узнать, сколько других объектов ссылается на данный с помощью функции
sys.getrefcount(). Есть еще сборка мусора, которая применяется для освобождения
памяти от объектов, которые более не используются, но имеют ссылки друг на друга
(циклические ссылки). Сборкой мусора (garbage collection) можно управлять из модуля gc.
Все это подчеркивает тот факт, что объекты в Python существуют не сами по себе, а
являются частью системы: они и их отношения строго учитываются интерпретатором.
199
"неофициальную", которая использует особенности той или иной реализации. С помощью
"официальных" средств интроспекции можно получить информацию о принадлежности
объекта тому или иному классу (функция type()), проверить принадлежность экземпляра
классу (isinstance()), отношение наследования между классами (issubclass()), а также
получить информацию, о которой говорилось чуть выше. Это как бы приборная доска
машины. С помощью "неофициальной" интроспекции (это то, что под капотом) можно
получить доступ к чему угодно: к текущему фрейму исполнения и стеку, к байт-коду
функции, к некоторым механизмам интерпретатора (от загрузки модулей до полного
контроля над внутренней средой исполнения). Сразу же стоит сказать, что этот механизм
следует рассматривать (и тем более вносить изменения) очень деликатно: разработчики
языка не гарантируют постоянство этих механизмов от версии к версии, а некоторые
полезные модули используют эти механизмы для своих целей. Например, упомянутый
ранее ускоритель выполнения Python-кода psyco очень серьезно вмешивается во фреймы
исполнения, заменяя их своими объектами. Кроме того, разные реализации Python могут
иметь совсем другие внутренние механизмы.
>>> s = "abcd"
>>> dir(s)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__',
'__eq__', '__ge__', '__getattribute__', '__getitem__',
'__getnewargs__',
'__getslice__', '__gt__', '__hash__', '__init__', '__le__',
'__len__',
'__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__',
'__str__', 'capitalize', 'center', 'count', 'decode',
'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum',
'isalpha',
'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join',
'ljust',
'lower', 'lstrip', 'replace', 'rfind', 'rindex', 'rjust', 'rstrip',
'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title',
'translate', 'upper', 'zfill']
>>> id(s)
1075718400
>>> print str(s)
abcd
>>> print repr(s)
'abcd'
>>> type(s)
<type 'str'>
>>> isinstance(s, basestring)
True
>>> isinstance(s, int)
False
>>> issubclass(str, basestring)
True
и так далее. Более правильно использовать для получения всех этих сведений модуль
inspect.
Модуль inspect
201
inspect.isroutine Функция или метод
inspect.istraceback Трассировочный объект
Пример:
203
В кортеже entry первый элемент - класс, а второй - кортеж с
его
базовыми классами. Иначе entry - вложенный список.
level - уровень отступов
"""
for entry in tree:
if type(entry) is type(()):
c, bases = entry
print level * " ", c.__name__, \
"(" + ", ".join([b.__name__ for b in bases]) + ")"
elif type(entry) is type([]):
formattree(entry, level+1)
v = exceptions.__dict__.values()
exc_list = [e for e in v
if inspect.isclass(e) and issubclass(e, Exception)]
formattree(inspect.getclasstree(exc_list))
import inspect
def f():
fr = inspect.currentframe()
for a in dir(fr):
if a[:2] != "__":
print a, ":", str(getattr(fr, a))[:70]
f()
В результате получается
#!/usr/bin/python
def dbg_except():
"""Функция для отладки операторов try-except"""
import traceback, sys, string
print sys.exc_info()
print " ".join(traceback.format_exception(*sys.exc_info()))
def bad_func2():
raise StandardError
def bad_func():
bad_func2()
try:
bad_func()
except:
dbg_except()
205
bad_func()
File "pr143.py", line 14, in bad_func
bad_func2()
File "pr143.py", line 11, in bad_func2
raise StandardError
StandardError
Разумеется, это еще не все возможности модуля inspect и свойств интроспекции в Python,
а лишь наиболее интересные функции и атрибуты. Подробнее можно прочитать в
документации или даже в исходном коде модулей стандартной библиотеки Python.
Заключение
С помощью возможностей интроспекции удается рассмотреть фазы работы транслятора
Python: лексический анализ, синтаксический разбор и генерации кода для интерпретатора,
саму работу интерпретатора можно видеть при помощи отладчика.
Вместе с тем, в этой лекции было дано представление об использовании профайлера для
исследования того, на что больше всего тратится процессорное время в программе, а также
затронуты некоторые аспекты оптимизации Python-программ и варианты оптимизации кода
на Python по скорости.
206