div.main {margin-left: 20pt; margin-right: 20pt}
Язык программирования Python - что нового в 2.2 Автор: Олег
Бройтман, phd@phd.pp.ru Опубликовано:
13.03.2002 Оригинал: http://www.softerra.ru/review/program/16640/
Python 2.2
Объемы данной статьи и ее обзорная
направленность не позволяют мне рассмотреть Python 2.2 во всех деталях.
Наиболее сложные темы я оставил «за бортом»: конструктор __new__, метаклассы,
изменение порядка поиска метода при множественном наследовании, кооперативные
методы в родительских классах.
Унификация классов и типов
Наибольший шаг вперед в этой версии – унификация
типов (написанных на C) и классов, написанных на Питоне. Раньше типы и классы
были разными понятиями в Питоне, и написать наследника от типа, реализованного
на C, было нельзя. Например, от встроенного типа «список». Это решалось
делегированием – создавался класс, одним из атрибутов (полей) которого была
ссылка на экземпляр нужного встроенного типа.
Python 2.2 решил эту проблему. Теперь встроенные
типы стали классами, от которых можно наследоваться, и экземпляры наследованного
класса должны быть приняты везде, где мог встретиться экземпляр встроенного
типа.
Новые классы
В Python 2.2 появились «новые» классы.
«Старые» классы продолжают работать совершенно без изменений, но уже объявлено,
что рано или поздно они исчезнут из Питона, и все классы будут «новыми»
классами.
Особенности «новых» классов: в них можно
определить как методы класса, так и статические методы; с помощью нового
механизма доступа к атрибутам (properties) можно определить методы, которые
будут вызываться при чтении или установке значения отдельного атрибута; возможно
ограничить список атрибутов (слоты), которые хранятся в экземпляре.
Для того чтобы создать «новый» класс,
программист должен унаследоваться от одного из уже созданных «новых» классов,
например, встроенных «новых» классов. В Питоне появился корневой класс object,
от которого создается иерархия всех «новых» классов. Большинство встроенных
типов данных (числа, строки, списки, словари и даже файлы) теперь стали «новыми»
классами, наследующимися от object. Соответственно, встроенные функции int,
float, str и т.д. стали именами классов, порождающими соответствующие объекты.
Впрочем, их прежние действия по конверсии объектов из одного типа в другой
сохранились – int('12') по прежнему превращает строку в число, а str(12) –
наоборот.
Дескрипторы
Дескриптор – это атрибут «нового» класса,
определяющий, является ли атрибут методом или полем. С помощью дескрипторов
можно создавать статические методы класса и переопределять виды доступа к
атрибутам.
Теперь запись obj.x означает descriptor = obj.__class__.x
descriptor.__get__(obj)
а присваивание obj.x = v, наоборот descriptor.__set__(obj, v)
Атрибуты
Встроенный тип descriptor – тоже «новый» класс,
от которого можно наследоваться. Помимо стандартного класса descriptor, Python
предоставляет еще два типа дескрипторов – staticmethod и classmethod, которые
позволяют создавать статические методы и методы класса.
Слоты (ограниченный список атрибутов) и новый
тип доступа к атрибутам (properties) тоже обеспечиваются дескрипторами.
Properties – это то, что раньше делалось с
помощью переопределения __get(set)attr__. Недостатком __get(set)attr__ было то,
что раз определив этот метод, весь доступ (или все присваивания) ко всем
атрибутам шли через этот метод. Дескрипторы позволяют делать такой доступ
индивидуально для одного атрибута. Например: class C(object):
def __init__(self):
self.__x = 0
def getx(self):
return self.__x
def setx(self, x):
if x < 0: x = 0
self.__x = x
x = property(getx, setx)
В этом классе x – это атрибут, доступ к которому
автоматически вызовет getx, а присваивание – setx.
Метод __getattr__ сохранил свою функциональность
– он по-прежнему вызывается, только если атрибут не найден другим способом. В
дополнение к нему появился новый метод __getattribute__, который вызывается для
доступа ко всем атрибутам.
Хорошие примеры использования дескрипторов можно
посмотреть в архиве comp.lang.python: http://groups.google.com/groups?th=6c03822aa9563747 и http://groups.google.com/groups?th=58073cb3db9a4bfb.
Итераторы
Другим большим новшеством стал интерфейс
итераторов. До версии 2.2 для того чтобы позволить экземпляру класса быть
объектом цикла, классу переопределялся метод __getitem__. Зачастую этот метод
брал не элемент по индексу, а просто следующий по порядку, что плохо, потому что
не позволяло использовать __getitem__ по прямому назначению. Кроме того, это не
позволяло использовать этот объект в циклах одновременно в нескольки потоках.
Python 2.2 решает эту проблему, вводя новый слот __iter__. Этот метод
возвращает итератор – новый тип в версии 2.2.
Итератор – это объект, имеющий как минимум метод
next(). Ожидается, что этот метод при каждом вызове возвращает следующее
значение для итерации, а когда последовательность исчерпается – возбудит
исключение StopIteration.
Появилась новая встроенная функция iter, которая
возвращает итератор для встроенных и «старых» последовательностей (с
__getitem__) , или вызывает __iter__ объекта. Словари приобрели новые методы
iterkeys(), itervalues() и iteritems(), которые создают итераторы по ключам,
значениям или парам (ключ, значение). Эти итераторы гораздо эффективнее методов
keys(), values() и items(), потому что не строят копии списков. Разумеется,
изменять словарь во время обхода его итератором нельзя – возникнет
RuntimeError.
Цикл for больше не ожидает последовательность –
он ожидает итератор, и обрабатывает StopItertion. Для встроенных и «старых»
последовательностей будет вызвана функция iter для получения итератора по этой
последовательности.
Кроме того, у функции iter есть второй тип
вызова – iter(f, sentinel), который возвратит итератор, который будет
вызывать f() до тех пор, пока f() не возвратит значение sentinel.
Метод __iter__ класса может создать итератор
несколькими способами. Во-первых, он может вернуть self (или любой другой
фиксированный итератор). Это, разумеется, означает, что одновременно может быть
активным только один «экземпляр» такого итератора. Во-вторых, __iter__ может
вернуть новый экземпляр встроенного итератора или класса-итератора. И в-третьих,
он может вернуть генератор.
Генераторы
Часто сложным функциям, которые должны в цикле
генерировать значения, необходимо сохранять свое внутреннее состояние. Это
создает проблему вызова такой функции в цикле. Обычным решением является
выворачивание цикла наизнанку – цикл пишется внутри такой функции, и уже она
изнутри цикла вызывает callback (функцию обратного вызова), которую ей передает
пользователь. Многие языки, однако, решают эту проблему, вводя механизм
генераторов. Теперь к этим языкам присоединился и Питон. Генератор как раз и
позволяет вернуть цикл в нормальное состояние – цикл пишется пользователем, и в
цикле вызывается генератор.
Генератор – это «возобновляемая» функция,
хранящая состояние, то есть функция, которая помнит, в каком месте была прервана
ее работа, и при следующем вызове возобновляет работу с прерванного места с
сохраненным контекстом. Python 2.2 вводит новое ключевое слово yield,
которое используется вместо return для указания, в каком месте прервать (и после
какого возобновить) работу генератора. Появление нового ключевого слова требует
from __future__ import genertors. Как и return, yield возвращает
значение. Например: def fib(n):
a, b = 0, 1
i = 1
while i <= n:
yield b
a, b = b, a+b
i += 1
Этот генератор вызывается с целочисленным
параметром, и раз за разом генерирует числа Фибоначи, пока не сгенерирует n
таких чисел. Это похоже на встроенную функцию xrange, которая не создает весь
список чисел, а генерирует их одно за другим. Так и генераторы можно
использовать как «ленивые» списки, которые не предвычисляются и не размещаются в
памяти целиком, а чьи значения вычисляются одно за другим: for i in fib(5):...
или a, b, c = fib(3)
Кроме xrange, очевидным генератором является
генератор случайных чисел.
Генератор может быть и бесконечным: def fib1():
a, b = 0, 1
while 1:
yield b
a, b = b, a+b
Пользователь такого генератора сам решает,
сколько значений ему надо.
С точки зрения реализации функция fib не
является возобновляемой вовсе. Это порождающая функция, которая возвращает
итератор, то есть объект, имеющий метод next(): >>> gen = fib(3)
>>> gen
>>> gen.next()
1
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
File "", line 1, in ?
File "", line 2, in fib
StopIteration
Это позволяет, вызвав fib() несколько раз,
получить несколько независимых генераторов, которые могут быть активны
одновременно.
Генератор может быть рекурсивным. Например,
генератор, осуществляющий обход дерева в глубину: def inorder(t):
if t:
for x in inorder(t.left):
yield x
yield t.label
for x in inorder(t.right):
yield x
Генераторы и итераторы связаны между собой и
тем, что метод __iter__ может быть генератором. Например: class Fib:
def __iter__(self):
a, b = 0, 1
while 1:
yield b
a, b = b, a+b
fib = Fib()
for i in fib: print i
Прочие изменения
Проверка key in dict эквивалентна
dict.has_key(key).
Поддержка уникода теперь включает в себя UCS-4
(32-битные кодировка). Список кодеков расширен, и включает в себя кодеки, не
связанные с уникодом, например, кодек zlib: data = s.encode('zlib')
data.decode('zlib')
Сокеты могут быть скомпилированы с поддержкой
IPv6. Модуль smtplib поддерживает RFC 2487 («Secure SMTP over TLS»). В
библиотеку включен новый пакет email для унифицированной обработки почты –
разбора и генерации RFC2822 и MIME-сообщений. Новый модуль xmlrpclib реализует
клиентскую часть протокола XML-RPC. Модуль hmac реализует RFC 2104 («HMAC:
Keyed-Hashing for Message Authentication»).
В Python 2.2 появилось «истинное» деление.
В предыдущих версиях деление целого на целое давало целый результат с
округлением к минус бесконечности, а деление вещественных или комплексных чисел
давало нормальное деление без округления. В версии 2.2 оператор / будет прежним
делением (и будет оставаться им до версии 3.0, если только в модуле не сказать
from __future__ import division). «Истинное» же деление,
обозначаемое оператором //, всегда будет осуществлять округление к минус
бесконечности.
Целочисленного переполнения больше не будет.
Интерпретатор ловит такое переполнение, и автоматически переходит на вычисления
с использованием длинных целых бесконечной разрядности.
Во время компиляции на Linux и Solaris
автоматически распознается поддержка больших файлов (с 64-битным смещением).
Выражаю благодарность Денису Откидачу за
обсуждение и ценные замечания.
|