MyTetra Share
Делитесь знаниями!
Python Часть I: Вводное руководство
27.12.2017
18:38
Текстовые метки: python
Раздел: Python - script-coding.com

Перейти на главную страничку сайта (список статей, файлы для скачивания)

ФОРУМ (здесь можно обсудить эту статью, а также любые проблемы программирования на различных макроязыках и в скриптовых средах)

Г. Россум, Ф.Л.Дж. Дрейк, Д.С. Откидач, Язык программирования Python

Часть I: Вводное руководство

1. Разжигая Ваш аппетит

Если Вы когда-либо писали большие shell-сценарии, Вам, возможно, знакомо чувство: хочется добавить еще какую-то возможность, но и без нее программа уже такая медленная, такая большая, или же Вам требуются системные вызовы или другие функции, доступные только в C... Обычно задача не настолько серьезная, чтобы переписывать все на C. Возможно, задача требует строк переменной длины или других типов данных (например, сортированные списки имен файлов), которые присутствуют в shell, но требуют значительных усилий для реализации на C, или же Вы не настолько знакомы с C.

Другая ситуация: Вам необходимо работать с несколькими библиотеками C, а на обычный цикл написание — компиляция — тестирование уходит слишком много времени. Вам нужно разрабатывать быстрее. Или же Вы уже написали программу, которая может использовать язык расширения, а у Вас нет желания его разрабатывать, писать и отлаживать для него интерпретатор, затем привязывать его к приложению.

В таком случае, Python — это то, что Вам нужно. Python прост в использовании, но это настоящий язык программирования, предоставляющий гораздо больше средств для структурирования и поддержки больших программ, чем shell. С другой стороны, он лучше обрабатывает ошибки, чем C и, будучи языком очень высокого уровня, имеет встроенные типы данных высокого уровня, такие как гибкие массивы и словари, эффективная реализация которых на C стоила бы Вам значительных затрат времени. Благодаря более общим типам данных, Python применим к более широкому кругу задач, чем Awk и даже Perl, в то время как многие вещи в языке Python делаются настолько же просто.

Python позволяет разбивать программы на модули, которые затем могут быть использованы в других программах. Python поставляется с большой библиотекой стандартных модулей, которые Вы можете использовать как основу для Ваших программ или в качестве примеров при изучении языка. Стандартные модули предоставляют средства для работы с файлами, системных вызовов, сетевые соединения и даже интерфейсы к различным графическим библиотекам.

Python — интерпретируемый язык, что позволит Вам сэкономить значительное количество времени, обычно расходуемого на компиляцию. Интерпретатор можно использовать интерактивно, что позволяет экспериментировать с возможностями языка, писать наброски программ или тестировать функции при разработке "снизу вверх". Он также удобен в качестве настольного калькулятора.

Python позволяет писать очень компактные и удобочитаемые программы. Программы, написанные на языке Python, обычно значительно короче эквивалента на C или C++ по нескольким причинам:

  • типы данных высокого уровня позволят Вам выразить сложные операции одной инструкцией;
  • группирование инструкций выполняется с помощью отступов вместо фигурных скобок;
  • нет необходимости в объявлении переменных.

Python расширяемый: знание C позволит Вам добавлять новые встраиваемые функции или модули для выполнения критичных операций с максимальной скоростью или написания интерфейса к коммерческим библиотекам, доступным только в двоичном виде. Вы можете вставить интерпретатор языка Python в приложение, написанное на C, и использовать его в качестве расширения или командного языка для этого приложения.

Кстати, язык назван в честь шоу BBC "Monty Python's Flying Circus" и не имеет ничего общего с мерзкими рептилиями.

Теперь, когда у Вас появился интерес к языку Python, Вы захотите узнать о нем более подробно. Так как лучший способ изучить язык — использовать его, приглашаем Вас так и сделать. В следующей главе мы объясним Вам, как пользоваться интерпретатором. Это довольно скучная информация, но она необходима для того, чтобы Вы могли попробовать приведенные в книге примеры. Последующие главы ознакомят Вас с различными особенностями языка на примерах, начиная с простых выражений, инструкций и типов данных, через функции и модули, заканчивая такими концепциями, как исключения и классы.

2. Использование интерпретатора

2.1. Вызов интерпретатора

Если расположение исполняемого файла Python включено в пути поиска, то для его запуска достаточно набрать команду

python 

Для выхода из интерпретатора необходимо набрать символ конца файла EOF (Ctrl-D в UNIX, Ctrl-Z в DOS и Windows) когда отображается первичное приглашение. Если это не работает, Вы можете набрать команду 'import sys; sys.exit()'.

Интерпретатор ведет себя подобно UNIX shell: если его стандартный ввод соединен с терминалом — читает и исполняет команды интерактивно; если он вызывается с именем файла в качестве аргумента или стандартный ввод интерпретатора ассоциирован с файлом, он считывает и исполняет команды из этого файла.

Еще одним способом использования интерпретатора является вызов 'python -c command [arg ... ]'. В этом случае исполняются одна или несколько инструкций в команде command, аналогично использованию опции -c в UNIX shell. Так как инструкции в языке Python часто содержат пробелы, воспринимаемые как разделитель аргументов, а также другие специальные символы, лучше всего заключать command полностью в двойные кавычки.

Следует заметить, что есть разница между 'python file' и 'python < file'. В последнем случае, запросы на ввод, такие как input() и raw_input() будут удовлетворяться из файла. Так как файл уже будет прочитан, прежде чем программа начнет исполняться, программа немедленно получит EOF. В первом же случае (который Вам обычно и будет нужен), ввод осуществляется из устройства, с которым соединен стандартный ввод интерпретатора Python.

Иногда бывает полезно после исполнения инструкций из файла перейти в интерактивный режим. Это можно сделать, передав параметр -i перед именем файла. (Такой способ не сработает, если чтение производится из стандартного ввода, по той же причине, которая описана в предыдущем абзаце.)

Описание всех возможных параметров командной строки интерпретатора приведено в приложении A.

2.1.1. Передача аргументов

Имя исполняемого файла (программы) и дополнительные аргументы передаются программе в переменной sys.argv, которая является списком строк. Его длина (количество элементов в списке) всегда больше или равна единице. Имя программы хранится в sys.argv[0]. В интерактивном режиме sys.argv[0] содержит пустую строку. Если же имя программы передано как '-' (имея в виду стандартный ввод) или интерпретатор запущен с опцией -c, то значение sys.argv[0] устанавливается в '-' и '-c' соответственно. Все, что указывается после -c command не воспринимается как опции интерпретатором Python, а передается в sys.argv для обработки инструкциями в command.

2.1.2. Интерактивный режим

Когда команды считываются с терминала, говорят, что интерпретатор находится в интерактивном режиме. В этом режиме для ввода последующих команд выводится первичное приглашение, обычно три знака больше ('>>> '); для продолжения ввода незаконченных инструкций выводится вторичное приглашение, по умолчанию — три точки ('... '). При запуске в интерактивном режиме интерпретатор выводит приветственное сообщение — номер версии и замечания об авторском праве — перед выводом первичного приглашения, например:

$ python
Python 2.0 (#8, Oct 16 2000, 17:27:58) [MSC 32 bit
(Intel)] on win32
Type "copyright", "credits" or "license" for more
information.
>>> 

Продолжение ввода незаконченных инструкций необходимо при вводе многострочных конструкций. В качестве примера, взгляните на инструкцию if:

>>> the_world_is_flat = 1
>>> # Если земля плоская, вывести предупреждние
... if the_world_is_flat:
...     print "Осторожно, не свалитесь!"
...
Осторожно, не свалитесь! 

2.2. Интерпретатор и его среда

2.2.1. Обработка ошибок

При возникновении ошибки интерпретатор печатает сообщение и остаток содержимого стека. В интерактивном режиме, после этого снова выдается первичное приглашение. Если программа читается из файла, интерпретатор печатает сообщение об ошибке, остаток содержимого стека и выходит с ненулевым кодом завершения. (Исключения, перехваченные ветвью except в инструкции try, не являются ошибками в данном контексте.) Некоторые ошибки — внутренние противоречия и некоторые случаи нехватки памяти — являются безусловно фатальными и приводят к выходу с ненулевым значением. Все сообщения об ошибках выводятся в стандартный поток ошибок; нормальные сообщения, возникающие в процессе выполнения команд, направляются в стандартный поток вывода.

Нажатие прерывающей комбинации клавиш (обычно Ctrl-C) во время выполнения генерирует исключение KeyboardInterrupt, которое может быть обработано с помощью инструкции try.

2.2.2. Исполняемые файлы

В операционных системах UNIX программу на языке Python можно сделать исполняемой непосредственно, поместив, например, строку

#!/usr/bin/env python 

(подразумевая, что путь к интерпретатору включен в переменную окружения PATH пользователя) и установив разрешение на исполнение. Символы '#!' должны быть первыми двумя символами файла. Заметьте, что символ '#' в языке Python используется для обозначения комментария.

2.2.3. Инициализация при запуске в интерактивном режиме

Если Вы используете Python интерактивно, часто удобно иметь стандартный набор команд, исполняемых при каждом запуске интерпретатора. Для этого нужно присвоить переменной окружения PYTHONSTARTUP имя файла, содержащего команды инициализации (аналогично '.profile' для UNIX shell).

Указанный файл читается только в интерактивном режиме и не используется, если команды читаются из файла, через конвейер или если терминал явно указан в качестве источника. Инициализационный файл исполняется в том же пространстве имен, что и интерактивные команды, то есть определенные в нем объекты и импортированные модули могут быть использованы далее без каких-либо ограничений. В этом файле Вы также можете переопределить первичное (sys.ps1) и вторичное (sys.ps2) приглашения.

Если Вы хотите считывать дополнительно инициализационный файл из текущего каталога, следует включить соответствующие инструкции в глобальный инициализационный файл, например:

if os.path.isfile('.pythonrc.py'):
    execfile('.pythonrc.py') 

Если необходимо использовать инициализационный файл в программе, Вы должны указать это явно:

import os
filename = os.environ.get('PYTHONSTARTUP')
if filename and os.path.isfile(filename):
    execfile(filename) 

3. Неформальное введение в Python

В следующих примерах ввод и вывод различаются наличием или отсутствием приглашения ('>>> ' или '... '): для воспроизведения примера Вам следует после появления приглашения набрать весь следующий за приглашением текст. Строки в примере, не начинающиеся с приглашения, выдаются самим интерпретатором. Обратите внимание, что наличие в строке только вторичного приглашения в примерах означает, что Вы должны ввести пустую строку — таким образом в интерактивном режиме обозначается конец многострочных команд.

Многие примеры в книге, даже если они вводятся в интерактивном режиме, снабжены комментариями. Комментарии в языке Python начинаются с символа '#' и продолжаются до конца строки. Комментарий может начинаться в начале строки или после кода, но не внутри строковых выражений. Символ '#' в строковом выражении является всего лишь символом '#'.

Несколько примеров:

# это первый комментарий
SPAM = 1    # а это второй
            # ... и, наконец, третий!
STRING = "# Это не комментарий." 

3.1. Использование интерпретатора Python в качестве калькулятора

Давайте испробуем несколько простых команд Python. Запустите интерпретатор и дождитесь появления первичного приглашения '>>> ' (это не должно занять много времени.)

3.1.1. Числа

Интерпретатор работает как простой калькулятор: Вы можете набрать выражение, и он выведет результат. Синтаксис выражений прост: операторы +, -, * и / работают, как и в большинстве других языков (например, в Pascal и C). Для группирования можно использовать скобки. Например:

>>> 2+2
4
>>> # Это комментарий
... 2+2
4
>>> 2+2 # Комментарий в той же строке, что и код
4
>>> (50-5*6)/4
5
>>> # При целочисленном делении результат округляется в
... # меньшую сторону:
... 7/3
2
>>> 7/-3
-3 

Подобно С, знак равенства ('=') используется для присваивания значения переменной. Присвоенное значение при этом не выводится:

>>> width = 20
>>> height = 5*9
>>> width * height
900 

Значение можно присвоить одновременно нескольким переменным:

>>> x = y = z = 0 # Переменным x, y и z присваивается 0
>>> x
0
>>> y
0
>>> z
0 

Имеется полная поддержка чисел с плавающей точкой. Операторы со смешанными типами операндов преобразуют целый операнд в число с плавающей точкой:

>>> 4 * 2.5 / 3.3
3.0303030303030303
>>> 7.0 / 2
3.5 

Также поддерживаются и комплексные числа. Мнимая часть записывается с суффиксом 'j' или 'J'. Комплексные числа записываются как '(real+imagj)' или могут быть созданы функцией 'complex(real, imag)'.

>>> 1j * 1J
(-1+0j)
>>> 1j * complex(0,1)
(-1+0j)
>>> 3+1j*3
(3+3j)
>>> (3+1j)*3
(9+3j)
>>> (1+2j)/(1+1j)
(1.5+0.5j) 

Комплексные числа представляются двумя числами с плавающей точкой — действительной и мнимой частью. Чтобы извлечь эти части из комплексного числа z, используйте z.real and z.imag.

>>> a=1.5+0.5j
>>> a.real
1.5
>>> a.imag
0.5 

Функции преобразования к целому числу и числу с плавающей точкой (int(), long() и float()) не работают для комплексных чисел — такое преобразование неоднозначно. Используйте abs(z) для получения абсолютного значения и z.real для получения вещественной части.

>>> a=1.5+0.5j
>>> float(a)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: can't convert complex to float; use e.g.
abs(z)
>>> a.real
1.5
>>> abs(a)
1.5811388300841898 

В интерактивном режиме последнее выведенное значение сохраняется в переменной _. Это позволяет использовать Python в качестве настольного калькулятора, например:

>>> tax = 17.5 / 100
>>> price = 3.50
>>> price * tax
0.61249999999999993
>>> price + _
4.1124999999999998
>>> print round(_, 2)
4.11 

Пользователь должен обращаться с ней как с переменной, доступной только для чтения. Не присваивайте ей значение явно — Вы создадите независимую локальную переменную с таким же именем, сделав встроенную переменную недоступной.

3.1.2. Строки

Кроме чисел, Python также может работать со строками (string), которые могут быть записаны различными путями. В настоящее время Python считает печатными 7-битное подмножество символов. Представление же остальных символов выводится с использованием управляющих последовательностей, что делает работу в интерактивном режиме, например, с русским языком неудобной. По этой причине текст в некоторых примерах данной книги оставлен без перевода. Они могут быть заключены в одинарные или двойные кавычки:

>>> 'spam eggs'
'spam eggs'
>>> 'doesn\'t'
"doesn't"
>>> "doesn't"
"doesn't"
>>> '"Yes," he said.'
'"Yes," he said.'
>>> "\"Yes,\" he said."
'"Yes," he said.'
>>> '"Isn\'t," she said.'
'"Isn\'t," she said.' 

Длинные строковые выражения могут быть разбиты различными способами на несколько строк. Символ новой строки может быть "спрятан" с помощью обратной косой черты ('\'), например:

hello = "Это длинное строковое выражение, содержащее\n\
несколько строк текста, как Вы бы это сделали в C.\n\
    Обратите внимание, что пробелы в\
 начале строки\nимеют значение.\n"
print hello 

Результат будет следующим:

Это длинное строковое выражение, содержащее
несколько строк текста, как Вы бы это сделали в C.
    Обратите внимание, что пробелы в начале строки
имеют значение. 

По-другому, текст может быть заключен в утроенные кавычки: """ или '''. Концы строк не нужно "прятать" при использовании утроенных кавычек, но они будут включены в текст.

print """
Usage: thingy [OPTIONS]
    -h             Display this usage message
    -H hostname    Hostname to connect to
""" 

выведет следующее:

Usage: thingy [OPTIONS]
    -h             Display this usage message
    -H hostname    Hostname to connect to 

Интерпретатор выводит результат строковых операций в том же виде, в котором они могли бы быть набраны: в кавычках, обходя кавычки и другие специальные символы с помощью управляющих последовательностей, начинающихся с обратной косой черты ('\'). Текст заключается в двойные кавычки, если он не содержит двойные кавычки, во всех остальных случаях, он выводится в одинарных кавычках. Инструкция print, описанная ниже, может быть использована для вывода текста без кавычек и специальных последовательностей.

Существует также "необрабатываемый" режим ввода строк, задаваемый с помощью буквы 'r' или 'R' перед кавычками: в этом случае символ обратной косой черты также может быть использован для маскировки символов одинарной и двойной кавычек, если им предшествует нечетное число символов обратной косой черты, однако сам символ обратной косой черты остается частью строки. Даже в таком режиме строка не может заканчиваться нечетным количеством символов обратной косой черты. Необрабатываемый режим наиболее полезен в тех случаях, когда необходимо вводить значительное количество символов обратной косой черты, например, в регулярных выражениях.

Строки можно объединить (склеить) с помощью оператора + и размножить оператором *:

>>> word = 'Help' + 'A'
>>> word
'HelpA'
>>> '<' + word*5 + '>'
'<HelpAHelpAHelpAHelpAHelpA>' 

Две строки, записанные друг за другом, автоматически объединяются. Первая строка в приведенном примере может быть также записана как 'word = 'Help' 'A''. Такой метод работает только для строк записанных непосредственно, но не для произвольных строковых выражений.

>>> 'str' 'ing'           # Правильно
'string'
>>> 'str'.strip() + 'ing' # Правильно
'string'
>>> 'str'.strip() 'ing'   # Ошибка
  File "<stdin>", line 1
    'str'.strip() 'ing'
                      ^
SyntaxError: invalid syntax 

Строка — последовательность символов с произвольным доступом, Вы можете получить любой символ строки по его индексу. Подобно C, первый символ имеет индекс 0. Нет отдельного типа для символа, символ — это просто строка единичной длины. Подобно Icon, подстрока может быть определена с помощью среза — двух индексов, разделенных двоеточием.

>>> word[4]
'A'
>>> word[0:2]
'He'
>>> word[2:4]
'lp' 

Строки в языке Python невозможно изменить. Попытка изменить символ в определенной позиции или подстроку вызовет ошибку:

>>> word[0] = 'x'
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
>>> word[:-1] = 'Splat'
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support slice assignment 

Индексы среза имеют полезные значения по умолчанию: опущенный первый индекс считается равным 0, опущенный второй индекс дает такой же результат, как если бы он был равен длине строки.

>>> word[:2]  # Первые два символа
'He'
>>> word[2:]  # Вся строка, кроме первых двух символов
'lpA' 

Полезный инвариант операции среза: s[:i] + s[i:] равно s.

>>> word[:2] + word[2:]
'HelpA'
>>> word[:3] + word[3:]
'HelpA' 

Срезы с вырожденными индексами обрабатываются изящно: слишком большой индекс обрабатывается, как если бы он был равен длине строки; если верхняя граница меньше нижней, то возвращается пустая строка.

>>> word[1:100]
'elpA'
>>> word[10:]
''
>>> word[2:1]
'' 

Индексы могут иметь отрицательные значения, для отсчета с конца:

>>> word[-1]  # Последний символ
'A'
>>> word[-2]  # Преджпоследний символ
'p'
>>> word[-2:] # Последние два символа
'pA'
>>> word[:-2] # Кроме последних двух символов
'Hel' 

Однако -0 обозначает то же самое, что и 0, то есть не будет отсчитываться с конца.

>>> word[-0]   # (так как -0 равен 0)
'H' 

Отрицательные индексы в срезах выходящие за пределы обрабатываются, как если бы они были равны нулю, но не пытайтесь использовать это для простых индексов (с одним элементом):

>>> word[-100:]
'HelpA'
>>> word[-10] # Ошибка
Traceback (innermost last):
  File "<stdin>", line 1
IndexError: string index out of range 

Лучший способ запомнить, как определяются индексы в срезе — считать их указывающими между символами, с номером 0 на левой границе первого символа. А правая граница последнего символа имеет индекс равный длине строки, например:

   H    e   l   p   A
 0    1   2   3   4   5
-5   -4  -3  -2  -1 

Первая строка чисел показывает позиции в строке, на которые указывают индексы от 0 до 5, вторая — соответствующие отрицательные индексы. Срез от i до j включает в себя все символы между краями, помеченными i и j, соответственно.

Для неотрицательных индексов длина подстроки равняется разности индексов, если они оба не выходят за пределы диапазона, например, длина подстроки word[1:3] равна 2.

Встроенная функция len() возвращает длину строки:

>>> s = 'supercalifragilisticexpialidocious'
>>> len(s)
34 

3.1.3. Строки Unicode

Начиная с версии 1.6, в языке Python доступен новый тип данных для хранения текста — строка Unicode. Его можно использовать для работы с текстом, содержащим одновременно буквы и символы нескольких языков, доступных в Unicode (см. http://www.unicode.org). Строки Unicode полностью интегрируется с обычными строками, автоматически производя, где это необходимо, преобразование.

Unicode имеет значительное преимущество — предоставляет возможность использовать все символы, используемые в современных и древних текстах. Ранее мы могли использовать только 256 символов из определенной кодовой страницы, что приводило к большим затруднениям, особенно при интернационализации (internationalization, обычно записывается как i18n — i + 18 символов + n) программного обеспечения. Unicode решает эту проблему, определяя одну кодовую страницу для всех символов.

Создаются строки Unicode настолько же просто, как и обычные строки:

>>> u'Hello World !'
u'Hello World !' 

Маленькая буква 'u' перед кавычками указывает, что предполагается создание строки Unicode. Если Вы хотите включить в строку специальные символы, используйте управляющие последовательности:

>>> u'Hello\u0020World !'
u'Hello World !' 

Управляющая последовательность \u0020 указывает, что необходимо вставить Unicode символ с порядковым номером в шестнадцатеричной системе исчисления 0x0020 (пробел).

Благодаря тому, что первые 256 символов Unicode те же, что и в стандартной кодировке Latin-1, ввод текста на большинстве языков, используемых в западных странах, сильно упрощается.

Как и для обычных строк, для строк Unicode существует "необрабатываемый" режим, задаваемый с помощью буквы 'r' или 'R' перед кавычками. Управляющими считаются только последовательности, которые применяются для обозначения символов Unicode, и только если используется нечетное количество символов обратной косой черты перед буквой 'u':

>>> ur'Hello\u0020World !'
u'Hello World !'
>>> ur'Hello\\u0020World !'
u'Hello\\\\u0020World !' 

Кроме описанного выше метода, Python предоставляет возможность создать строку Unicode на основе строки в известной кодировке. Встроенная функция unicode() может работать с Latin-1, ASCII, UTF-8, UTF-16, с русскими кодировками ISO-8859-5, KOI8-R, CP1251, CP866 и Mac-cyrillic, и многими другими. Python по умолчанию использует кодировку ASCII (ASCII является общей частью для подавляющего большинства кодировок. Вы можете изменить кодировку по умолчанию с помощью функции sys.set_string_encoding(). Однако лучше все же указывать ее явно.), например, при выводе на экран инструкцией print и записи в файл. Если у Вас есть данные в определенной кодировке, для получения строки Unicode используйте встроенную функцию unicode(), указав кодировку в качестве второго аргумента:

>>> s = unicode("Привет", "KOI8-R")
>>> s
u'\u041F\u0440\u0438\u0432\u0435\u0442' 

Если строка Unicode содержит символы с кодом больше 127, преобразование в ASCII не возможно:

>>> str(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeError: ASCII encoding error: ordinal not in
range(128) 

Метод encode() позволяет преобразовывать строки Unicode в обычные строки, содержащие текст в указанной кодировке:

>>> s.encode("KOI8-R")
'\360\322\311\327\305\324'
>>> s.encode("UTF-8")
'\320\237\321\200\320\270\320\262\320\265\321\202' 

3.1.4. Списки

В Python имеется несколько типов данных, используемых для группирования вместе нескольких значений. Самым гибким является список (list), который может быть записан в виде списка значений (элементов), разделенных запятыми, заключенного в квадратные скобки. Совсем не обязательно, чтобы элементы списка были одного типа.

>>> a = ['spam', 'eggs', 100, 1234]
>>> a
['spam', 'eggs', 100, 1234] 

Как и для строк, для списков нумерация индексов начинается с нуля. Для списка можно получить срез, объединить несколько списков и так далее:

>>> a[0]
'spam'
>>> a[3]
1234
>>> a[-2]
100
>>> a[1:-1]
['eggs', 100]
>>> a[:2] + ['bacon', 2*2]
['spam', 'eggs', 'bacon', 4]
>>> 3*a[:3] + ['Boe!']
['spam', 'eggs', 100, 'spam', 'eggs', 100, 'spam',
'eggs', 100, 'Boe!'] 

В отличие от строк, которые неизменяемы (immutable), существует возможность изменения отдельных элементов списка:

>>> a
['spam', 'eggs', 100, 1234]
>>> a[2] = a[2] + 23
>>> a
['spam', 'eggs', 123, 1234] 

Присваивание срезу также возможно, и это может привести к изменению размера списка:

>>> # Заменить несколько элементов:
... a[0:2] = [1, 12]
>>> a
[1, 12, 123, 1234]
>>> # удалить:
... a[0:2] = []
>>> a
[123, 1234]
>>> # Вставить:
... a[1:1] = ['bletch', 'xyzzy']
>>> a
[123, 'bletch', 'xyzzy', 1234]
>>> # Вставить копию самого себя в начало:
>>> a[:0] = a
>>> a
[123, 'bletch', 'xyzzy', 1234, 123, 'bletch', 'xyzzy',
1234] 

Встроенная функция len() также применима и к спискам:

>>> len(a)
8 

Списки могут быть вложенными (списки, содержащие другие списки), например:

>>> q = [2, 3]
>>> p = [1, q, 4]
>>> len(p)
3
>>> p[1]
[2, 3]
>>> p[1][0]
2
>>> p[1].append('xtra')
>>> p
[1, [2, 3, 'xtra'], 4]
>>> q
[2, 3, 'xtra'] 

Как Вы уже, наверное, догадались, метод append() добавляет элемент в конец списка. Заметьте, что p[1] и q на самом деле ссылаются на один и тот же объект!

3.2. Первые шаги к программированию

Конечно, мы можем использовать Python для более сложных задач, чем "два плюс два". Например, можно вывести начало ряда Фибоначчи:

>>> # Ряд Фибоначчи:
... # сумма двух предыдущих элементов определяет
... # следующий
... a, b = 0, 1
>>> while b < 10:
... print b
... a, b = b, a+b
...
1
1
2
3
5
8 

Этот пример знакомит с несколькими новыми особенностями.

  • Первая строка содержит множественное присваивание (multiple assignment): переменным a и b одновременно присваиваются новые значения 0 и 1. В последней строке оно используется снова, демонстрируя, что выражения в правой части вычисляются до того как будет осуществлено присваивание. Выражения в правой части вычисляются слева направо.
  • Цикл while выполняется пока условие (здесь: b < 10) является истинным. В Python, как и в С, любое ненулевое значение является истиной, ноль — ложь. В качестве условия может служить также строка, список — на самом деле любая последовательность. Последовательность с ненулевой длиной является истиной, пустая — ложью. Проверка, использованная в примере, является простым сравнением. Стандартные операторы сравнения записываются так же, как в С: <, >, ==, <= (меньше или равно), >= (больше или равно) и != (не равно).
  • Тело цикла записано с отступом: отступы используются в языке Python для записи группирующих инструкций. Интерпретатор (пока) не предоставляет разумных средств редактирования вводимых строк, поэтому Вам необходимо нажимать клавишу табуляции или пробела для каждой строки с отступом. На практике Вы будете готовить более сложный ввод для Python с помощью текстового редактора, большинство из которых позволяют делать сдвиг автоматически. Когда составная инструкция вводится интерактивно, за ним должна следовать пуста строка, как признак завершения (поскольку синтаксический анализатор не может угадать, когда Вы ввели последнюю строку). Заметим, что сдвиг строк внутри блока должен быть одинаковым.
  • Инструкция print выводит переданные ей значения. В отличие от простого вывода значений интерпретатором в интерактивном режиме (которым мы пользовались ранее в примерах использования интерпретатора в качестве калькулятора), инструкция print выводит строки без кавычек и между элементами вставляет пробел, так что Вы можете удобно форматировать вывод:
>>> i = 256*256
>>> print 'Значение переменной i равно', i
Значение переменной i равно 65536

Завершающая запятая позволяет после вывода значения вставлять пробел вместо перехода на новую строку:

>>> a, b = 0, 1
>>> while b < 1000:
... print b,
... a, b = b, a+b
...
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

Обратите внимание, что интерпретатор переходит на новую строку перед выводом следующего приглашения, даже если последняя выведенная строка не завершается переходом на новую строку.

4. Средства управления логикой

Кроме только что представленной инструкции while, в языке Python присутствуют другие привычные средства управления логикой программы с небольшими изменениями.

4.1. Инструкция if

Пожалуй, наиболее известной является инструкция if:

>>> x = int(raw_input("Введите, пожалуйста, число: "))
>>> if x < 0:
... x = 0
... print 'Отрицательное, меняем на ноль'
... elif x == 0:
... print 'Ноль'
... elif x == 1:
... print 'Один'
... else:
... print 'Больше'
... 

Ветвь elif может, как совсем отсутствовать, так и присутствовать несколько раз; наличие ветви else необязательно. Ключевое слово elif является короткой формой для else if и позволяет избежать чрезмерных отступов. Последовательность if ... elif ... elif ... эквивалентна инструкциям switch и case в других языках.

4.2. Инструкция for

Инструкция for в языке Python немного отличается от того, что используется в таких языках как C или Pascal. Вместо того, чтобы всегда перебирать числа арифметической прогрессии (как в Pascal), или предоставлять пользователю полную свободу выбора итератора и условия выхода из цикла (как в С), перебирает элементы произвольной последовательности (например, списка или строки) в порядке их следования (с формальной точки зрения это не совсем так: в языке Python под последовательностью всегда подразумевается последовательность с произвольным доступом; средства для работы с последовательностями (в том числе и инструкция for) требует возможности получить произвольный элемент по индексу.):

>>> # Измерение нескольких строк:
... a = ['кот', 'окно', 'выбросить']
>>> for x in a:
...     print x, len(x)
...
кот 3
окно 4
выбросить 9 

Небезопасно изменять в цикле итерируемую последовательность (такое возможно только для последовательностей, допускающих изменение, например, списков). Если Вы собираетесь вносить изменения в список, элементы которого перебираете, например, продублировать избранные элементы, следует перебирать элементы копии исходного списка. Запись в виде среза делает это особенно удобным:

>>> for x in a[:]: # сделать копию (среза) всего списка
... if len(x) > 4: a.insert(0, x)
...
>>> for x in a:
...     print x,
...
выбросить кот окно выбросить 

Используя средства функционального программирования (см. раздел 5.2), можно одновременно перебирать элементы нескольких последовательностей.

4.3. Функции range() и xrange()

Если Вам необходимо перебирать последовательность чисел, то пригодится встроенная функция range(). Она создает список, содержащий арифметическую прогрессию:

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Указанная верхняя граница никогда не входит в созданную последовательность. range(10) создает список из 10 значений, точно соответствующих допустимым индексам для элементов последовательности, имеющей длину 10. Можно указать другую нижнюю границу или другое приращение (шаг), в том числе и отрицательное:

>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70] 

Для того, чтобы перебрать индексы последовательности, используйте совместно range() и len():

>>> a = ['У', 'Марии', 'есть', 'маленькая', 'овечка']
>>> for i in range(len(a)):
...     print i, a[i]
...
0 У
1 Марии
2 есть
3 маленькая
4 овечка 

Дотошный читатель может заметить, что если нужно перебирать числа большого диапазона, создание списка будет неоправданно, а в некоторых случаях просто не хватит памяти:

>>> l=range(10000000)
Traceback (innermost last):
    File "<stdin>", line 1, in ?
MemoryError 

Действительно, если мы не собираемся изменять список, достаточно создать псевдосписок — объект, для которого мы можем получить значения "элементов", но не можем изменить их или порядок их следования. Для этих целей в языке Python предусмотрена функция xrange():

>>> xrange(5, 10)
(5, 6, 7, 8, 9)
>>> xrange(0, 10, 3)
(0, 3, 6, 9)
>>> xrange(-10, -100, -30)
(-10, -40, -70)
>>> a = ['У', 'Марии', 'есть', 'маленькая', 'овечка']
>>> for i in xrange(len(a)):
...     print i, a[i]
...
0 У
1 Марии
2 есть
3 маленькая
4 овечка 

4.4. Инструкции break и continue, ветвь else в циклах

Инструкция break, как и в C, выходит из самого внутреннего вложенного цикла for или while. Инструкция continue, также позаимствованная из C, продолжает выполнение цикла со следующей итерации.

Циклы могут иметь ветвь else, которая выполняется при "нормальном" выходе (исчерпание последовательности в цикле for, неудовлетворение условия в цикле while), без прерывания инструкцией break. Продемонстрируем ее использование на примере поиска простых чисел:

>>> for n in xrange(2, 10):
...     for x in xrange(2, n):
...         if n % x == 0:
...             print n, '=', x, '*', n/x
...             break
...     else:
...         print n, '- простое число'
...
2 - простое число
3 - простое число
4 = 2 * 2
5 - простое число
6 = 2 * 3
7 - простое число
8 = 2 * 4
9 = 3 * 3 

4.5. Инструкция pass

Инструкция pass ничего не делает и может быть использована там, где инструкция требуется синтаксисом языка, однако действий никаких выполнять не требуется:

>>> while 1:
...     pass # Ожидание прерывания от клавиатуры
... 

4.6. Определение функций

Мы можем создать функцию, которая будет выводить последовательность чисел Фибоначчи с произвольной верхней границей:

>>> def fib(n):
...     '''Выводит последовательность чисел Фибоначчи,
...     не превышающих n'''
...     a, b = 0, 1
...     while b < n:
...         print b,
...         a, b = b, a+b
...
>>> # Теперь вызовем только что определенную функцию
... fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 

Ключевое слово def представляет определение функции. После него должно следовать имя функции и, в скобках, список формальных параметров. Инструкции, образующие тело функции, записываются с отступом, начиная со следующей строки. Первой инструкцией тела функции может быть строка документации (см. раздел 4.7.5). Существуют средства для автоматического создания документации, а также средства, позволяющие пользователю просматривать строки документации в интерактивном режиме. Включение строк документации в код является хорошей традицией.

Выполнение функции вводит новую таблицу имен, используемую для локальных переменных. Точнее, все присваивания переменным в теле функции сохраняются в локальной таблице имен. При ссылке на переменную, ее поиск производится сначала в локальной таблице имен, затем в глобальной и, в последнюю очередь, в таблице встроенных имен. Так, глобальным переменным нельзя прямо присвоить значение в теле функции (не упомянув их перед этим в инструкции global), хотя на них можно ссылаться.

Аргументы функции в момент вызова помещаются в локальную таблицу имен вызываемой функции. Таким образом, аргументы передаются по значению (где значение всегда является ссылкой на объект, а не его значением) [На самом деле, правильнее было бы сказать, что аргументы передаются по ссылке на объект: если передается объект, допускающий изменения, то изменения (например, добавление элементов в список) будут видны в том месте, откуда функция была вызвана.]. В случае вызова функции другой функцией, также создается новая локальная таблица имен для этого вызова.

Определение функции вводит имя этой функции в текущую таблицу имен. Значение имени функции имеет тип, распознаваемый интерпретатором как определенная пользователем функция. Это значение может быть присвоено другому имени, которое затем также можно использовать в качестве функции (механизм переименования):

>>> fib
<function object at 10042ed0>
>>> f = fib
>>> f(100)
1 1 2 3 5 8 13 21 34 55 89 

Вы можете возразить, что fib является процедурой, а не функцией. В языке Python, как и в C, процедура — всего лишь функция, которая не возвращает никакого значения. Строго говоря, процедуры все же возвращают значение, которое, скорее, не представляет большого интереса. Это значение — None (встроенное имя). Значение None обычно не выводится интерпретатором, но Вы можете его увидеть, если действительно хотите этого:

>>> print fib(0)
None 

Довольно просто написать функцию, которая возвращает список чисел ряда Фибоначчи, вместо того, чтобы выводить их:

>>> def fib2(n):
...     '''Возвращает список, содержащий числа ряда
...     Фибоначчи, не превышающие n'''
...     result = []
...     a, b = 0, 1
...     while b < n:
...         result.append(b)
...         a, b = b, a+b
...     return result
...
>>> # Вызываем только что определенную функцию
... f100 = fib2(100)
>>> # Выводим результат
... f100
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] 

Этот пример, как обычно, демонстрирует несколько новых особенностей языка Python:

  • Инструкция return выходит из функции и возвращает значение. Без аргументов return используется для выхода из середины процедуры (иначе выход происходит при достижении конца процедуры), в этом случае возвращается значение None.
  • Инструкция result.append(b) вызывает метод объекта-списка result. Метод—это функция, "принадлежащая" объекту, вызывается как obj.methodname, где obj — объект (или выражение его определяющее) и methodname — имя метода, определенного для данного типа объектов. Различные типы имеют различные наборы методов. Методы различных типов могут иметь одинаковые имена, не приводя к неопределенности. (Вы можете определить собственные типы объектов — классы — и методы работы с ними.) Метод append(), используемый в примере, определен для объектов-списков. Он добавляет элемент в конец списка. В приведенном примере его использование эквивалентно записи 'result = result + [b]', но более эффективно.

4.7. Дополнительные возможности в определении функций

В языке Python можно определить функцию с переменным числом аргументов. Для этого существуют три способа, которые можно комбинировать.

4.7.1. Значения аргументов по умолчанию

Наиболее полезный способ — установить значения по умолчанию для одного или нескольких аргументов. Таким образом, получается функция, которую можно вызывать с меньшим количеством аргументов, чем в определении, например:

def ask_ok(prompt, retries=4,
           complaint='Да или нет, пожалуйста!'):
    while 1:
        ok = raw_input(prompt)
        if ok in ('д', 'да'): return 1
        if ok in ('н', 'не', 'нет'): return 0
        retries = retries - 1
        if retries < 0:
            raise IOError(
                  'Пользователь отказывается отвечать')
        print complaint 

Эта функция может быть вызвана так: 'ask_ok('Вы действительно хотите выйти?')', или так: 'ask_ok('Перезаписать файл?', 2)'.

Значения по умолчанию вычисляются в месте определения функции в области видимости определения, так что

i = 5
def f(arg = i): print arg
i = 6
f() 

выведет 5.

Важное замечание: значение по умолчанию вычисляется только один раз. Это отражается в том случае, когда аргумент со значением по умолчанию является объектом, допускающим изменения, таким как список или словарь. Например, следующая функция накапливает передаваемые аргументы (то есть переменная l является статической) [Такое поведение является, скорее, нежелательной особенностью языка, использование которой может привести к трудно отлавливаемым ошибкам. В качестве статических переменных рекомендуется использовать глобальные переменные модуля.]:

def f(a, l = []):
    l.append(a)
    return l
print f(1)
print f(2)
print f(3) 

Результат выполнения будет следующий:

[1]
[1, 2]
[1, 2, 3] 

Если Вы не хотите, чтобы аргумент по умолчанию совместно использовался при последующих вызовах, можете немного изменить функцию:

def f(a, l = None):
    if l is None:
        l = []
    l.append(a)
    return l 

4.7.2. Произвольный набор аргументов

Наконец, не так часто используемый вариант — определить функцию таким образом, чтобы ее можно было вызвать с произвольным числом аргументов. В этом случае аргументы будут переданы в виде кортежа (Кортеж — своего рода последовательность объектов, в которую Вы не можете вносить изменения.). Перед переменным числом аргументов может присутствовать произвольное число обычных аргументов:

def fprintf(file, format, *args):
    file.write(format % args) 

4.7.3. Именованные аргументы

Функция может быть вызвана с использованием именованных аргументов (keyword arguments) в виде 'keyword = value'. Например, функция ask_ok() (раздел 4.7.1) может быть вызвана следующими способами:

ask_ok('Вы действительно хотите выйти?')
ask_ok(retries = 2, prompt = 'Перезаписать файл?')
ask_ok('Продолжим?', complaint =
       'А теперь то же самое, только по-русски!')
ask_ok('Удалить все файлы из корневого раздела?', 100,
       'А может все-таки удалить?') 

Однако следующие вызовы ошибочны:

# отсутствует обязательный аргумент
ask_ok()
# неименованный аргумент следует за именованным
ask_ok(prompt = 'Перезаписать файл?', 2)
# два значения для одного аргумента
ask_ok('Продолжим?', prompt = 'Да/нет')
# неизвестное имя аргумента
ask_ok(actor = 'Никулин') 

В общем, в списке аргументов именованные аргументы должны следовать после позиционных. Не имеет значения, имеет неименованный аргумент значение по умолчанию или нет. Никакой аргумент не может быть передан более одного раза:

>>> def function(a):
...     pass
...
>>> function(0, a=0)
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: keyword parameter redefined 

Если в определении функции присутствует формальный параметр в виде **name, то его значением становится словарь, содержащий все именованные аргументы, чьи имена не соответствуют формальным параметрам. Такой способ можно комбинировать с формой *name, позволяющей получить кортеж (tuple), содержащий позиционные аргументы, не входящие в список формальных параметров. (Запись **name должна следовать после записи *name.) Например, определим функцию:

def example(formal, *arguments, **keywords):
    print "Количество пользователей:", formal
    print '-'*40
    for arg in arguments: print arg
    print '-'*40
    for kw in keywords.keys():
        print kw, ':', keywords[kw] 

Ее можно вызвать следующим образом:

example(1000000,
    'Это очень много',
    'Это действительно очень много',
    language = 'Python',
    author = 'Guido van Rossum') 

Вывод будет таким:

Количество пользователей: 1000000
----------------------------------------
Это очень много
Это действительно очень много
----------------------------------------
language: Python
author: Guido van Rossum 

4.7.4. Короткая форма

Python содержит несколько популярных особенностей, характерных для языков функционального программирования. С помощью ключевого слова lambda Вы можете создать простую функцию без имени. Например, функция, возвращающая сумму двух своих аргументов, может быть записана как 'lambda a, b: a+b'. Короткая форма может быть использована везде, где требуется объект-функция. Ее синтаксис ограничен одним выражением. Как и в обычных определениях функций, при записи в короткой форме Вы не можете ссылаться на переменные, находящиеся в области видимости, которая содержит определение этой функции. Однако это ограничение можно обойти, используя значения аргументов по умолчанию:

>>> def make_incrementor(n):
...     return lambda x, incr=n: x+incr
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43 

4.7.5. Строки документации

Строки документации обычно состоят из одной строки с кратким изложением назначения (или использования) объекта и более подробного описания, разделенных пустой строкой. Первая строка часто используется интегрированными средами разработки (например, IDLE) в качестве подсказки при использовании объекта. Большинство встроенных объектов и объектов, определенных в стандартной библиотеке, снабжены строками документации — используйте их в качестве примеров оформления. Например:

>>> print map.__doc__
map(function, sequence[, sequence, ...]) -> list

Return a list of the results of applying the function
to the items of the argument sequence(s). If more than
one sequence is given, the function is called with an
argument list consisting of the corresponding item of
each sequence, substituting None for missing values
when not all sequences have the same length. If the
function is None, return a list of the items of the
sequence (or a list of tuples if more than one
sequence). 

Интерпретатор языка Python не убирает отступы в многострочных строковых литеральных выражениях. Поэтому средства, обрабатывающие строки документации, должны это делать сами. Для определения уровня отступа всей строки документации используется первая непустая строка после первой строки (первая строка обычно записывается без отступа). Именно на столько следует уменьшить уровень отступа всей документации при выводе.

4.7.6. Вызов функций

Помимо описанного (func(arg ... )), язык Python предоставляет еще несколько способов вызова функций. Начиная с версии 1.6, Вы можете указать кортеж позиционных и словарь именованных аргументов, например:

args = ('Это очень много',
        'Это действительно очень много')
kwds = {'language': 'Python',
        'author': ?Guido van Rossum?)
example(1000000, *args, **kwds) 

Такой же эффект можно получить используя встроенную функцию apply():

apply(example, (1000000,)+args, kwds) 

С помощью средств функционального программирования Вы можете применить функцию к элементам последовательности (см. раздел 5.2).

5. Структуры данных

В этой главе описываются более детально ранее изученные возможности, а также некоторые новые особенности.

5.1. Подробнее о списках

Ранее мы уже говорили, что метод append() позволяет добавить элемент в конец списка:

>>> a = [66.6, 333, 333, 1, 1234.5]
>>> a.append(333)
>>> a
[66.6, 333, 333, 1, 1234.5, 333] 

Однако иногда необходимо вставить элемент в начало или другую позицию списка. Это позволяет сделать метод insert — Вы указываете индекс элемента, перед которым новый элемент будет добавлен:

>>> a.insert(2, -1)
[66.6, 333, -1, 333, 1, 1234.5, 333] 

Кроме того, для списка определены методы, позволяющие анализировать его содержимое: найти, в каком положении находится (первый) элемент с определенным значением (метод index), или подсчитать количество таких элементов (метод count):

>>> a.index(333)
1
>>> print a.count(333), a.count(66.6), a.count('x')
3 1 0 

Метод remove() позволяет удалить из списка (первый) элемент, имеющий заданное значение:

>>> a.remove(333)
>>> a
[66.6, -1, 333, 1, 1234.5, 333] 

Элементы списка можно отсортировать (метод sort()) и изменить порядок следования элементов на противоположный (метод reverse()):

>>> a.sort() # сортируем по возрастанию
>>> a
[-1, 1, 66.6, 333, 333, 1234.5]
>>> a.reverse()
>>> a
[1234.5, 333, 333, 66.6, 1, -1] 

Более подробно эти и другие операции над списками описаны в разделе 11.2.6. Приведем лишь несколько примеров, показывающих насколько широка область их применения.

5.1.1. Стеки

Методы, определенные для списков, позволяют использовать список в качестве стека, где последний добавленный элемент извлекается первым (LIFO, "last-in, first-out"). Для добавления элемента в стек, используйте метод append(), для извлечения элемента с вершины стека — метод pop() без явного указания индекса:

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4] 

5.1.2. Очереди

Еще одно применение списков — очереди, где элементы извлекаются в том же порядке, в котором они добавлялись (FIFO, "first-in, first-out"). Для добавления элемента в стек, используйте метод append(), для извлечения "очередного" элемента — метод pop() с индексом 0:

>>> queue = ["Eric", "John", "Michael"]
>>> queue.append("Terry") # Terry добавлен в очередь
>>> queue.append("Graham") # Graham добавлен в очередь
>>> queue.pop(0)
'Eric'
>>> queue.pop(0)
'John'
>>> queue
['Michael', 'Terry', 'Graham'] 

5.2. Средства функционального программирования

Существуют четыре встроенные функции, которые могут быть полезны при работе с последовательностями: filter(), map(), zip() и reduce().

filter(function, sequence) возвращает последовательность (по возможности того же типа, что и sequence), состоящую из тех элементов последовательности sequence, для которых function(item) является истиной. Например, выделим простые числа из списка:

>>> def f(x):
...     for y in xrange(2, x):
...         if x%y==0: return 0
...     return 1
...
>>> filter(f, xrange(2, 40))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] 

map(function, sequence [. . . ]) возвращает список значений, полученных применением функции function к элементам одной или нескольких последовательностей. Например, создадим список кубов натуральных чисел:

>>> def cube(x): return x*x*x
...
>>> map(cube, xrange(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] 

Функция function должна воспринимать столько аргументов, сколько последовательностей передается. Она вызывается с соответствующими элементами из каждой последовательности или None, если какая-нибудь последовательность оказалась короче других. Если function равно None, то используется функция, возвращающая свои аргументы.

Учитывая эти две особенности, мы видим, что 'map(None, list1, list2)' является удобным способом превращения пары списков в список пар:

>>> seq = xrange(8)
>>> def square(x): return x*x
...
>>> map(None, seq, map(square, seq))
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25),
(6, 36), (7, 49)] 

Таким образом Вы можете перебирать элементы одновременно несколько последовательностей одинаковой длины:

>>> seq1 = ['cat', 'mouse', 'bird']
>>> seq2 = ['кот', 'мышь', 'птица']
>>> for x, y in map(None, seq1, seq2):
...     print x, y
...
cat кот
mouse мышь
bird птица 

Как мы уже говорили, если какая-либо последовательность оказывается короче других, функция map() дополняет ее элементами, равными None. Такое поведение не всегда желательно. Поэтому в версии 2.0 была добавлена функция zip(). zip(sequence [...]) возвращает список кортежей, каждый из которых состоит из соответствующих элементов аргументов-последовательностей. При этом длина полученной последовательности будет равна длине самой короткой последовательности среди аргументов. Например:

>>> a = (1, 2, 3, 4)
>>> b = (5, 6, 7, 8)
>>> c = (9, 10, 11)
>>> d = (12, 13)
>>> zip(a, b)
[(1, 5), (2, 6), (3, 7), (4, 8)]
>>> zip(a, d)
[(1, 12), (2, 13)]
>>> zip(a, b, c, d)
[(1, 5, 9, 12), (2, 6, 10, 13)] 

Если последовательности имеют одинаковую длину, поведение функции zip() полностью аналогично поведению map() с None в качестве первого аргумента. Кроме того, в этом случае действие функций zip() и map() обратимо:

>>> a = (1, 2, 3)
>>> b = (4, 5, 6)
>>> x = zip(a, b)
>>> y = zip(*x) # или apply(zip, x)
>>> z = zip(*y) # или apply(zip, y)
>>> x
[(1, 4), (2, 5), (3, 6)]
>>> y
[(1, 2, 3), (4, 5, 6)]
>>> z
[(1, 4), (2, 5), (3, 6)]
>>> x == z
1 

reduce(function, sequence [, initial]) возвращает значение, полученное путем последовательного применения бинарной функции function сначала к первым двум элементам последовательности sequence, затем к результату и следующему элементу и т. д. Например, вычислим сумму арифметической последовательности:

>>> def add(x, y): return x+y
...
>>> reduce(add, xrange(1, 11))
55 

Если последовательность содержит единственный элемент, возвращается его значение, если же последовательность пуста, то генерируется исключение TypeError.

В качестве третьего аргумента можно указать начальное значение. В этом случае оно возвращается для пустой последовательности, и функция сначала применяется к начальному значению и первому элементу последовательности, затем к результату и следующему элементу и т. д.:

>>> def sum(seq):
...     def add(x,y): return x+y
...     return reduce(add, seq, 0)
...
>>> sum(xrange(1, 11))
55
>>> sum([])
0 

5.3. Дополнительные возможности при конструировании списков

Начиная с версии 2.0, языка Python, существуют дополнительные возможности конструирования списков, не прибегая к средствам функционального программирования. Такие определения списков записываются с использованием в квадратных скобках выражения и следующих за ним блоков for:

>>> freshfruit = [' banana',
...               ' loganberry ',
...               'passion fruit ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> vec = [2, 4, 6]
>>> [3*x for x in vec]
[6, 12, 18]
>>> [{x: x**2} for x in vec]
[{2: 4}, {4: 16}, {6: 36}]
>>> [[x,x**2] for x in vec]
[[2, 4], [4, 16], [6, 36]] 

Элементы можно фильтровать, указав условие, записываемое с помощью ключевого слова if:

>>> [3*x for x in vec if x > 3]
[12, 18]
>>> [3*x for x in vec if x < 2]
[] 

Выражение, дающее кортеж, обязательно должно быть записано в скобках:

>>> [x, x**2 for x in vec]
  File "<stdin>", line 1
    [x, x**2 for x in vec]
               ^
SyntaxError: invalid syntax
>>> [(x, x**2) for x in vec]
[(2, 4), (4, 16), (6, 36)] 

Если в конструкторе указано несколько блоков for, элементы второй последовательности перебираются для каждого элемента первой и т. д., то есть перебираются все комбинации:

>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3] 

5.4. Инструкция del

Существует способ удалить не только элемент по его значению, но и элемент с определенным индексом, элементы с индексами в определенном диапазоне (ранее мы производили эту операцию путем присваивания пустого списка срезу): инструкция del:

>>> a
[-1, 1, 66.6, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.6, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.6, 1234.5] 

Инструкция del может быть также использована для удаления переменных:

>>> del a 

После этого ссылка на переменную a будет генерировать исключение NameError, пока ей не будет присвоено другое значение. Позже мы расскажем о других применениях инструкции del.

5.5. Кортежи

Списки и строки имеют много общего, например, для них можно получить элемент по индексу или применить операцию среза. Это два примера последовательностей. Еще одним встроенным типом последовательностей является кортеж (tuple).

Кортеж состоит из набора значений, разделенных запятой, например:

>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Кортежи могут быть вложенными:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5)) 

Как Вы заметили, при выводе кортежи всегда заключаются в круглые скобки, для того, чтобы вложенные кортежи воспринимались корректно. Вводить их можно как в скобках, так и без них, хотя в некоторых случаях скобки необходимы (если кортеж является частью более сложного выражения).

Кортежи имеют множество применений: пара координат (x, y), запись в базе данных и т. д. Кортежи, как и строки, не допускают изменений: нельзя присвоить значение элементу кортежа (тем не менее, Вы можете имитировать такую операцию с помощью срезов и последующего объединения).

Для того, чтобы сконструировать пустые или содержащие один элемент кортежи, приходится идти на синтаксические ухищрения. Пустой кортеж создается с помощью пустой пары скобок, кортеж с одним элементом — с помощью значения и следующей за ним запятой (не достаточно заключить значение в скобки). Несколько неприятно, но эффективно. Например:

>>> empty = ()
>>> # обратите внимание на завершающую запятую
... singleton = 'hello',
>>> len(empty)
0
>>> empty
()
>>> len(singleton)
1
>>> singleton
('hello',) 

Инструкция t = 12345, 54321, 'hello!' — пример упаковки в кортеж: значения 12345, 54321 и 'hello!' упаковываются вместе в один кортеж. Также возможна обратная операция — распаковки кортежа:

>>> x, y, z = t 

Распаковка кортежа требует, чтобы слева стояло столько переменных, сколько элементов в кортеже. Следует заметить, что множественное присваивание является всего лишь комбинацией упаковки и распаковки кортежа. Иногда может оказаться полезной распаковка списка:

>>> a = ['spam', 'eggs', 100, 1234]
>>> a1, a2, a3, a4 = a 

Как мы уже говорили, кортежи, как и строки, не допускают изменений. Однако, в отличие от строк, кортежи могут содержать объекты, которые можно изменить с помощью методов:

>>> t = 1, ['foo', 'bar']
>>> t
(1, ['foo', 'bar'])
>>> t[1] = [] # Ошибка
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
>>> t[1].append('baz') # Правильно
>>> t
(1, ['foo', 'bar', 'baz']) 

5.6. Словари

Еще один встроенный в Python тип данных — словарь (dictionary) — часто называют ассоциативным массивом. В отличие от последовательностей, индексируемых диапазоном чисел, доступ к элементам словаря производится по ключу, который может быть любого типа, не допускающего изменений [На самом деле, в качестве ключа может служить любой объект (в том числе допускающий изменения), для которого определена функция hash(). Правило относится, скорее, к стандартным типам данных языка.] — строки и числа всегда могут быть ключами. Кортежи могут использоваться в качестве ключа, если они содержат строки, числа и кортежи, удовлетворяющие этому правилу. Нельзя использовать списки, так как их можно изменить (не создавая нового объекта-списка) с помощью, например, метода append().

Лучше всего представлять словарь как неупорядоченное множество пар ключ: значение, с требованием уникальности ключей в пределах одного словаря. Пара фигурных скобок {} создает пустой словарь. Помещая список пар key: value, разделенных запятыми, в фигурные скобки, Вы задаете начальное содержимое словаря. В таком же виде записывается словарь при выводе.

Основные операции над словарем — сохранение с заданным ключом и извлечение по нему значения. Также можно удалить пару key: value с помощью инструкции del. Если Вы сохраняете значение с ключом, который уже используется, то старое значение забывается. При попытке извлечь значение с несуществующим ключом, генерируется исключение KeyError.

Метод keys() для словаря возвращает список всех используемых ключей в произвольном порядке (если Вы хотите, чтобы список был отсортирован, просто примените к нему метод sort()). Чтобы определить, используется ли определенный ключ — используйте метод has_key().

Приведем простой пример использования словаря в качестве телефонного справочника:

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
>>> tel.keys()
['guido', 'irv', 'jack']
>>> tel.has_key('guido')
1 

5.7. Подробнее об условиях

Помимо описанных ранее операторов сравнения, существует еще несколько условных операторов.

Операторы in и not in проверяют, есть указанное значение в последовательности. Операторы is и is not определяют, ссылаются ли две переменные на один и тот же объект. Все приведенные здесь операторы имеют одинаковый приоритет, который ниже, чем у арифметических операторов.

Логические выражения могут быть сцеплены: например, 'a < b == c' проверяет, меньше ли a чем b и равны ли b и c.

Логические выражения можно группировать с помощью логических операторов and и or, а также результат можно инвертировать оператором not. Все логические операторы имеют меньший приоритет, чем операторы сравнения. Среди логических операторов, not имеет наибольший приоритет и or — наименьший. Таким образом, 'A or not B and C' эквивалентно 'A or ((not B) or C)'. Безусловно, можно использовать скобки для определения порядка вычисления.

Аргументы логических операторов and и or вычисляются справа налево до тех пор, пока результат не будет определен. Например, если выражения A и C истинны, но B ложно, в 'A and B and C' выражение C вычисляться не будет. Вообще говоря, возвращаемое значение операторов and и or является не логическим, а равно значению последнего вычисленного аргумента.

Можно присвоить результат сравнения или логического выражения переменной:

>>> string1, string2, string3 = \
...     '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim' 

Обратите внимание, что, в отличие от C, присваивание не может находиться внутри выражения. Такое ограничение позволяет избежать ошибок, типичных для программ на C: использование = вместо ==.

5.8. Сравнение последовательностей

Объекты-последовательности можно сравнивать с другими объектами того же типа. Сравнение производится лексикографически: сначала сравниваются первые элементы последовательностей, и, если они отличаются, то результат их сравнения определяет результат; если они равны, сравниваются следующие элементы и т. д., пока не будет определен результат или одна из последовательностей не будет исчерпана. Если два элемента сами являются последовательностями одного типа, то лексикографическое сравнение выполняется рекурсивно. Если все элементы двух последовательностей в результате сравнения оказываются равными, то последовательности считаются равными. Если одна из последовательностей является началом другой, то меньшей считается последовательность с меньшим количеством элементов. Лексикографический порядок строк определяется порядком следования ASCII символов. Приведем несколько примеров сравнения последовательностей одинакового типа:

(1, 2, 3)              < (1, 2, 4)
[1, 2, 3]              < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4)           < (1, 2, 4)
(1, 2)                 < (1, 2, -1)
(1, 2, 3)              == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4) 

Заметим, что сравнение объектов различного типа допустимо. Результат будет вполне определенным, однако не следует на него полагаться — правила сравнения объектов различного типа могут измениться в следующих версиях языка. Числа сравниваются в соответствии с их значениями, так 0 равен 0.0, и т. д.

6. Модули

Когда Вы выходите из интерпретатора и заходите снова, все сделанные Вами определения (функции, переменные) безвозвратно теряются. Поэтому, если Вы хотите набрать сколько-нибудь длинную программу, для приготовления ввода лучше воспользоваться текстовым редактором. По мере возрастания программы, Вы, возможно, захотите разбить ее на несколько файлов, чтобы их было легче поддерживать. Вы можете захотеть использовать написанные Вами полезные функции в нескольких программах, не копируя их определения в каждую из программ.

Python позволяет поместить определения в файл и использовать их в программах и интерактивном режиме. Такой файл называется модулем. Определения из модуля могут быть импортированы в другие модули и в главный модуль (коллекция переменных, доступная в программе и в интерактивном режиме).

6.1. Создание и использование модулей

Модуль — файл, содержащий определения и другие инструкции языка Python. Имя файла образуется путем добавления к имени модуля суффикса (расширения) '.py'. В пределах модуля, его имя доступно в глобальной переменной __name__. Например, используя Ваш любимый текстовый редактор, создайте в текущем каталоге файл с именем 'fibo.py' следующего содержания:

'''\
Генерация и вывод чисел Фибоначчи
'''
def fib(n):
    '''Выводит последовательность чисел Фибоначчи,
    не превышающих n'''
    a, b = 0, 1
    while b < n:
        print b,
        a, b = b, a+b

def fib2(n):
    '''Возвращает список, содержащий числа ряда
    Фибоначчи, не превышающие n'''
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result 

Теперь запустите интерпретатор и импортируйте только что созданный модуль:

>>> import fibo 

Эта инструкция не вводит имена функций, определенных в fibo прямо в текущее пространство имен, она только вводит имя модуля fibo. Используя имя модуля, Вы можете получить доступ к функциям:

>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo' 

Если Вы собираетесь использовать функцию часто, можете присвоить ее локальной переменной:

>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 

Модуль может содержать любые инструкции, предназначенные для его инициализации, а не только определения функций. Они выполняются, только когда модуль импортируется первый раз.

Каждый модуль имеет собственное пространство имен, являющееся глобальной областью видимости для всех определенных в нем функций. Таким образом, автор модуля может использовать глобальные переменные, не беспокоясь о возможных конфликтах с глобальными переменными пользователя. С другой стороны, если Вы знаете, что делаете, Вы можете получить доступ к глобальным переменным модуля точно так же, как и к его функциям (на самом деле, функции тоже являются переменными), modname.itemname.

Модули могут импортировать другие модули. Обычно инструкцию import располагают в начале модуля или программы. Имена импортируемых модулей помещаются в текущее пространство имен импортирующего модуля или программы.

Другой вариант инструкции import импортирует имена из модуля непосредственно в текущее пространство имен:

>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 

В этом случае имя модуля не будет представлено в текущей области видимости (в приведенном примере, имя fibo не определено).

Еще один вариант инструкции import позволяет импортировать все имена, определенные в модуле, кроме имен, начинающихся с символа подчеркивания ('_'):

>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 

Часто возникает необходимость импортировать модуль или объект модуля, используя для него локальное имя, отличное от исходного. Например, следующий код позволяет заменить имя string на _string (которое не будет импортироваться инструкцией 'from my_module import *') при написании модуля:

import string
_string = string
del string 

Еще один пример показывает, как можно избежать конфликта имени, определенного в модуле, со встроенным именем:

import anydbm
dbopen = anydbm.open 

Начиная с версии 2.0, подобные операции можно произвести гораздо проще (и безопаснее) благодаря расширению синтаксиса инструкции import:

import string as _string
from anydbm import open as dbopen 

Следует заметить, что as не является зарезервированным словом, и Вы можете попрежнему определять переменные с таким именем.

Если имя модуля, который необходимо импортировать, становится известным только во время выполнения программы, Вы можете воспользоваться инструкцией exec ('exec 'import ' + module_name') или встроенной функцией __import__() (см. раздел 12).

6.2. Поиск модулей

Когда импортируется модуль, например spam, интерпретатор ищет файл с именем 'spam.py' в текущем каталоге, затем в каталогах, указанных в переменной окружения PYTHONPATH, затем в зависящих от платформы путях по умолчанию.

Каталоги, в которых осуществляется поиск, хранятся в переменной sys.path. Таким образом, программы на языке Python могут изменять пути поиска модулей во время их выполнения.

6.3. "Компилированные" файлы

Для ускорения запуска программ, использующих большое количество модулей, если уже существует файл с именем 'spam.pyc' в том же каталоге, где найден 'spam.py', считается, что он содержит "байт-компилированный" модуль spam. Если такого файла нет, то он создается, и время последнего изменения 'spam.py' записывается в созданном 'spam.pyc' (при последующем использовании, '.pyc'-файл игнорируется, если исходный '.py'-файл был изменен).

Обычно Вам не надо ничего делать для создания 'spam.pyc'. Как только 'spam.py' успешно откомпилирован, интерпретатор пытается записать компилированную версию в 'spam.pyc'. Если интерпретатору по каким-либо причинам это не удается (например, недостаточно пользовательских полномочий), ошибки не возникает. Если же файл записан не полностью, далее он распознается как неработоспособный и игнорируется. Содержимое байт-компилированных файлов является платформно-независимым (но может быть разным для разных версий интерпретатора), так что каталог с модулями может совместно использоваться машинами с разными архитектурами.

Несколько тонкостей для опытных пользователей:

  • Если интерпретатор вызывается с опцией -O или переменная окружения PYTHONOPTIMIZE имеет непустое значение, интерпретатор генерирует оптимизированный байт-код и сохраняет его в '.pyo'-файлах. В настоящий момент оптимизация дает не слишком много: при этом удаляются инструкции assert, игнорируются инструкции 'if __debug__: ... ', не сохраняется информация о нумерации строк в исходных '.py'-файлах. В этом случае оптимизируются все используемые модули, '.pyc'-файлы игнорируются.
  • Опция -OO приводит к тому, что интерпретатор выполняет оптимизацию которая, в некоторых (редких) случаях, может привести к сбоям в работе программ. В настоящий момент, помимо действий, выполняемых с опцией -O, удаляются строки документации, давая более компактные '.pyo'-файлы. Так как некоторые программы могут рассчитывать на наличие строк документации, используйте эту опцию с осторожностью.
  • Для программы, запускаемой из командной строки, байт-код никогда не записывается в '.pyc'- или '.pyo'-файл. Поэтому, если Вы хотите уменьшить время, требующееся для загрузки, поместите большую часть кода в модуль, оставив в программе лишь загрузочную часть, которая импортирует этот модуль.
  • Возможно использование модуля (или запуск программы) при наличии '.pyc'-файла (или '.pyo'-файла, если используется одна из опций -O или -OO), даже если отсутствует '.py'-файл. Таким образом, Вы можете распространять библиотеки и программы в виде, из которого относительно сложно извлечь информацию об используемых алгоритмах.
  • Модуль compileall позволяет создать '.pyc'- (или '.pyo'-) файлы для всех модулей в каталоге. Это может быть особенно полезно, если вы хотите ограничить доступ к каталогу, в котором находится библиотека. Заметим, что интерпретатор не будет использовать '.pyc'-файлы, если он запущен с включенной оптимизацией, и '.pyo'-файлы, если оптимизация выключена (если же отсутствует '.py'-файл, модуль окажется недоступным).

6.4. Стандартные модули

Python распространяется с библиотекой стандартных модулей, которой посвящена третья часть книги. Часть модулей встроена в интерпретатор, обеспечивая доступ к операциям, которые не входят в ядро языка, но, тем не менее, встроены либо из соображений эффективности, либо для обеспечения доступа к примитивам операционной системы.Набор таких модулей зависит от конфигурации, так, например, модуль amoeba присутствует только в системах, которые поддерживают примитивы Amoeba. Один модуль заслуживает особого внимания: sys, который присутствует всегда. Переменные sys.ps1 и sys.ps2 при работе в интерактивном режиме определяют строки, используемые для первичного и вторичного приглашения:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print 'Привет!'
Привет!
C> 

Переменная sys.path содержит список строк с именами каталогов, в которых происходит поиск модулей. Она инициализируется из значения переменной окружения PYTHONPATH и встроенного значения по умолчанию. Вы можете изменить ее значение, используя стандартные операции со списками:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python') 

6.5. Функция dir()

Для выяснения имен, определенных в модуле, можно использовать встроенную функцию dir(). Она возвращает отсортированный список строк:

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__name__', 'argv', 'builtin_module_names',
'copyright', 'exit', 'maxint', 'modules', 'path',
'ps1', 'ps2', 'setprofile', 'settrace', 'stderr',
'stdin', 'stdout', 'version'] 

Без аргументов, dir() возвращает список имен, определенных в текущей области видимости:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo, sys
>>> fib = fibo.fib
>>> dir()
['__name__', 'a', 'fib', 'fibo', 'sys'] 

Обратите внимание, что перечисляются имена объектов всех типов: переменные, модули, функции и т. д.

Список, возвращаемый функцией dir() не содержит имена встроенных функций и переменных — они определены в стандартном модуле __builtin__:

>>> import __builtin__
>>> dir(__builtin__)
['AccessError', 'AttributeError', 'ConflictError',
'EOFError', 'IOError', 'ImportError', 'IndexError',
'KeyError', 'KeyboardInterrupt', 'MemoryError',
'NameError', 'None', 'OverflowError', 'RuntimeError',
'SyntaxError', 'SystemError', 'SystemExit',
'TypeError', 'ValueError', 'ZeroDivisionError',
'__name__', 'abs', 'apply', 'chr', 'cmp', 'coerce',
'compile', 'dir', 'divmod', 'eval', 'execfile',
'filter', 'float', 'getattr', 'hasattr', 'hash', 'hex',
'id', 'input', 'int', 'len', 'long', 'map', 'max',
'min', 'oct', 'open', 'ord', 'pow', 'range',
'raw_input', 'reduce', 'reload', 'repr', 'round',
'setattr', 'str', 'type', 'xrange'] 

6.6. Пакеты

Пакеты — способ структурирования пространств имен модулей, используя "точечную запись". Например, имя модуля A.B обозначает подмодуль с именем A в пакете B. Так же, как использование модулей делает безопасным использование глобального пространства имен авторами различных модулей, использование точечной записи делает безопасным использование имен модулей авторами многомодульных пакетов.

Предположим, Вы хотите спроектировать совокупность модулей ("пакет") для единообразной обработки звуковых файлов и данных. Существует множество форматов звуковых файлов (обычно распознаваемых по их расширению, например '.wav', '.aiff', '.au'), так что Вам необходимо создавать и поддерживать все возрастающий набор модулей для преобразования между различными форматами файлов. Вы можете захотеть выполнять множество различных операций над звуковыми данными (микширование, добавление эха, частотная обработка, создание искусственного стереоэффекта), то есть, вдобавок, Вы будете писать нескончаемый поток модулей для выполнения этих операций. Вот примерная структура Вашего пакета (выраженная в терминах иерархической файловой системы):

Sound/                  Верхний уровень пакета
    __init__.py         Инициализация пакета
    Formats/            Работа с файлами
        __init__.py
        wavread.py
        wavwrite.py
        aiffread.py
        aiffwrite.py
        auread.py
        auwrite.py
        ...
    Effects/            Звуковые эффекты
        __init__.py
        echo.py
        surround.py
        reverse.py
        ...
    Filters/            Фильтры
        __init__.py
        equalizer.py
        vocoder.py
        karaoke.py
        ... 

Файл '__init__.py' необходим для того, чтобы Python распознавал каталог, как содержащий пакет — таким образом предотвращается маскировка полноценных модулей, расположенных далее в путях поиска, каталогами с распространенными именами (такими как 'string'). В простейшем случае, '__init__.py' — пустой файл, но может содержать код инициализации пакета и/или устанавливать переменную __all__, описанную ниже.

Пользователи пакета могут импортировать индивидуальные модули пакета, например:

import Sound.Effects.echo 

В этом случае загружается модуль Sound.Effects.echo. На него нужно ссылаться по полному имени:

Sound.Effects.echo.echofilter(input, output,
                              delay=0.7, atten=4) 

Вы можете использовать альтернативный способ импортирования подмодуля:

from Sound.Effects import echo 

В этом случае также загружается модуль Sound.Effects.echo, и делает его доступным для использования без префикса:

echo.echofilter(input, output, delay=0.7, atten=4) 

Еще один вариант — импортировать желаемую функцию или переменную непосредственно:

from Sound.Effects.echo import echofilter 

И в этом случае загружается модуль Sound.Effects.echo, но на этот раз, функция echofilter() становится доступной для использования непосредственно:

echofilter(input, output, delay=0.7, atten=4) 

Заметим, что при использовании 'from package import item', item может быть модулем, подпакетом или другим именем, определенном в пакете package, таким как функция, класс или переменная. Инструкция import сначала проверяет, определено ли имя item в пакете, если нет, считает его модулем и пытается загрузить. Если при загрузке возникает ошибка, генерируется исключение ImportError.

И наоборот, при использовании инструкции 'import item.subitem.subsubitem', каждая единица, кроме последней, должна быть пакетом. Последняя единица может быть модулем или пакетом, но не может быть классом, функцией или переменной, определенной в предыдущей единице.

6.6.1. Импортирование всего содержимого пакета (модуля)

Что же происходит, когда пользователь использует 'from Sound.Effects import *'? В идеале, интерпретатор должен каким-либо образом обойти файлы, находящиеся в каталоге пакета, и импортировать их все. К сожалению, такой подход не будет работать достаточно хорошо на таких платформах, как Macintosh и Windows, где файловая система не всегда имеет точную информацию о регистре букв в именах файлов. В этом случае нет надежного пути узнать, с каким именем должен быть импортирован файл с именем 'ECHO.PY': echo, Echo или ECHO.

Единственный выход для автора — снабдить пакет явным указателем его содержимого. Инструкция import использует следующее соглашение: если в инициализационном файле '__init__.py' определен список с именем __all__, он используется в качестве списка имен модулей, которые должны импортироваться при использовании 'from package import *'. Поддержка этого списка в соответствии с текущим составом пакета возлагается на автора. Можно также не определять список __all__, если авторы не считают уместным импортирование *. Например, файл 'Sounds/Effects/__init__.py' может содержать следующий код:

__all__ = ["echo", "surround", "reverse"] 

Это означает, что from Sound.Effects import * импортирует три указанных модуля из пакета Sound.

Если список __all__ не определен, 'from Sound.Effects import *' не будет импортировать все модули пакета Sound.Effects, а только имена, явным образом определенные в инициализационном файле '__init__.py' (включая явно импортированные в нем модули). Кроме того, в текущую область видимости попадут модули пакета, явно загруженные предыдущими инструкциями import, например:

import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import * 

В приведенном примере, модули echo и surround импортируются в текущее пространство имен, потому что они уже определены в пакете Sound.Effects на момент выполнения инструкции from ... import.

Заметим, что импортирование всех имен, определенных в модуле или пакете, обычно приводит к засорению пространства имен и, как результат, к возможным конфликтам. (Некоторые модули специально спроектированы таким образом, чтобы экспортировать только имена, следующие определенному шаблону.) Кроме того, при этом глобальные переменные модуля становятся доступными только для чтения — при попытке присвоить такой переменной новое значение Вы создадите новую (локальную) переменную с таким же именем. А все изменения, внесенные в глобальные переменные модуля после его инициализации, не будут видны за его пределами.

Мы рекомендуем стараться использовать запись 'from package import specific_module', за исключением случаев, когда необходимо использовать модули с одинаковыми именами из разных пакетов.

6.6.2. Связи между модулями пакета

Часто возникает необходимость в связях между модулями одного пакета. Например, модуль surround может использовать модуль echo. В самом деле, подобные связи распространены настолько, что инструкция import сначала просматривает содержимое пакета, в который входит содержащий эту инструкцию модуль, и только потом в путях поиска модулей. Таким образом, модуль surround может просто использовать 'import echo' или 'from echo import echofilter'.

Когда пакеты разделены на подпакеты (пакет Sound в примере), нет краткой записи для ссылок между ответвлениями пакета — нужно использовать полное имя. Например, если модуль Sound.Filters.vocoder должен использовать модуль echo пакета Sound.Effects, нужно использовать 'from Sound.Effects import echo'.

7. Ввод/вывод

Существует несколько способов представления программного вывода: данные могут быть напечатаны в виде, удобном для восприятия, или записаны в файл для дальнейшего использования. В этой главе обсуждаются некоторые возможности представления выводимых данных.

7.1. Форматированный вывод

До сих пор мы использовали два способа вывода: вывод значений выражений в интерактивном режиме и с помощью инструкции print (третий способ — метод объектов-файлов write()).

Часто возникает желание иметь больший контроль над форматированием вывода, чем просто выводить значения, разделенные пробелом. Конечно, Вы можете сами обрабатывать строки: с помощью операций среза и объединения можно создать любое расположение, какое только Вы сможете представить. Строки имеют методы, позволяющие дополнять их пробелами до необходимой ширины колонки [Эти методы строк появились в версии 1.6 языка Python. В предыдущих версия они доступны в виде функций, определенных в модуле string.]. Другой путь — использовать оператор % со строкой в качестве левого аргумента. Оператор % интерпретирует строку справа как строку формата в стиле функции sprintf() в C, которую нужно применить к правому аргументу, и возвращает строку с результатом форматирования.

Безусловно, остается еще один вопрос: как получить строковое представление для значений различного типа? К счастью, Python предоставляет возможность преобразовывать значение любого типа в строку: с помощью функции str(). Фактически язык предоставляет две функции для получения строкового представления — repr() (тот же эффект можно получить просто заключив выражение в обратные кавычки: `expr`) и str(). Первый способ, например, используется интерпретатором для вывода значений выражений в интерактивном режиме, второй — для вывода аргументов инструкцией print. Функция str() по возможности возвращает представление, наиболее пригодное для вывода, а функция repr() — для ввода выражения в интерактивном режиме. Приведем несколько примеров:

>>> x = 10 * 3.14 

Число 31.4 не может быть точно представлено в двоичном виде, поэтому:

>>> x
31.399999999999999 

Однако функция str() выведет число с разумной точностью:

>>> y = 200*200
>>> s = 'Значение x равно ' + str(x) + \
...     ', значение y равно ' + str(y) + '...'
>>> print s
Значение x равно 31.4, значение y равно 40000... 

Длинные целые числа записываются в языке Python с суффиксом 'L'. Начиная с версии 1.6, функция str() его не выводит:

>>> repr(1000L)
'1000L'
>>> str(1000L)
'1000' 

Строковое представление можно получить и для других типов:

>>> p = [x, y]
>>> ps = repr(p)
>>> ps
'[31.399999999999999, 40000]'
>>> `x, y, ('spam', 'eggs')`
"(31.399999999999999, 40000, ('spam', 'eggs'))" 

Функция repr() (или ``) добавляет кавычки и записывает спецсимволы с помощью управляющих последовательностей:

>>> hello = 'hello, world\n'
>>> print hello
hello, world
>>> hellos = `hello`
>>> print hellos
'hello, world\012' 

Выведем таблицу квадратов и кубов двумя описанными способами:

>>> for x in range(1, 11):
...     print str(x).rjust(2), str(x*x).rjust(3),
...     # Обратите внимание на завершающую запятую
...     print str(x*x*x).rjust(4)
...
1    1    1
2    4    8
3    9   27
4   16   64
5   25  125
6   36  216
7   49  343
8   64  512
9   81  729
10 100 1000

>>> for x in range(1,11):
...     print '%2d %3d %4d' % (x, x*x, x*x*x)
...
1    1    1
2    4    8
3    9   27
4   16   64
5   25  125
6   36  216
7   49  343
8   64  512
9   81  729
10 100 1000 

(Обратите внимание на то, что один пробел между колонками был добавлен инструкцией print.)

Этот пример демонстрирует использование метода строк rjust(), который выравнивает строку вправо в поле заданной ширины, дополняя ее слева пробелами. Аналогично действуют методы ljust() и center(). Они не выводят ничего — просто возвращают новую строку. Если исходная строка слишком длинная, она не обрезается, а возвращается в неизменном виде: обычно лучше внести беспорядок в расположение колонок, чем вывести неверное значение. (Если Вы действительно хотите ее обрезать, воспользуйтесь операцией среза: 's.ljust(n)[0:n]'.)

Также может быть полезна функция zfill(), определенная в модуле string, которая дополняет слева нулями строку с числом, корректно обрабатывая знаки плюс и минус:

>>> import string
>>> string.zfill('12', 5)
'00012'
>>> string.zfill('-3.14', 7)
'-003.14'
>>> string.zfill('3.14159265359', 5)
'3.14159265359' 

Использование оператора % выглядит примерно так:

>>> import math
>>> print 'Значение PI приближенно равно %5.3f.' % \
...        math.pi
Значение PI приближенно равно 3.142. 

Если в строке нужно вывести несколько значений, в качестве правого операнда используется кортеж:

>>> table = {'Sjoerd': 4127,
...          'Jack' : 4098,
...          'Dcab' : 7678}
>>> for name, phone in table.items():
...     print '%-10s ==> %10d' % (name, phone)
...
Sjoerd    ==>        4127
Dcab      ==>        7678
Jack      ==>        4098 

Большинство форматов работают точно так же, как и в C. Однако, если типы аргументов не соответствуют формату, интерпретатор приводит их к необходимому типу (например, выражение любого типа может быть преобразовано в строку с помощью встроенной функции str()) или, если это невозможно, генерирует исключение [Из этого правила есть исключение: интерпретатор не будет преобразовывать строку к числовому типу.]. Вы можете использовать * для того, чтобы передать отдельным параметром ширину поля или точность.

Заметим, что если правый аргумент кортеж, он всегда считается списком аргументов:

>>> def f(x):
...     print 'Функции передано значение "%s"' % x
...
>>> f(1)
Функции передано значение "1"
>>> f([1, 2])
Функции передано значение "[1, 2]"
>>> # интерпретируется не так, как Вы ожидали
... f((1,))
Функции передано значение "1"
>>> # ошибка
... f((1, 2))
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 2, in f
TypeError: not all arguments converted 

В данном случае надежнее будет использовать в качестве правого операнда кортеж, состоящий из одного элемента:

>>> def f(x):
...     print 'Функции передано значение "%s"' % (x,)
...
>>> # теперь все правильно
... f((1,))
Функции передано значение "(1,)"
>>> f((1, 2))
Функции передано значение "(1, 2)" 

В случае длинных строк формата, Вы можете захотеть ссылаться на переменные по имени вместо их положения. Это можно сделать, используя расширенную запись в виде %(name)format, например:

>>> table = {'Sjoerd': 4127, 'Jack': 4098,
...         'Dcab': 8637678}
>>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; \
... Dcab: %(Dcab)d' % table
Jack: 4098; Sjoerd: 4127; Dcab: 8637678 

Такая запись особенно полезна в комбинации со встроенной функцией vars(), которая возвращает словарь с переменными в текущей области видимости.

Более подробно операции над строками описаны в разделе 11.2.1.

7.2. Чтение и запись файлов

Встроенная функция open() возвращает объект-файл (file) и обычно используется с двумя аргументами: 'open(filename, mode)'.

>>> f=open('/tmp/workfile', 'wb')
>>> print f
<open file '/tmp/workfile', mode 'wb' at 80a0960> 

Первый аргумент — строка, содержащая имя файла, второй аргумент — строка, содержащая несколько символов, описывающих режим использования файла. Режим может быть 'r', если файл открывается только для чтения, 'w' — только для записи (существующий файл будет перезаписан), и 'a' — для дописывания в конец файла. В режиме 'r+' файл открывается сразу для чтения и записи. Аргумент mode не является обязательным: если он опущен, подразумевается 'r'.

В Windows (а в некоторых случаях и в Macintosh) файлы по умолчанию открываются в текстовом режиме — для того, чтобы открыть файл в двоичном режиме, необходимо к строке режима добавить 'b'. Следует помнить, что двоичные данные, такие как картинки в формате JPEG и даже текст в UNICODE, при чтении из файла или записи в файл, открытый в текстовом режиме, будут испорчены! Лучший способ оградить себя от неприятностей — всегда открывать файлы в двоичном режиме, даже на тех платформах, где двоичный режим используется по умолчанию (возможно у Вас когда-нибудь возникнет желание запустить программу на другой платформе).

7.2.1. Методы объектов-файлов

Примеры в этом разделе рассчитаны на то, что объект-файл f уже создан.

f.read(size) считывает и возвращает некоторое количество данных из файла. Аргумент size не является обязательным. Если он опущен или отрицательный, будет считано все содержимое файла, в противном случае, будет считано не более size байт данных. По достижении конца файла, возвращается пустая строка ().

>>> f.read() # Считываем все содержимое файла
'This is the entire file.\012'
>>> f.read()
'' 

f.readline() считывает из файла одну строку. Возвращаемая строка всегда заканчивается символом новой строки (\n), за исключением последней строки файла, если файл не заканчивается символом новой строки. Это делает возвращаемое значение недвусмысленным: если readline() возвращает пустую строку — значит, достигнут конец файла, в то время как пустая строка будет представлена как '\n'.

>>> f.readline() # Считываем первую строку
'This is the first line of the file.\012'
>>> f.readline() # Считываем вторую строку
'Second line of the file\012'
>>> f.readline() # Достигли конца файла
'' 

f.readlines() считывает все содержимое файла, и возвращает список строк.

>>> f.readlines()
['This is the first line of the file.\012',
'Second line of the file\012'] 

f.write(s) записывает содержимое строки s в файл.

>>> f.write('This is a test\n') 

f.tell() возвращает текущее положение в файле в байтах, отсчитываемое от начала файла. Чтобы изменить текущее положение, используйте 'f.seek(offset, from_what)'. Новое положение вычисляется путем добавления offset к точке отсчета. Точка отсчета выбирается в зависимости от значения аргумента from_what: 0 соответствует началу файла (используется по умолчанию, если метод вызывается с одним аргументом), 1 — текущему положению и 2 — концу файла.

>>> f=open('/tmp/workfile', 'rb+')
>>> f.write('0123456789abcdef')
>>> f.seek(5)     # Go to the 5th byte in the file
>>> f.read(1)
'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
>>> f.read(1)
'd' 

После того, как Вы закончили все операции с файлом, закройте файл с помощью f.close(). При попытке использовать закрытый файл для операций чтения/записи генерируется исключение ValueError:

>>> f.close()
>>> f.read()
Traceback (innermost last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file 

Объекты-файлы имеют еще несколько методов, используемых не так часто (isatty(), truncate()). Для получения о них полной информации смотрите раздел 11.7.

7.2.2. Модуль pickle

Строки легко могут быть записаны в файл и считаны из него. Числа требуют приложения небольших усилий, так как метод read() всегда возвращает строки, которые нужно обработать, например, с помощью функции int(). Однако задача сильно усложняется, если Вы хотите сохранять более сложные типы данных, такие как списки, словари или экземпляры классов.

Чтобы пользователю не приходилось постоянно писать и отлаживать код для сохранения сложных типов данных, Python предоставляет для этих целей стандартный модуль pickle. Этот изумительный модуль позволяет получить представление почти любого объекта (даже некоторые формы кода) в виде набора байтов (строки), одинакового для всех платформ. Такой процесс называют "консервированием" (pickling). Такое представление (законсервированный объект) можно сохранить в файле или передать через сетевое соединение на другую машину. К считанному из файла или принятому на другой машине законсервированному объекту может быть применена операция восстановления (unpickling).

Если у Вас есть объект x и файловый объект f, открытый для записи, простейший способ сохранить объект потребует всего одну строку кода:

pickle.dump(x, f) 

Так же просто можно восстановить объект (f — файловый объект, открытый для чтения):

x = pickle.load(f) 

Для получения полного представления о возможностях модуля pickle, смотрите его описание в третьей части книги.

Использование модуля pickle является стандартным в языке Python путем сохранения так называемых долгоживущих (persistent) объектов для дальнейшего использования. Модуль настолько часто используется, что многие авторы дополнительных модулей заботятся о том, чтобы новые типы данных (например, матрицы) могли быть правильно законсервированы.

8. Ошибки и исключения

До сих пор мы лишь упоминали об ошибках, но если Вы пробовали приведенные примеры, то могли увидеть некоторые из них. Можно выделить (как минимум) два различимых типа ошибок: синтаксические ошибки и исключения [Синтаксические ошибки также являются исключениями, которые, однако, не могут быть перехвачены на том же уровне.].

8.1. Синтаксические ошибки

Синтаксические ошибки, пожалуй, чаще всего встречаются во время изучения языка:

>>> while 1 print 'Hello world'
  File "<stdin>", line 1
    while 1 print 'Hello world'
                ^
SyntaxError: invalid syntax 

Синтаксический анализатор выводит строку, содержащую ошибку, и указывает место, где ошибка была обнаружена. Ошибка обычно вызвана лексемой, предшествующей стрелке: в приведенном примере, ошибка обнаружена на месте ключевого слова print, так как перед ним отсутствует двоеточие (':'). Имя файла и номер строки выводится для того, чтобы Вы знали, где искать ошибку в случае, если инструкции считываются из файла.

8.2. Исключения

Даже если инструкция или выражение синтаксически верно, ошибка может произойти при попытке выполнения. Ошибки, обнаруженные во время выполнения, не являются безусловно фатальными, и скоро Вы узнаете, как их можно обрабатывать в программах на языке Python. Большинство исключений, однако, не обрабатываются программами и приводят к сообщениям об ошибке:

>>> 10 * (1/0)
Traceback (innermost last):
  File "<stdin>", line 1
ZeroDivisionError: integer division or modulo
>>> 4 + spam*3
Traceback (innermost last):
  File "<stdin>", line 1
NameError: spam
>>> '2' + 2
Traceback (innermost last):
  File "<stdin>", line 1
TypeError: illegal argument type for built-in operation 

Последняя строка сообщения показывает, что произошло. Исключения бывают разного типа — он выводится в сообщении. Типы исключений в приведенном примере: ZeroDivisionError, NameError и TypeError. Имена стандартных исключений являются встроенными идентификаторами, но не являются зарезервированными ключевыми словами.

Сразу после типа выводятся подробности возникновения исключения. Предшествующая часть сообщения об ошибке показывает контекст возникновения исключительной ситуации в форме содержимого стека. При этом выводятся строки исходного текста, за исключением строк, читаемых со стандартного ввода.

В главе 13 перечислены все встроенные исключения и их назначение.

8.3. Обработка исключений

Вы можете писать программы, которые будут обрабатывать определенные исключения. Посмотрите на следующий пример, в котором пользователю будет выдаваться приглашение до тех пор, пока не будет введено целое число или выполнение не будет прервано (обычно Ctrl-C). Во втором случае генерируется исключение KeyboardInterrupt.

>>> while 1:
...     try:
...         x = int(raw_input(
...             "Введите, пожалуйста, целое число: "))
...         break
...     except ValueError:
...         print "Вы ошиблись. Попробуйте еще раз..."
... 

Инструкция try работает следующим образом.

  • Сначала выполняется ветвь try (инструкции, находящиеся между ключевыми словами try и except).
  • Если не возникает исключительной ситуации, ветвь except пропускается и выполнение инструкции try завершается.
  • Если во время выполнения ветви try генерируется исключение, оставшаяся часть ветви пропускается. Далее, если тип (класс) исключения соответствует указанному после ключевого слова except, выполняется ветвь except и выполнение инструкции try завершается.
  • Если исключение не соответствует указанному после ключевого слова except, то оно передается внешнему блоку try или, если обработчик не найден, исключение считается не перехваченным, выполнение прерывается и выводится сообщение об ошибке.

Инструкция try может иметь более одной ветви except, определяя обработчики для разных исключений. Выполняться будет (как максимум) только один из них. Обрабатываются только исключения, сгенерированные в соответствующей ветви try, но не в других обработчиках инструкции try. После ключевого слова except может быть указано несколько типов исключений в виде кортежа:

... except (RuntimeError, TypeError, NameError):
...     pass 

В последней ветви except тип исключения может быть опущен — в этом случае будут обрабатываться все исключения. Используйте такую запись с особой осторожностью — так Вы легко можете замаскировать реальные ошибки! Перехват всех исключений можно использовать для вывода сообщения об ошибке, а затем повторно сгенерировать его (позволяя обрабатывать исключение в другом месте):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError, exc:
    print "Ошибка ввода/вывода", exc
except ValueError:
    print "Не могу преобразовать данные к целому типу."
except:
    print "Неожиданная ошибка:", sys.exc_info()[0]
    raise # повторно генерирует последнее
          # перехваченное исключение 

После всех ветвей except, инструкция try может содержать ветвь else, которая будет выполняться в случае, если во время выполнения ветви try исключения не генерируются. Например:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'Не могу открыть', arg
    else:
        print arg, 'содержит', len(f.readlines()), \
              'строк'
        f.close() 

Обычно лучше использовать ветвь else, чем добавлять код в основную ветвь инструкции try, так как это позволяет избежать обработки исключений, сгенерированных кодом, который Вы вовсе не собирались защищать.

Исключение может иметь ассоциированное с ним значение — аргумент, переданный классу исключения при инициализации. Наличие и тип аргумента зависит от типа исключения. Ассоциированное значение используется при получении для исключения строкового значения. Чтобы получить значение исключения, в ветви except после класса исключения (или кортежа классов) укажите переменную:

>>> try:
...     spam()
... except NameError, x:
...     print 'eIN', x, 'не определено'
...
Имя spam не определено 

Если исключение не обрабатывается, значение исключения выводится в сообщении об ошибке после имени класса исключения.

Обработчик перехватывает не только исключения, сгенерированные непосредственно в блоке try, но и в вызываемых из него функциях. Например:

>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError, exc:
...     print 'Ошибка времени выполнения:', exc
...
Ошибка времени выполнения: integer division or modulo 

8.4. Генерация исключений

Инструкция raise позволяет программисту принудительно сгенерировать исключение. Например:

>>> raise NameError('HiThere')
Traceback (innermost last):
  File "<stdin>", line 1
NameError: HiThere 

В качестве аргумента raise используется экземпляр класса. Класс указывает на тип исключения; аргумент, передаваемый конструктору, обычно описывает "подробности" возникновения исключительной ситуации.

8.5. Исключения, определяемые пользователем

Вы можете использовать свои собственные исключения, используя строковые выражения для обозначения его имени (устаревший способ) или создавая новые классы исключения. Например:

>>> class MyError(Exception): pass
...
>>> try:
...     raise MyError(2*2)
... except MyError, e:
...     print 'Исключение MyError, value равно', e
...
Исключение MyError, value равно 4
>>> raise MyError(1)
Traceback (innermost last):
  File "<stdin>", line 1
__main__.MyError: 1 

Подробную информацию о классах Вы можете получить в главе 9.

8.6. "Страхование" от ошибок

Еще один вариант записи инструкции try — с определением "страховочной" ветви finally, которая будет выполняться при любых обстоятельствах. Например:

>>> try:
...     raise KeyboardInterrupt()
... finally:
...     print 'До свидания!'
...
До свидания!
Traceback (innermost last):
  File "<stdin>", line 2
KeyboardInterrupt 

Ветвь finally выполняется независимо от того, возникла ли исключительная ситуация во время выполнения блока try или нет, в том числе и если выход происходит по инструкции break или return.

Инструкция try может иметь либо одну или несколько ветвей except, либо одну ветвь finally, но не оба варианта сразу.

9. Классы

Механизм классов языка Python использует минимум нового синтаксиса и семантики. Он представляет собой смесь механизмов классов C++ и Modula-3. Так же, как и модули, классы не возводят абсолютного барьера между определением и пользователем, а скорее полагаются на хороший стиль пользователя "не вламываться" в определение. Однако наиболее важные особенности классов полностью сохранены: механизм наследования допускает несколько базовых классов, производный класс может переопределить любые методы базовых классов, из метода можно вызывать метод с тем же именем базового класса. Объекты могут содержать произвольное количество собственных данных.

Говоря в терминологии C++, все атрибуты класса (включая поля данных) являются открытыми (public), а все методы — виртуальными (virtual). Как и в Modula-3, нет сокращения, позволяющего ссылаться на атрибуты объекта из его метода: функция-метод определяется с явным первым аргументом, представляющим сам объект, и подставляемым автоматически при вызове. Подобно Smalltalk, классы сами являются объектами, хотя и в более широком смысле этого слова: в языке Python все типы данных являются объектами. Это позволяет использовать общую семантику для импортирования и переименования, но встроенные типы (как и в C++ и Modula-3) нельзя использовать в качестве базовых классов. Кроме того, так же как в C++, но в отличие от Modula-3, можно переопределить большинство встроенных операций над экземплярами классов.

9.1. Несколько слов о терминологии

По причине отсутствия универсальной терминологии, время от времени мы будем пользоваться терминами Smalltalk и C++. Также следует предупредить, что существует терминологическая ловушка для читателей, знакомых с объектно-ориентированным программированием: слово "объект" в языке Python совсем не обязательно означает экземпляр класса. Так, объекты встроенных типов, такие как целые числа, списки и даже некоторые экзотические, вроде файлов, тоже не являются экземплярами классов. Однако все объекты языка Python имеют общую часть семантики, лучше всего описываемую словом "объект".

Один и тот же объект может быть привязан к нескольким именам, в том числе находящимся в различных пространствах имен (использование псевдонимов, aliasing). На это свойство обычно не обращают внимания при первом знакомстве, и его можно благополучно игнорировать, пока Вы работаете с неизменяемыми объектами (числа, строки, кортежи). Однако использование псевдонимов (преднамеренно!) отражается на семантике кода, работающего с изменяемыми объектами (списки, словари, файлы и т.п.). Обычно из этого извлекают пользу, так как псевдонимы во многих отношениях работают аналогично указателям. Например, передача объекта в качестве аргумента достаточно эффективна, потому что реализована как передача указателя. Если функция изменяет объект, переданный в качестве аргумента, все изменения будут видны после возврата из функции — таким образом, отпадает необходимость в использовании двух различных механизмов передачи аргументов, как это сделано в языке Pascal.

9.2. Области видимости и пространства имен

Перед тем, как знакомиться с классами, следует рассказать о правилах языка, касающихся областей видимости. Определения классов выполняют несколько изящных приемов с пространствами имен, и Вам следует знать о работе областей видимости и пространств имен, для полного понимания происходящего.

Начнем с нескольких определений. Пространство имен определяет отображение имен в объекты. Большинство пространств имен в языке Python реализованы как словари, что, однако, никак себя не проявляет (кроме производительности) и может быть изменено в будущем. Вот несколько примеров пространств имен: множество встроенных имен (функции, исключения), глобальные имена в модуле, локальные имена при вызове функций. В этом смысле множество атрибутов объекта также образует пространство имен. Важно понимать, что между именами в разных пространствах имен нет связи. Например, два модуля могут определить функции с именем "maximize" не создавая при этом путаницы — пользователь должен ссылаться на них с использованием имени модуля в качестве приставки.

Под словом атрибут мы подразумеваем любое имя, следующее после точки: например, в выражении z.real, real является атрибутом объекта z. Строго говоря, имена в модулях являются атрибутами модуля: в выражении modname.funcname, modname является объектом-модулем и funcname является его атрибутом. В этом случае имеет место прямое соответствие между атрибутами модуля и глобальными именами, определенными в модуле: они совместно используют одно пространство имен! [Есть одно исключение. Объект-модуль имеет секретный атрибут __dict__, содержащий словарь, используемый для реализации пространства имен модуля. Имя __dict__ является атрибутом, однако не является глобальным именем. Не следует использовать атрибут __dict__ где-либо кроме отладчиков, так как это нарушает абстракцию реализации пространства имен.]

Атрибуты могут быть доступны только для чтения, а могут и допускать присваивание. Во втором случае Вы можете записать 'modname.attribute = 42' или даже удалить его, используя инструкцию del: 'del modname.attribute'.

Пространства имен создаются в различные моменты времени и имеют разную продолжительность жизни. Пространство имен, содержащее встроенные имена, создается при запуске интерпретатора и существует все время его работы. Глобальное пространство имен модуля создается, когда он считывается, и, обычно, также существует до завершения работы интерпретатора. Инструкции, выполняемые на верхнем уровне, т. е. читаемые из файла-сценария или интерактивно, рассматриваются как часть модуля __main__, имеющего собственное глобальное пространство имен. (В действительности, встроенные имена также находятся в модуле — __builtin__.)

Локальное пространство имен функции создается при вызове функции и удаляется при выходе из нее (возвращается значение или генерируется исключение, которое не обрабатывается внутри функции). Безусловно, при рекурсивном вызове создается собственное локальное пространство имен для каждого вызова.

Область видимости — фрагмент программы, в котором пространство имен непосредственно доступно, то есть нет необходимости в использовании записи через точку для того, чтобы поиск имени производился в данном пространстве имен.

Несмотря на статическое определение, области видимости используются динамически. В любой момент времени выполнения программы используется ровно три вложенных области видимости (три непосредственно доступных пространства имен). Сначала поиск имени производится во внутренней области видимости, содержащей локальные имена. Далее — в средней, содержащей глобальные имена модуля. И, наконец, во внешней, содержащей встроенные имена.

Обычно локальная область видимости соответствует локальному пространству имен текущей функции (класса, метода). За пределами функции (класса, метода), локальная область видимости соответствует тому же пространству имен, что и глобальная: пространству имен текущего модуля.

Важно понимать, что область видимости определяется по тексту: глобальная область видимости функции, определенной в модуле — пространство имен этого модуля, независимо от того, откуда или под каким псевдонимом функция была вызвана. С другой стороны, реально поиск имен происходит динамически, во время выполнения. Однако язык развивается в сторону статического разрешения имен, определяемого во время "компиляции", поэтому не стоит полагаться на динамическое разрешение имен! (Фактически, локальные переменные уже определяются статически.)

В языке Python есть особенность: присваивание всегда производится имени в локальной области видимости, если перед этим не было указано явно (инструкция global), что переменная находится в глобальной области видимости. Присваивание не копирует данные — оно только привязывает имя к объекту. То же самое верно и для удаления: инструкция 'del x' удаляет имя x из пространства имен, соответствующего локальной области видимости. В сущности, все операции, которые вводят новые имена, используют локальную область. Так, импортирование модуля и определение функции привязывают модуль или функцию к локальной области видимости.

9.3. Первый взгляд на классы

Классы требуют немного нового синтаксиса, добавляют три новых типа объектов и некоторое количество новой семантики.

9.3.1. Синтаксис определения класса

Простейшая модель определения класса выглядит следующим образом:

class имя_класса:
    инструкция1
    .
    .
    .
    инструкцияN 

Определение класса, подобно определению функции (инструкция class, как и def), должно быть выполнено перед тем, как класс можно будет использовать. (Предполагается, что Вы можете поместить определение класса в одну из ветвей инструкции if или в тело функции.)

На практике инструкции внутри определения класса обычно являются определениями функций, но разрешены, и иногда полезны, другие инструкции. Определение функции внутри класса имеет специфическую форму записи списка аргументов, продиктованную соглашениями по вызову методов. Мы вернемся к этим особенностям позже.

Когда выполняется определение класса, создается новое пространство имен и используется в качестве локальной области видимости при выполнении инструкций в теле определения. Таким образом, все присваивания производятся именам из нового пространства имен. В частности, определение функции создает соответствующее имя в пространстве имен класса.

По окончании выполнения определения функции, создается объект-класс. По существу он является "оболочкой" пространства имен, созданного определением класса. В следующем разделе мы расскажем об объектах-классах более подробно. Исходная область видимости (которая была перед выполнением определения класса) восстанавливается, и объект-класс привязывается к имени класса (в приведенном примере — имя_класса) в пространстве имен, соответствующему исходной области видимости.

9.3.2. Объекты-классы

Объекты-классы поддерживают два вида операций: доступ к атрибутам и создание экземпляра класса.

Доступ к атрибутам объекта-класса осуществляется так же, как и для других объектов языка Python: obj.attrname. Действительными именами атрибутов являются все имена, помещенные в пространство имен класса при создании объекта-класса. Пусть определение класса выглядит следующим образом:

class MyClass:
    'Простой пример класса'
    i = 12345
    def f(x):
    return 'Привет всему миру' 

Тогда i и f являются действительными атрибутами, ссылающимися на целое число и объект-метод соответственно. Атрибутам можно присваивать новые значения, например, Вы можете изменить значение MyClass.i. __doc__ также является действительным атрибутом, ссылающимся на строку документации класса: 'Простой пример класса'.

Создание экземпляра класса использует запись вызова функций. Просто считайте объект-класс функцией без параметров, возвращающей созданный экземпляр класса. Например,

x = MyClass() 

создает новый экземпляр класса и присваивает его локальной переменной x.

В приведенном примере создается "пустой" объект. Во многих случаях необходимо создавать объект с определенным начальным состоянием — для этого класс должен содержать специальный метод __init__(), например:

class MyClass:
    def __init__(self):
        self.data = [] 

Если для класса определен метод __init__(), то он автоматически вызывается при создании каждого экземпляра этого класса.

Для большей гибкости метод __init__(), конечно, может иметь аргументы. В этом случае, аргументы, используемые при создании экземпляра класса, передаются методу __init__(). Например:

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5) 

9.3.3. Объекты-экземпляры

Что мы теперь можем делать с объектами-экземплярами? Основная операция, воспринимаемая объектом-экземпляром — доступ к его атрибутам. Атрибуты могут быть двух видов.

Первый — атрибуты данных. Они соответствуют "переменным экземпляра" в Smalltalk и "членам данных" в C++. Атрибуты данных не нужно декларировать: они возникают, когда им первый раз присваивают значение. Например, если x является экземпляром класса MyClass, определенного выше, следующий код выведет значение 16:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print x.counter
del x.counter 

Второй тип атрибутов — методы. Метод — это функция, "принадлежащая" объекту. В языке Python термин "метод" применим не только к экземплярам классов — другие объекты тоже могут иметь методы. Например, у объектов-списков есть методы append(), insert(), remove(), sort() и т. д. Однако ниже, если явно не указано другого, мы будем использовать термин "метод" для методов экземпляров (instance method).

Действительные имена методов объекта-экземпляра зависят от класса: все атрибуты класса, являющиеся объектами-функциями автоматически становятся методами при обращении к соответствующим атрибутам экземпляра [Функция может быть записана в lambda-форме, однако другие объекты, поддерживающие вызов (класс или экземпляр класса, для которого определен метод __call__) не подходят.]. Так, в нашем примере x.f является методом, привязанным к объекту x. Но x.f — это не то же самое, что и MyClass.f. В первом случае метод "знает" объект, к которому он применяется, во втором — он не привязан к какому-либо объекту и ведет себя аналогично функции.

9.3.4. Методы экземпляров классов

Обычно метод вызывают непосредственно:

x.f() 

В нашем примере он вернет строку 'Привет всему миру'. Однако совсем не обязательно вызывать метод прямо. x.f является объектом-методом, и его можно сохранить для дальнейшего использования:

xf = x.f
while 1:
    print xf() 

будет выводить 'Привет всему миру' до тех пор, пока выполнение не будет прервано.

Что же происходит при вызове метода? Вы могли заметить, что x.f() вызывается без аргумента, несмотря на то, что в определении аргумент указан. Что же случилось с аргументом? Конечно, Python генерирует исключение, если функцию, требующую аргумент, вызвать без него — даже если аргумент на самом деле не используется.

Вы могли уже догадаться: особенность методов состоит в том, что объект, для которого он применяется, передается в качестве первого аргумента. В нашем примере вызов x.f() полностью эквивалентен MyClass.f(x). В общем, вызов метода, привязанного к объекту, со списком из n аргументов полностью эквивалентен вызову соответствующего не привязанного метода (или функции) со списком аргументов, полученным добавлением объекта перед первым аргументом.

Если Вы все еще не поняли, как работают методы, возможно, взгляд на реализацию внесет ясность. Если Вы ссылаетесь на атрибут объекта, не являющийся атрибутом данных, то поиск атрибута производится в классе, экземпляром которого является объект. Если имя соответствует действительному атрибуту класса, который определен как функция, то атрибут считается методом. Метод является структурой, содержащей информацию о классе, в котором он определен, и функции, его представляющей, но "не знает", к какому именно объекту будет применяться (не привязанный к объекту метод). Соответствующий атрибут экземпляра класса, в свою очередь, содержит, помимо указанной информации, еще и ссылку на объект-экземпляр (привязанный к объекту метод) упакованные вместе в объекте-методе. При вызове метода со списком аргументов, он распаковывается, создается новый список аргументов из объекта-экземпляра и исходного списка аргументов, и с этим списком вызывается объект-функция.

9.4. Выборочные замечания

Атрибуты экземпляров (обычно атрибуты данных) записываются поверх атрибутов классов (обычно методов) с тем же именем. Чтобы избежать конфликтов имен, которые могут привести к тяжело обнаружимым ошибкам, полезно использовать своего рода соглашение, позволяющее минимизировать вероятность конфликтов. Например: называть методы именами, начинающимися с заглавной буквы, добавлять небольшую приставку в начало имен атрибутов данных (возможно просто символ подчеркивания) или использовать глаголы для методов и существительные для атрибутов данных.

В некоторых случаях изменение атрибутов данных напрямую, а не с помощью специально предназначенных для этого методов, может привести к порче инварианта объекта и непригодности его для дальнейшего использования. Вы можете "спрятать" данные и, тем самым, защитить их от случайного изменения. Для этого в языке существует соглашение: все атрибуты, имена которых содержат не менее двух символов подчеркивания в начале и не более одного символа подчеркивания в конце, считаются частными и доступны только из методов объекта. (На самом деле, Вы можете получить доступ к частным атрибутам извне, используя специальное имя, однако такой доступ никак не назовешь случайным. Более подробно работа с частными атрибутами описана в разделе 9.6.) С другой стороны, модули расширения, написанные на C, могут полностью спрятать детали реализации и при необходимости полностью контролировать доступ.

Обычно первый аргумент в определении метода называют self. Это не более чем соглашение: имя self не имеет абсолютно никакого специального значения. Однако, следуя этому соглашению, Вы делаете код более понятным для других программистов. (Некоторые программы, например программы просмотра классов, рассчитаны на то, что пользователь всегда следует этому соглашению.)

Любой объект-функция, являющийся атрибутом класса, определяет метод экземпляров этого класса. Совсем не обязательно, чтобы определение функции находилось в определении класса: присваивание объекта-функции локальной переменной также будет работать. Например:

# Определение функции за пределами определения класса
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'Привет всему миру'
    h = g 

Теперь f, g и h являются атрибутами класса C и ссылаются на объекты функции и, следовательно, все они будут методами экземпляров класса C. Вы можете также использовать функцию, определенную с помощью оператора lambda. Заметим, что такая практика обычно только сбивает с толку.

Методы могут вызывать другие методы, как атрибуты аргумента self, например:

class Bag:
    def __init__(self):
        self.data = []
    def add(self, x):
        self.data.append(x)
    def addtwice(self, x):
        self.add(x)
        self.add(x) 

Методы могут ссылаться на глобальные имена точно таким же способом, как и обычные функции. Глобальная область видимости, ассоциированная с методом — это глобальное пространство имен модуля, содержащего определение функции (так как определение функции обычно находится в определении класса, то — в глобальном пространстве имен модуля, содержащего определение класса). В то время как редко существует веская причина использовать глобальные данные в методе, глобальная область видимости все же находит множество разумных применений: использование импортированных в глобальную область видимости модулей, а также определенных в ней функций и классов.

Атрибуты классов ведут себя как статические атрибуты их экземпляров (то есть общие для всех экземпляров данного класса). Однако присвоить такому атрибуту новое значение Вы можете, только обратившись к нему как атрибуту того класса, в котором он определен (в противном случае Вы лишь создадите новый атрибут экземпляра с таким же именем).

9.5. Наследование

Конечно, особенность языка не достойна называться "классом" без поддержки наследования. Определение производного класса с именем производный_класс выглядит следующим образом:

class производный_класс(базовый_класс):
    инструкция1
    .
    .
    .
    инструкцияN 

Базовый класс (базовый_класс) должен быть определен в области видимости, в которой находится определение производного класса (производный_класс). Вместо имени базового класса можно использовать выражение, например, если базовый класс определен в другом модуле:

class производный_класс(модуль.базовый_класс): 

Определение производного класса выполняется так же, как и определение базового класса. Сконструированный объект-класс помнит базовый — он используется для разрешения имен атрибутов: если запрошенный атрибут не найден в классе, поиск продолжается в базовом классе. Это правило применяется рекурсивно, если базовый класс, в свою очередь, является производным от другого класса. В создании экземпляра производного класса нет ничего особенного: производный_класс() порождает новый экземпляр класса.

Производные классы могут переопределить методы базовых классов. Метод базового класса, вызывающего другой определенный для него метод, может, на самом деле, вызывать метод производного класса, переопределившего этот метод (говоря в терминах C++, все методы в языке Python являются виртуальными).

Переопределяя метод в производном классе, Вы можете также захотеть вызвать метод базового класса с тем же именем. Это можно сделать напрямую: просто вызовите 'базовый_класс.метод(self, список_аргументов)', где базовый_класс — ссылка на базовый класс в том виде, в котором он доступен в текущей области видимости.

В языке Python есть ограниченная поддержка множественного наследования. Определения класса с несколькими базовыми классами выглядит следующим образом:

class производный_класс(базовый_класс1,
                        базовый_класс2,
                        базовый_класс3):
    инструкция1
    .
    .
    .
    инструкцияN 

Единственное правило, необходимое для объяснения семантики, — правило разрешения имен атрибутов. Поиск производится сначала в глубину, затем слева направо. Таким образом, если атрибут не найден в производный_класс, то он ищется сначала в базовый_класс1, затем (рекурсивно) в базовых классах класса базовый_класс1 и только потом в базовый_класс2 и т. д. (Для некоторых людей кажется более естественным другой порядок разрешения имен атрибутов — сначала в классах базовый_класс1, базовый_класс2, базовый_класс3 и только потом в базовых классах класса базовый_класс1. Однако в этом случае возникает зависимость результата от реализации каждого из базовых классов. С принятым же правилом, нет разницы между прямыми и унаследованными атрибутами базовых классов.)

9.6. Частные атрибуты

Python предоставляет ограниченную поддержку частных атрибутов классов. Любой атрибут вида __атрибут (имя которого содержит не менее двух символов подчеркивания в начале и не более одного в конце) заменяется на _класс__атрибут, где класс — имя текущего класса с "обрезанными" символами подчеркивания в начале. Такая обработка производится независимо от синтаксического расположения идентификатора, то есть может использоваться для определения частных атрибутов, доступ к которым будет возможен только из методов этого класса и методов его экземпляров. (Имя может быть обрезано, если его длина превысит 255 символов.) Если Вы ссылаетесь на имя, находясь за пределами класса, или если имя класса состоит только из символов подчеркивания, то оно преобразованию не подлежит.

Преобразование имен обеспечивает классам возможность определить "частные" атрибуты, не беспокоясь о том, что производные классы могут переопределить их, а также не дают к ним доступа коду, находящемуся за пределами класса. Заметьте, что правила преобразования разработаны в основном для того, чтобы избежать случайного вмешательства. Иногда доступ к частным атрибутам необходим, например, для отладчиков — это одна из причин, почему оставлена лазейка.

Если Вы из класса вызываете код с помощью exec, execfile, eval() или evalfile(), то внутри этого кода класс не будет считаться текущим: ситуация аналогична использованию инструкции global — действие ограничивается единовременно байт-компилированным кодом. Это ограничение распространяется и на getattr(), setattr() и delattr(), а также на прямое использование __dict__.

9.7. Примеры использования классов

Иногда полезно иметь тип данных (record в Pascal или struct в C), объединяющий несколько именованных единиц данных. С этой задачей прекрасно справится пустой класс:

class Employee:
    pass

# Создаем пустую карточку на служащего
john = Employee()

# Запоняем поля карточки:
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000 

Использование экземпляров классов в качестве исключений позволяет расположить их в виде "дерева" и обрабатывать ошибки находящиеся на определенной ветви.

Часто вместо ожидаемого типа данных в функции (методе) можно использовать экземпляр класса, эмулирующего методы этого типа. Например, если есть функция, считывающая данные из файла, Вы можете определить класс с методами read() и readline(), которые будут брать данные из буфера вместо файла, и передать его экземпляр функции в качестве аргумента. Используя же специальные методы (см. раздел 11.6.3), можно эмулировать поведение чисел, списков, словарей и даже полностью контролировать доступ к атрибутам.

В библиотеке стандартных модулей Вы найдете множество примеров классов, эмулирующих поведение строк, списков, словарей, файлов. Рекомендуем посмотреть на реализацию таких модулей, как UserString, UserList и UserDict, StringIO. Кроме того, в дистрибутив обычно входит несколько демонстрационных модулей, среди которых Вы найдете много интересных примеров, показывающих, как, например, можно реализовать рациональные числа.

9.7.1. Экземпляры классов в качестве исключений

Исключения в языке Python могут быть строками (для совместимости со старыми версиями; поддержка строк в качестве исключений может быть удалена в будущих версиях) или экземплярами классов. Использование механизма наследования позволяет создавать легко расширяемые иерархии исключений.

Чаще всего инструкция raise употребляется в следующем виде:

raise экземпляр_класса 

После ключевого слова except могут быть перечислены как строковые объекты, так и классы. Указанный класс считается "совместимым" с исключением, если исключение является экземпляром этого класса или класса, производного от него (но не наоборот — если в ветви except указан производный класс от того, экземпляром которого является исключение, то исключение не обрабатывается). Следующий пример выведет (именно в этом порядке) 'B', 'C', 'D':

class B:
    pass
class C(B):
    pass
class D(C):
    pass

for c in [B, C, D]:
    try:
        raise c()
    except D:
        print "D"
    except C:
        print "C"
    except B:
        print "B" 

Обратите внимание, что если расположить ветви except в обратном порядке, то Вы получите 'B', 'B', 'B' — срабатывает первая ветвь except, совместимая с исключением.

Если исключение-экземпляр не обрабатывается, выводится сообщение об ошибке: имя класса, двоеточие, пробел и строка, полученная применением встроенной функции str() к исключению-экземпляру.

>>> class MyError:
...     def __init__(self, value):
...         self.value = value
...     def __str__(self):
...         return ‘self.value‘
...
>>> raise MyError(1)
Traceback (innermost last):
  File "<stdin>", line 1
__main__.MyError: 1 

Язык имеет встроенный набор исключений, которые описаны в разделе 13. В качестве базового для всех исключений рекомендуется использовать класс Exception — это позволит полностью или частично избежать определения методов, необходимых для инициализации (метод __init__()) и вывода сообщения об ошибке (метод __str__(). В большинстве случаев определение нового исключения будет выглядеть совсем просто:

>>> class MyError(Exception): pass
...
>>> raise MyError('Oops!')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: Oops! 

9.7.2. Классы-помощники

Часто бывает полезно определить класс, помогающий выполнять какие-либо рутинные операции. Например, Вам часто необходимо перебирать параллельно элементы нескольких последовательностей, а поведение функции map() (см. раздел 5.2) Вас не устраивает или Вы работаете с длинными последовательностями и не хотите реально создавать последовательность пар (троек, и т. д.). Тогда Вы можете определить простой класс, создающий псевдопоследовательность:

class parallel:
    def __init__(self, *args):
        self.__args = args
    def __getitem__(self, item):
        return map(lambda s, i=item: s[i], self.__args) 

С таким классом-помощником задача параллельного перебора элементов сильно упрощается:

>>> seq1 = xrange(10)
>>> seq2 = [1, 2, 3, 5, 7]
>>> for x, y in parallel(seq1, seq2):
...     print x, y
...
0 1
1 2
2 3
3 5
4 7 

Как же наша псевдопоследовательность "узнает" о том, что элементы в одной из последовательностей закончились? Дело в том, что индикатором конца последовательности при использовании цикла for или средств функционального программирования в языке Python служит исключение IndexError. Исключение генерируется при первом использовании индекса, выходящего за пределы любой из последовательностей (в нашем примере seq1 и seq2). Так что нам достаточно не обрабатывать его и инструкция for будет считать, что закончилась наша псевдопоследовательность.

В версии 2.0 языка появилась новая встроенная функции zip(), предназначенная специально для параллельного перебора нескольких последовательностей. Ее поведение аналогично приведенному здесь классу parallel, с одним лишь отличием — функция zip() создает полноценный список, в который можно вносить изменения.

9.7.3. Множества

По своей природе, множества находятся скорее ближе к словарям, чем к спискам. Так же, как и словари обеспечивают уникальность ключей, множества гарантируют уникальность входящих в него элементов. С другой стороны, списки обеспечивают порядок следования элементов, что для множеств совсем необязательно. Таким образом, встроенный тип dictionary может служить хорошей базой для реализации множеств. Ниже приведено примерное определение класса, реализующего множество.

class set:

    def __init__(self, seq = ()):
        # Атрибут '_data' содержит словарь, ключами
        # которого являются элементы множества. Делать
        # атрибут частным ('__data') нежелательно, так
        # как в этом случае будет сложно работать с
        # производными от set классами.
        if isinstance(seq, set):
            # Если seq является экземпляром set или
            # производного от него класса, можно
            # воспользоваться "секретами" реализации
            self._data = seq._data.copy()
        else:
            # Иначе мы считаем seq произвольной
            # последовательностью
            self._data = {}
            for item in seq:
                self._data[item] = None

    def items(self):
        # Этот метод позволит перебирать элементы
        # множества:
        # for item in myset.items():
        # ...
        return self._data.keys()

    def tuple_key(self):
        # Сами множества изменяемые и не могут
        # использоваться в качестве ключа в словаре или
        # элемента в другом множестве. Для этого их
        # нужно преобразовать в кортеж.
        items = self._data.keys()
        # Сортируя элементы, мы можем гарантировать,
        # что порядок следования элементов в двух
        # множествах всегда будет одинаковым.
        items.sort()
        return tuple(items)

    def add(self, item):
        # Добавление элемента реализуется добавлением в
        # словарь пустой записи с соответствующим
        # ключем.
        self._data[item] = None

    def remove(self, item):
        if self._data.has_key(item):
            del self._data[item]
        else:
            # Множество не содержит такого элемента
            raise ValueError("item doesn't exist")

    def copy(self):
        return set(self)

    def __repr__(self):
        return 'set('+`self.items()`+')'

    def __len__(self):
        # Количество элементов в множестве, вызывается
        # функцией len(). Также определяет истинность
        # множества.
        return len(self._data)

    def __contains__(self, item):
        # Операторы 'in' и 'not in'.
        return self._data.has_key(item)

    def __cmp__(self, other):
        # Все операции сравнения.
        return cmp(self._data, other._data)

    def __or__(self, other):
        # Оператор '|' (объединение).
        res = set(self)
        res._data.update(other._data)
        return res

    def __ior__(self, other):
        # Оператор '|='.
        self._data.update(other._data)
        return self

    def __and__(self, other):
        # Оператор '&' (пересечение).
        # Будем перебирать элементы того множества,
        # которое содержит меньше элементов.
        if len(other._data) < len(self._data):
            self, other = other, self
        res = set()
        for item in self._data.keys():
            if other._data.has_key(item):
                res._data[item] = None
        return res

    def __iand__(self, other):
        # Оператор '&='.
        for item in self._data.keys():
            if not other._data.has_key(item):
                del self._data[item]
        return self

    def __sub__(self, other):
        # Оператор '-' (элементы, которые содержатся в
        # первом множестве и не содержатся во втором).
        res = set()
        for item in self._data.keys():
            if not other._data.has_key(item):
                res._data[item] = None
        return res

    def __isub__(self, other):
        # Оператор '-='.
        for item in other._data.keys():
            if self._data.has_key(item):
                del self._data[item]
        return self

    # Если мы реализуем вычитание, то для симметрии
    # следует также реализовать и сложение
    # (объединение).
    __add__ = __or__
    __iadd__ = __ior__

    def __xor__(self, other):
        # Оператор '^' (элементы, содержащиеся только в
        # одном из множеств).
        if len(other._data) < len(self._data):
            self, other = other, self
        res = set(other)
        for item in self._data.keys():
            if res._data.has_key(item):
                del res._data[item]
            else:
                res._data[item] = None
        return res

    def __ixor__(self, other):
        # Оператор '^='
        for item in other._data.keys():
            if self._data.has_key(item):
                del self._data[item]
            else:
                self._data[item] = None
        return self 

9.7.4. Контроль доступа к атрибутам

С помощью специальных методов __getattr__(), __setattr__() и __delattr__() Вы можете контролировать все обращения к атрибутам экземпляра. Приведем пример класса, реализующего собственные методы __getattr__() и __setattr__() и сохраняющего все атрибуты в частной переменной:

class VirtualAttributes:
    __vdict = None
    # Таким образом мы можем получить преобразованное
    # имя атрибута __vdict:
    __vdict_name = locals().keys()[0]

    def __init__(self):
        # мы не можем записать 'self.__vdict = {}',
        # т.к. при этом произойдет обращение к методу
        # __setattr__
        self.__dict__[self.__vdict_name] = {}

    def __getattr__(self, name):
        return self.__vdict[name]

    def __setattr__(self, name, value):
        self.__vdict[name] = value 

Перейти на главную страничку сайта (список статей, файлы для скачивания)

© 2007 http://www.script-coding.com При любом использовании материалов сайта обязательна ссылка на него как на источник информации, а также сохранение целостности и авторства материалов.

 
MyTetra Share v.0.52
Яндекс индекс цитирования