Куда приполз Питон?

Введение

17 апреля 2001 года вышел Python 2.1. Несмотря на то, что младший номер версии изменился всего лишь на единицу, количество дополнений в языке и библиотеках сравнимо с тем, что было внесено в 2.0 по сравнению с 1.6 (впрочем, основной причиной того, что после версии 1.6 вышла 2.0, а не 1.7, была необходимость четко отделить все последующие версии, от версий, выходившие под эгидой CNRI).

Одно из самых крупных изменений касается не самого языка, а процесса его разработки. Хотя (к счастью) Python сохранил "диктаторскую" модель разработки, когда автор языка имеет абсолютно решающее слово в принятии или непринятии любых предложений по расширению/изменению языка или библиотек, сама модель внесения, обсуждения и принятия таких предложений изменилась, став упорядоченной и формализованной. Она основана на PEP (Python Enhancement Proposals - предложения по расширению Python). PEP - документ, описывающий предлагаемое новое свойство языка (Standard Track PEP) или предоставляющий некоторую существенную информацию сообществу пользователей Python (Informational PEP). Примером последнего может служить PEP0001 - PEP Purpose and Guidelines, описывающий предназначение и механизм PEP. Категоризованный индекс всех PEP находится на http://python.sourceforge.net/peps/. Python 2.1 - первая версия, полностью разработанная на основе модели PEP.
Дополнения в языке

Статически вложенные пространства имен (nested/lexical scopes)

Это изменение касается способа, которым в Python разрешаются имена внутри функций. Язык Python имеет блочную структуру, сходную с языками алгольного типа (к которым, с некоторыми оговорками, относится и C). Базовой единицей (областью) программы являются модуль, определение класса или функция. Статически вложенные пространства имен определяют порядок поиска имени, встретившегося в блоке, как последовательный поиск, начиная с ближайшего лексически объемлющего блока и по направлению ко все более внешним.

В Python 2.0 (и ранее) любое имя, на которое появлялась ссылка в функции, последовательно искалось в трех пространствах имен:

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

Это правило поиска иногда называют LGB (Local, Global, Builtin). При этом, для вложенных функций (т.е, функций определенных внутри других функций), поиск имен производился точно таким же образом, т.е., если имя не было найдено непосредственно в пространстве имен вложенной функции, оно сразу начинало искаться в глобальном пространстве, а не в объемлющей функции. Это отличалось от логики других блочных языков, где поиск производится последовательно во всех объемлющих областях.

# Пример разрешения имени во вложенной функции
x = 'Глобальная область'
def external():
 x = 'Область функции "external()"'
 def nested():
 print "Внутри nested() x=='%s'" % x
 nested()

external()

Вышеприведенный пример выдает для Python версии 2.0 или 1.5:

Внутри nested() x=='Глобальная область'

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

* Эти правила неочевидны для новичков в Python, имеющих опыт программирования на других языках, например, Pascal (и даже для неновичков они продолжают оставаться странными);
* Применимость конструкции lambda ограничивается, поскольку единственным способом передать в lambda-функцию элемент контекста объемлющей функции оказывается неестественная и уродливая конструкция с применением параметров по умолчанию;

# Пример использования lambda без вложенного пространства имен
 def incrlist(lst, incr):
 # Внутри labmbda переменная incr не видна, поэтому передаем ее как умалчиваемый параметр
 map(lambda x, incr = incr: x + incr, lst)

* Невозможность рекурсивного вызова вложенной функции (по вполне очевидной причине - имя вложенной функции определено в контексте объемлющей и, как следствие, невидимо для нее самой!)

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

# Включить вложенные пространства имен
from __future__ import nested_scopes

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

Внутри nested() x=='Область функции "external()"'

а вышеприведенный пример с конструкцией lambda можно записать естественным образом:

def incrlist(lst, incr):
 map(lambda x: x + incr, lst)

Если nested_scopes выключено, Python использует старую семантику пространств имен, но выдает предупреждения обо всех участках кода, смысл которых изменится при применении новой семантики. Это дает возможность исправить такой код до выхода версии 2.2 (если вы, конечно, не предпочитаете навсегда остаться с версией 2.1 или 2.0).

Механизм введения новых свойств в язык

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

Директива __future__ - обычный оператор импорта, использующий зарезервированное имя модуля __future__:

from __future__ import feature

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

Для любого свойства, чреватого серьезной несовместмостью, задается переходный период (выраженный в терминах номеров версий), в течение которого оно является опциональным. По истечении этого периода оно становится обязательной частью языка (т.е. присутствует вне зависимости от утверждения __future__). Например, статически вложенные пространства имен - опциональное свойство в версии 2.1, но, начиная с версии 2.2 - обязательное.

# Включить вложенные пространства имен
from __future__ import nested_scopes

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

Механизм предупреждений

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

Начиная с версии 2.1, в Python также вводится механизм предупреждений. Его главное предназначение - сообщать программисту об использовании устаревших возможностей, а также возможностей, которые могут быть изменены или удалены в будущей версии языка (см. __future__). Например, в версии 2.1 не рекомендуется использовать модуль regex (собственно, он устарел еще в версии 2.0):

>>> import regex
__main__:1: DeprecationWarning: the regex module is deprecated; please use the re module

Предупреждения могут выдаваться как во время компиляции, так и во время выполнения. При этом в предупреждении присутствуют имя модуля и строка, откуда оно выдано. Функциональность выдачи предупреждения обеспечивает модуль warnings, причем пользовательские модули также могут использовать его для выдачи собственных предупреждений. Вводится также C API для выдачи предупреждений из расширений и программ со встроенным интерпретатором (PyErr_Warn())3.

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

* Warning
o
UserWarning базовая категория по умолчанию для warning.warn()
DeprecationWarning базовая категория для сообщений об отменяемых/устаревших свойствах языка
SyntaxWarning базовая категория для предупреждений об использовании сомнительного синтаксиса
RuntimeWarning базовая категория для предупреждений об использовании сомнительных возможностей времени выполнения (например, зависящих от реализации)

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

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

warnings.warn(message[, category[, stacklevel]])),

где message - собственно строка сообщения; category - категория сообщения, производная от Warning (по умолчанию - UserWarning); stacklevel - относительный уровень стека, для которого выдается сообщение.

# Пример выдачи предупреждения
import warnings
warnings.warn("Свойство X зависит от реализации", RuntimeWarning)

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

# В этом примере stacklevel = 2 приводит к тому, что в выданном предупреждении присутствуют 
# имя модуля и строка, откуда была _вызвана_ функция implementation_dependent(). Если бы этот
# параметр не был задан, в предупреждении присутствовали бы модуль и строка где _определена_
# функция implementation_dependent(), что сделало бы ее определение бессмысленным!
#
def implementation_dependent(feature):
 warnings.warn("Результат %s зависит от реализации!" % feature, RuntimeWarning, stacklevel = 2)

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

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

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

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

warnings.filterwarnings(action[, message[, category[, module[, lineno[, append]]]]]).

Ниже приведены примеры фильтров:

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

import warnings
warnings.filterwarnings(action = "ignore", category = DeprecationWarning)

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

import warnings
# Считать предупреждения о TERMIOS ошибками
warnings.filterwarnings(action = "error", category = DeprecationWarning, module = "TERMIOS")
# Подавлять предупреждения о regex
warnings.filterwarnings(action = "ignore", message = ".*regex module.*" category = DeprecationWarning)

Фильтры предупреждений могут также задаваться из командной строки Python с помошью параметра -W filter, где filter задается в виде action:message:category:module:lineno. Любой элемент фильтра может быть опущен:

python -W ignore::DeprecationWarning::

Слабые ссылки (weak references)

Слабые ссылки - новый тип данных, введенный в Python 2.1, позволяющий решить проблему циклических ссылок и динамического кеширования. Слабые ссылки позволяют ссылаться на объект, не увеличивая его счетчик ссылок.

Все обычные ссылки в Python - "жесткие" - владеющие объектом, на который ссылаются. Управление памятью и временем жизни объектов в Python осуществляется подсчетом ссылок. Это означает, что любой объект живет до тех пор, пока на него есть хотя бы одна ссылка (и уничтожается немедленно при удалении последней ссылки). Это очень удобно - с одной стороны, позволяет не заботиться об управлении памятью, с другой - обеспечивает детерминированное время жизни объекта (в отличие, например, от Java). Но такая модель не в состоянии решить ряд проблем:

* Циклические ссылки. Циклическая ссылка возникает, если объект A содержит прямую или косвенную (через несколько других объектов) ссылку на объект B, а объект B, в свою очередь, содержит прямую или косвенную ссылку на A. Это приводит к тому, что объекты, входящие в цикл, становятся "бессмертными" - их счетчики ссылок никогда не становятся равными 0, даже когда все ссылки извне удалены. Проблема состоит в том, что для многих задач циклические структуры естественны - стоит упомянуть лишь GUI, где родительское окно содержит ссылки на свои элементы, а те - ссылку на родительское окно. Частично проблема решается сборщиком мусора (модуль gc), но это решение неудачно - во-первых, время жизни оказывается недетерминированным, во-вторых - деструкторы для собранных объектов никогда не вызываются, что может быть фатальным для логики программы;

* Динамическое кеширование объектов. Представим себе функцию, возвращающую объект, создание которого достаточно дорого (и/или занимающий много памяти), при этом внутреннее состояние объекта может разделяться. В такой (достаточно частой) ситуации используется кэш объектов:

def create_n_cache(param, cache = {}):
 try:
 # Проверим, есть ли в кеше искомое значение
 retval = cache[param]
 except KeyError:
 cache[param] = retval = _really_create()
 return retval

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

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

Модуль weakref предоставляет два вида ссылок - собственно ссылку (reference) и делегатор (proxy). Ссылку на объект можно получить, вызвав конструктор

weakref.ref(object[, callback]).

Параметр callback может задавать функцию, которая будет вызвана перед удалением объекта, на который указывает ссылка.

# Создать слабую ссылку на объект
import weakref
from UserList import UserList
object = UserList([1, 2, 3, 4])
wr = weakref.ref(object)
# Чтобы получить реальный объект, надо разыменовать ссылку, просто вызвав ее как функцию
print "reference==%r object==%r" % (wr, wr())
# Удаляем реальный объект. Если объект, на который указывает слабая ссылка, удаляется,
# разыменование ссылки возвращает None
del object
print "reference==%r object==%r" % (wr, wr())

Пример выведет следующее:

reference==<weakref at 0x86475c; to 'instance' at 0x8627cc> object==[1, 2, 3, 4]
reference==<weakref at 86475c; dead> object==None

Делегатор (proxy reference) - слабая ссылка, ведущая себя (настолько, насколько у нее получается) как объект, на который она ссылается, т.е. доступ к атрибутам делегатора перенаправляется к атрибутам исходного объекта. Делегатор создается вызовом weakref.proxy(object[, callback]):

# Создать слабую ссылку-proxy на объект
import weakref
from UserList import UserList
object = UserList([1, 2, 3, 4])
proxy = weakref.proxy(object)
print "reference==%r" % proxy
# Делегатор ведет себя так же, как исходный объект
print "object[0]==%s, object[-1]==%s" % (proxy[0], proxy[-1])
# Удаляем реальный объект. Если объект, на который указывает делегатор, удаляется,
# делегатор указывает на None (и ведет себя соответственно)
del object
# Попытка индексировать None вызовет бурное возмущение интерпретатора
print "object[0]==%s, object[-1]==%s" % (proxy[0], proxy[-1])

Как и ожидалось, пример завершается с ошибкой:

reference==<weakref at 0086475C to instance at 008627CC>
object[0]==1, object[-1]==4
Traceback (most recent call last):
 File "testproxy.py", line 13, in ?
 print "object[0]==%s, object[-1]==%s" % (proxy[0], proxy[-1])
weakref.ReferenceError: weakly-referenced object no longer exists

Модуль weakref определяет класс, отлично подходящий для создания кешей - WeakValueDictionary. Это словарь, ссылки на значения в котором - слабые. При уничтожении объекта, на который есть ссылка в таком словаре, соответствующий вход в словаре (ключ и значение) автоматически удаляются. Функция create_n_cache() с использованием WeakValueDictionary могла бы выглядеть так:

import weakref
def create_n_cache(param, cache = weakref.WeakValueDictionary()):
 # Проверим, есть ли в кеше искомое значение
 retval = cache.get(param)
 if not retval:
 cache[param] = retval = _really_create()
 return retval

Новая модель приведения типов

Значительно модифицирован механизм приведения числовых типов для расширений на C.

Раньше требовалось, чтобы бинарные числовые операции всегда получали аргументы одинакового типа, и поэтому PyNumber_Coerce() всегда автоматически вызывалась перед вызовом соответствующей операции. Теперь модуль расширения может установить флаг Py_TPFLAGS_CHECKTYPES среди флагов структуры PyTypeObject чтобы показать, что данный тип поддерживает новую модель приведения. При этом PyNumber_Coerce() не будет вызываться вообще, а операнды будут передаваться числовым операциям, определенным в этом типе, "как есть". Тем самым ответственность за обработку операндов разных типов перекладывается непосредственно на операцию (естественно, первый операнд всегда будет иметь заданный тип, поскольку это self). В том случае, если операция не может обработать операнд заданного типа, она может вернуть указатель на специальный глобальный объект - Py_NotImplemented. В этом случае интерпретатор вызывает симметричную операцию другого операнда. Если и эта операция возвращает Py_NotImplemented, возбуждается исключение. Этот алгоритм можно представить следующим псевдокодом на Python:

# Псевдокод, описывающий выполнение бинарных арифметических операций для новых правил приведения типов
def binary_operation(o1, o2):
 result = o1.__binary_operation__(o2)
 if result is NotImplemented:
 result = o2.__binary_operation__(o1)
 if result is NotImplemented:
 raise TypeError, "операция не реализована для данных типов операндов"
 return result

Если у типа не установлен флаг Py_TPFLAGS_CHECKTYPES, к нему применяются старые правила приведения (с вызовом PyNumber_Coerce()), что позволяет обеспечить полную обратную совместимость.

* Новые правила приведения обладают существенными преимуществами:
* Предоставляют возможность определять индивидуальную логику обработки разных типов для разных операций;
* Не приводят к вызову PyNumber_Coerce() при каждой операции
* Позволяют элегантно обрабатывать ситуации, когда нет общего типа, к которому можно было бы привести разнородные операнды совершенно корректно определенной операции (пример - над типами DateTime и DateTimeDelta из расширения mxDateTime определены операции сложения и вычитания, но общего типа, к которому их можно привести, нет)

Расширенный механизм сравнения

Новый механизм сравнения позволяет определять отдельные функции для реализации разных операций сравнения (<, >, <=, >=, ==, !=) как для классов, так и для расширений на С. Кроме того, он позволяет определять операции сравнения, возвращающие значения, тип которых отличен от булевского.

Стандартный механизм сравнения Python предусматривает реализацию операций сравнения для класса при помощи одной функции - __cmp__ (или, соответственно, слота tp_compare для типа расширения). Эта функция должна возвращать -1, 0 или 1 (меньше, равно, больше соответственно) и возвращать булевское значение. Такой подход прост, но обладает несколькими серьезными недостатками:

* Невозможно определить функцию сравнения, возвращающую результат не булевского типа. Иногда такая операция имеет глубокий смысл - например, сравнение двух матриц должно возвращать матрицу результатов поэлементного сравнения;
* Существует множество типов, для которых определено равенство, но не отношение порядка. При определении функции сравнения для таких типов приходится вводить искусственное упорядочение, хотя правильно было бы возбуждать
исключение при попытке сравнить объекты как-нибудь кроме == или !=;
* Часто можно существенно оптимизировать операцию сравнения, зная, какое именно сравнение осуществляется, но функция __cmp__ не обладает такой информацией.

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

== __eq__
!= __ne__
< __lt__
> __gt__
<= __le__
>= __ge__

Функции расширенного сравнения могут возвращать значения любых типов.

Расширенное сравнение подчиняется следующим правилам:

* Python поддерживает рефлексивность операций сравнения. Интерпретатор считает, что операция A < B эквивалентна B > A, а A <= B эквивалентна B >= A. Если при сравнении A < B интерпретатор обнаруживает, что операция < для A не определена, он пытается выполнить B > A. То же верно и для <=. Это приводит к тому, что для класса (типа) часто достаточно определить операции ==, !=, <, <=;
* Python не поддерживает комплементарность операций сравнения, т.е. не считает, что A != B эквивалентно !(A == B), или A >= B эквивалентно !(A < B);
* Если используется краткая запись составного сравнения (X < Y < Z) и какая-нибудь из операций сравнения (например, X < Y) возвращает последовательность, Python не делает попытки интерпретировать результат как булевский и возбуждает исключение;

Атрибуты функций

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

В предыдущих версиях функции также имели набор атрибутов (__doc__ AKA func_doc, __name__ AKA func_name, func_code, func_defaults, func_globals), но этот набор фиксирован, только часть атрибутов могут быть записываемыми (__doc__, func_code, func_defaults), при этом только один из записываемых атрибутов обладает более-менее "произвольной" семантикой, позволяющей связать с функцией некоторую произвольную информацию - __doc__ . Это приводит к тому, что "строка документации" используется разными системами для множества разных целей, не имеющих к документации отношения. Например, Web-сервер Zope использует этот атрибут для описания "публикуемого" интерфейса; в системе разбора небольших языков SPARK в атрибут __doc__ помещаются правила разбора, etc. Такой подход неудачен по нескольким причинам:

* Разные способы использования несовместимы; в результате системы, использующие атрибут функции __doc__, не могут использоваться совместно (или, по крайней мере, имеют очень серьезное ограничение на совместный интерфейс);
* Такое применение делает невозможным то, для чего этот атрибут напрямую предназначен - документирование. Так, публикуемые функции в Zope не могут быть нормально отдокументированы;
* И, наконец, несвойственное использование некоторой возможности плохо просто потому, что делает код неочевидным.

Чтобы решить эти проблемы, к объекту функции добавлен стандартный атрибут __dict__ и возможность добавления произвольных пользовательских атрибутов. Синтаксис манипуляций с атрибутами функции такой же, как с атрибутами объекта класса, т.е. первое присваивание атрибуту создает его, del - удаляет из словаря, etc.

# Пример создает атрибуты published и return_type функции cvt()
def cvt(s):
 """Функция возвращает свой аргумент, преобразованный к плавающему и деленый пополам"""
 return float(s) / 2.0
s.published = 1
s.return_type = type(1.0)

В отличие от класса или объекта класса, атрибуту __dict__ функции можно явно присвоить значение, но оно должно быть либо реальным словарем (т.е., например, UserDict присвоить нельзя), либо None. В последнем случае все пользовательские атрибуты функции удаляются7.

# Пример создает атрибуты published и return_type функции cvt()
def cvt(s):
 """Функция возвращает свой аргумент, преобразованный к плавающему и деленый пополам"""
 return float(s) / 2.0
s.__dict__ = { "published":1, "return_type": type(1.0) }

Методам класса (как связанным (bound), так и свободным (unbound)) атрибуты непосредственно присвоить нельзя, но можно присвоить атрибуты функции, являющейся реализацией метода5:

class A:
 def a(): pass

# !!!! ОШИБКА !!!!
A.a.published = 1

# Правильно
A.a.im_func.published = 1

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

Обработчик исключений верхнего уровня

Добавлен слот sys.excepthook, позволяющий переопределить обработчик исключений верхнего уровня. Значением этого слота должна быть функция, принимающая тип исключения, его значение и traceback. Эта функция вызывается, если исключение не обработано ни одним блоком try...except, позволяя, таким образом, переопределить реакцию на необработанное исключение.

Обработчик вывода значений в интерактивном режиме интерпретатора

Добавлен слот sys.displayhook, позволяющий переопределить вывод значений в интерактивном режиме интерпретатора. Интерактивный режим выполняется в режиме REPL - Read-Evaluate-Print Loop. По умолчанию в этом режиме результат выполнения evaluate выводится с помощью функции repr(). sys.displayhook позволяет переопределить это поведение. Значением sys.displayhook должна быть функция (точнее, любой вызываемый объект). Эта функция вызывается в качестве print-стадии цикла REPL с параметром, являющимся результатом выполнения введенной команды (стадия evaluate). Код этой функции по умолчанию эквивалентен следующему:

import __builtin__
def displayhook(o):
 if o is None:
 return
 __builtin__._ = None
 print `o`
 __builtin__._ = o

Пример функции вывода:

# Эта функция заменяет стандартный sys.displayhook
# Очень красиво выводит сложные структуры данных (словари/списки/кортежи)
# Имеет смысл поместить ее в sitecustomize.py
import __builtin__, sys
from pprinter import pprint
def pretty_display(object):
 if object is None: return
 __builtin__._ = None
 pprint(object)
 __builtin__._ = object
sys.displayhook = pretty_display

Дополнения в механизме импорта

* Правила импорта для платформ, нечувствительных к регистру имен файлов (Windows, Mac OS), упрощены и сделаны единообразными. Раньше, если при выполнении конструкции import file Python находил в пути File.py, он немедленно возбуждал исключение ImportError, даже если в каком-нибудь из следующих каталогов в пути был файл file.py. Теперь он ищет вплоть до первого полного совпадения (т.е. в описанном случае File.py будет пропущен, file.py найден и загружен). Это делает правила поиска под Windows/Mac и Unix одинаковыми. Тот, кто хочет, чтобы на таких платформах Python вообще не различал регистр имен при импорте, может определить переменную среды PYTHONCASEOK (в описанном случае это привело бы к загрузке File.py). Под Unix регистр имен различается всегда;
* Модуль может явно определять набор имен, импортируемых из него конструкцией from ... import *. В модуле может быть определен список __all__, элементами которого должны быть имена, определенные в модуле. from ... import * будет импортировать только то, что перечислено в этом списке (правило, в соответствии с которым from ... import * игнорирует имена, начинающиеся с подчеркивания, остается);
* from M import N работает теперь даже в том случае, если sys.modules['M'] - не модуль. В общем случае, эта операция эквивалентна коду:

N = getattr(sys.modules['M'], 'name')

(естественно, если в sys.modules нет входа M, сначала происходит импорт модуля)
* Вызов PyImport() C API реализован через вызов PyImport_ImportModule(). В результате все вызовы импорта из программ на C/C++ учитывают перехват импорта (например, через imputil, etc.)

Новые и измененные модули

inspect - новый модуль, позволяющий получить массу полезной информации о выполняющейся программе на Python. Фактически, он предоставляет удобную обертку для разнообразного использования всевозможных "специальных" атрибутов объектов языка (func_*, co_*, im_*, tb_*, etc.), а также мощные средства интроспекции кода, описаний классов, etc.

pydoc - потрясающее средство оперативного просмотра и запросов к online-документации для Python. Наделяет интерактивный режим интерпретатора возможностями, сходными со справочными средствами Emacs, man и info. В состоянии извлекать документацию как из кода модулей, так и из файлов HTML. В качестве дополнительной возможности может быть запущен как сервер документации, позволяющий делать запросы через обычный Web-браузер, в том числе по сети; при этом браузер получает ответ в виде прекрасно форматированного HTML.

doctest и unittest - модули, предназначенные для поддержки тестирования других модулей. doctest обеспечивает оболочку для тестирования, выполняющую тесты, встроенные в __doc__ - строки функций, и сравнивающую реальный вывод с ожидаемым. unittest - много более мощный модуль, питоновская версия JUnit, оболочки для тестирования программ на Java (в свою очередь, являющийся Java-версией аналогичного пакета для Smalltalk). Поддерживает создание и прогон пакетов тестов. При этом пакеты тестов, в отличие от подхода doctest, являются отдельными сущностями.

difflib - предоставляет класс SequenceMatcher, позволяющий вычислять разницу между последовательностями, позволяющую восстановить одну последовательность из другой. Это позволяет писать программы создания и наложения патчей. Кроме того, используемый им алгоритм выдает результаты, которые выглядят "правильно" с точки зрения человеческого восприятия, что позволяет писать качественные программы построчного сравнения, наподобие diff. В качестве примера см. Tools/scripts/ndiff.py

Новая версия PyXML. Поддерживает Expat версий 1.2 и последующих. Для версий Expat 1.95 и выше поддерживает дополнительные обработчики. Поддерживает разбор файлов во всех кодировках, поддерживаемых Python. Множество иправлений в модулях sax, minidom, pulldom.

В модуле zipfile объекты класса ZipFile могут теперь конструироваться не только из имени файла, но и из любого "файлоподобного" объекта.

Модуль random теперь самодостаточен и предоставляет всю функциональность, ранее предоставляемую модулем whrandom (который теперь считается устаревшим).
Прочие изменения
continue может теперь присутствовать в блоке try, находящемся в теле цикла.

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

Значительно ускорен строчно-ориентированный ввод из текстовых файлов. Метод readline() стал работать приблизительно в 3 раза быстрее;

Добавлен новый файловый метод - xreadlines(). Он относится к readlines() так же, как xrange() к range(). Таким образом, самый быстрый, экономичный по памяти и элегантный метод построчного чтения текстового файла следующий:

for s in f.xreadlines(): 
 do_somenthing(s)

Специальный метод __rcmp__ больше не поддерживается. Вместо него вызывается __cmp__ с инвертированным порядком операндов;

repr(), примененная к строке, в которой присутствуют не-ASCII и контрольные символы, использует для их вывода шетнадцатеричное, а не восьмеричное представление (наконец-то!);

Отменена поддержка Windows 3.1, DOS и OS/2 и удалены соответствующие плаформо-специфические каталоги из библиотеки.

[1]. Обратите внимание, что конструкция import __future__ не является специальной - она просто импортирует вполне реальный модуль __future__.py, присутствующий в стандартной библиотеке Python (в котором, правда, присутствует определенная полезная информация об опциональных свойствах).

[2]. Может возникнуть вопрос - почему бы для поддержки семантики __future__ не предусмотреть специальное ключевое слово вместо своеобразной конструкции импорта? Но проблема в том, что любое новое ключевое слово - само по себе мощный источник несовместимости; возникает порочный круг. Кроме того,

[3]. PyErr_Warn() вызывает для обработки предупреждения код из модуля warnings, написанного на Python. Это следует иметь в виду, например, при создании "замороженных" приложений (т.е. монолитных файлов, где все необходимые модули на Python помещены в виде байт-кода в статические данные).

[4]. Это ограничение совершенно оправдано с точки зрения ясности кода. Поскольку атрибуты метода на самом деле устанавливаются для функции, его реализующей, разрешение присваивать атрибуты непосредственно через метод приводит к неочевидным результатам:

class C:
 def a(self): pass

c1 = C()
c2 = C()
c1.a.publish = 1
# c2.a.publish теперь тоже равно 1 !!!

Причем установка атрибутов для несвязанного метода тоже может привести к неоднозначности:

class D(C): pass
class E(C): pass

D.a.publish = 1
# E.a.publish теперь тоже равно 1 !!!

[5]. Для типов, определнных в расширениях C, также определен механизм расширенного сравнения. Функция расширенного сравнения задается в слоте tp_richcompare и принимает дополнительноый третий параметр - целое число, сигнализирующее о том, какая именно операция сравнение вызвана.

[6]. Для тех, кто не знает - имена функций сравнения взяты из соответствующих операторов языка FORTRAN (EQ - EQual, равно; NE - Not Equal, не равно, etc.).

[7]. Если атрибут __dict__ функции равен None, первое присваивание любому (пользовательскому) атрибуту функции автоматически создаст словарь и поместит в него атрибут.

30 ноября 2006 в 11:38
Вам понравился сайт или конкретно эта страница? Поделитесь ею со своими друзьями, нажав на одну из кнопок соцсетей слева или снизу. Желаете быть в курсе последних обновлений сайта — подпишитесь удобным для вас образом:
или поддержите нас, нажав на кнопку "Мне нравится"!
Материалы по теме
Обсуждения
{"url":"http://www.fastvps.ru/", "src":"/images/advbanners/fastvps.png", "alt":"Хостинг Fastvps.ru. Наш выбор!"}
Заработок