Автор: Олег Бройтман http://phd.pp.ru/ phd@phd.pp.ru
Это седьмая статья данного цикла лекций, и третья, рассказывающая, что нового в последних версиях Питона.
Перепечатка. Оригинал статьи опубликован по адресу http://www.softerra.ru/freeos/16640/
Выражаю благодарность Денису Откидачу за обсуждение и ценные замечания.
Объемы данной статьи и ее обзорная направленность не позволяют мне рассмотреть 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 generators. Как и 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Это позволяет, вызвав fib() несколько раз, получить несколько независимых генераторов, которые могут быть активны одновременно.>>> 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
Генератор может быть рекурсивным. Например, генератор, осуществляющий обход дерева в глубину:
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 и Solris автоматически распознается поддержка больших файлов (с 64-битным смещением).