Автор: Олег Бройтман, phd@phd.pp.ru
Опубликовано: 13.3.2002
Объемы данной статьи и ее обзорная направленность не позволяют мне рассмотреть 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-битным смещением).
Выражаю благодарность Денису Откидачу за обсуждение и ценные замечания.
Телефон редакции: (095) 232-2261
E-mail редакции: inform@softerra.ru
По вопросам размещения рекламы обращаться к Алене Шагиной по телефону +7 (095) 232-2263 или электронной почте mailto:reclama@computerra.ru