Библиотека Интернет Индустрии I2R.ru |
|||
|
Куда приполз Питон?
Введение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 (и ранее) любое имя, на которое появлялась ссылка в функции, последовательно искалось в трех пространствах имен:
Это правило поиска иногда называют LGB (Local, Global, Builtin). При этом, для вложенных функций (т.е, функций определенных внутри других функций), поиск имен производился точно таким же образом, т.е., если имя не было найдено непосредственно в пространстве имен вложенной функции, оно сразу начинало искаться в глобальном пространстве, а не в объемлющей функции. Это отличалось от логики других блочных языков, где поиск производится последовательно во всех объемлющих областях. # Пример разрешения имени во вложенной функции Вышеприведенный пример выдает для Python версии 2.0 или 1.5: Внутри nested() x=='Глобальная область' Такая логика приводит к следующим неприятностям:
Введение в версии 2.1 сходных с другими блочными языками правил разрешения
имен во вложенных областях призвано решить эти проблемы. Но из-за того, что новые
правила могут вызвать проблемы с обратной совместимостью, вложенные пространства
имен введены как опциональное свойство ( # Включить вложенные пространства имен Таким образом, если nested_scopes включено, имя последовательно ищется во всех объемлющих контекстах (кроме контекста класса), пока не будет найдено. Контекст класса исключен, чтобы не нарушать единобразия правил доступа к атрибутам в Python, которые требуют, чтобы объект, к атрибуту которого нужен доступ, задавался всегда. В результате вышеприведенный пример выдаст: Внутри nested() x=='Область функции "external()"' а вышеприведенный пример с конструкцией Если nested_scopes выключено, Python использует старую семантику пространств имен, но выдает предупреждения обо всех участках кода, смысл которых изменится при применении новой семантики. Это дает возможность исправить такой код до выхода версии 2.2 (если вы, конечно, не предпочитаете навсегда остаться с версией 2.1 или 2.0). Механизм введения новых свойств в язык Разработчики языка Python традиционно стараются вносить изменения в язык таким
образом, чтобы не затронуть совместимость с программами на Python, написанными
для предыдущих версий. Но все же время от времени приходится вносить несовместимые
изменения - чаще всего в тех случаях, когда исправляется какой-нибудь застарелый
огрех дизайна языка, или (много реже!) если совместимость оказывается непреодолимым
препятствием для внесения в язык абсолютно необходимого свойства. Для того,
чтобы смягчить последствия таких изменений и облегчить переход на новые версии,
в язык было добавлено соглашение, позволяющее включать такие свойства опционально,
но лишь на переходный период в одну или несколько версий, после которого они становятся
обязательными. Для этого используется директива Директива from __future__ import feature где feature - имя включаемого свойства. Директива Для любого свойства, чреватого серьезной несовместмостью, задается переходный
период (выраженный в терминах номеров версий), в течение которого оно является
опциональным. По истечении этого периода оно становится обязательной частью языка
(т.е. присутствует вне зависимости от утверждения # Включить вложенные пространства имен В течение переходного периода, если свойство не включено, для конструкций,
могущих изменить смысл после его включения, выдается предупреждение.
После того, как переходный период для для некоторого свойства истекает, директива
Механизм предупрежденийПомимо механизма выдачи сообщений об ошибках, роль которого в Python обеспечивает обработка исключений, во многих языках существует также механизм выдачи предупреждений. Предупреждение, в отличие от исключения. не является фатальным, но сигнализирует о возможной ошибке программирования, неоднозначности либо об использовании устаревшего (устаревающего) свойства языка. Начиная с версии 2.1, в Python также вводится механизм предупреждений. Его
главное предназначение - сообщать программисту об использовании устаревших возможностей,
а также возможностей, которые могут быть изменены или удалены в будущей версии
языка (см. >>> import regex __main__:1: DeprecationWarning: the regex module is deprecated; please use the re module Предупреждения могут выдаваться как во время компиляции, так и во время выполнения.
При этом в предупреждении присутствуют имя модуля и строка, откуда оно выдано.
Функциональность выдачи предупреждения обеспечивает модуль Все предупреждения делятся на категории. Категории предупреждений составляют иерархию классов, наподобие исключений, с корневым классом Warning. Класс Warning, в свою очередь, производный от класса Exception, что позволяет преобразовывать предупреждения в исключения, если это необходимо (см. ниже описание фильтрации сообщений). На сегодняшний день стандартная иерархия категорий предупреждений следующая:
Как и в случае с обработкой исключений, при фильтрации базовая категория включает в себя все производные. Чтобы выдать предупреждение из програмы на Python, следует пользоваться функцией warnings.warn(message[, category[, stacklevel]])) ,где message - собственно строка сообщения; category - категория сообщения, производная от Warning (по умолчанию - UserWarning); stacklevel - относительный уровень стека, для которого выдается сообщение. # Пример выдачи предупреждения Параметр stacklevel очень полезен для создания общих функций для выдачи предупреждений. Например: # В этом примере stacklevel = 2 приводит к тому, что в выданном предупреждении
присутствуют Несмотря на то, что, как правило, программисту нужно видеть все предупреждения (для того, чтобы довести код до такого состояния, что они исчезнут), бывают случаи, когда их необходимо подавлять. Так, например, может случиться. если предупреждение выдается из "чужого" кода, или если сроки не позволяют исправить код немедленно, а промышленная версия не должна бомбардировать пользователя предупреждениями, не имеющими к нему отношения. При этом просто отключить выдачу всех предупреждений нельзя - могут оказаться подавленными необходимые. С другой стороны, некоторые предупреждения разумно интерпретировать как ошибки - например, если код модифицируется с целью очистки от многолетних наслоений, имеет смысл интерпретировать все DeprecationWarning как исключения - это позволит быстро выловить весь устаревший код. Для этого механизм предупреждений поддерживает фильтрацию. Фильтрация позволяет задать реакцию языка на предупреждения, отобранные по заданным критериям. Критериями могут выступать:
Фильтр может использовать один критерий или их произвольную комбинацию. Фильтры задаются функцией warnings.filterwarnings( action[, message[,
category[, module[, lineno[,
append]]]]]) . Ниже приведены примеры фильтров: Вы перешли с версии 1.5.2 на 2.1. Все превосходно продолжает работать, но ваша программа использует устаревшие модули regex и TERMIOS, поэтому выдает предупреждения при каждом запуске. Чтобы не пугать пользователей, вы добавляете в промышленную версию код, подавляющий все предупреждения об устаревших модулях: import warnings Тем временем, вы хотите вычистить все места, где используется TERMIOS,
но решаете, что переходить с regex на re слишком сложно, и добавляете в отладочную
версию следующий код:
import warnings Фильтры предупреждений могут также задаваться из командной строки Python с помошью параметра -W filter, где filter задается в виде action:message:category:module:lineno. Любой элемент фильтра может быть опущен: python -W ignore::DeprecationWarning:: Слабые ссылки (weak references)Слабые ссылки - новый тип данных, введенный в Python 2.1, позволяющий решить проблему циклических ссылок и динамического кеширования. Слабые ссылки позволяют ссылаться на объект, не увеличивая его счетчик ссылок. Все обычные ссылки в Python - "жесткие" - владеющие объектом, на который ссылаются. Управление памятью и временем жизни объектов в Python осуществляется подсчетом ссылок. Это означает, что любой объект живет до тех пор, пока на него есть хотя бы одна ссылка (и уничтожается немедленно при удалении последней ссылки). Это очень удобно - с одной стороны, позволяет не заботиться об управлении памятью, с другой - обеспечивает детерминированное время жизни объекта (в отличие, например, от Java). Но такая модель не в состоянии решить ряд проблем:
Механизм слабых ссылок позволяет решить вышеупомянутые (и многие другие) проблемы. Слабая ссылка - объект, указывающий на объект Python, но не увеличивающий его счетчик ссылок. Не на все объекты можно создать слабую ссылку; можно создавать слабые ссылки на объекты классов, функции (написанные на Python), методы (связанные и несвязанные). Модуль weakref предоставляет два вида ссылок - собственно ссылку (reference) и делегатор (proxy). Ссылку на объект можно получить, вызвав конструктор weakref.ref(object[, callback]) .Параметр callback может задавать функцию, которая будет вызвана перед удалением объекта, на который указывает ссылка. # Создать слабую ссылку на объект Пример выведет следующее: reference== reference== Делегатор (proxy reference) - слабая ссылка, ведущая себя (настолько, насколько
у нее получается) как объект, на который она ссылается, т.е. доступ к атрибутам
делегатора перенаправляется к атрибутам исходного объекта. Делегатор создается
вызовом # Создать слабую ссылку-proxy на объект Как и ожидалось, пример завершается с ошибкой: reference== 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 Новая модель приведения типовЗначительно модифицирован механизм приведения числовых типов для расширений на C. Раньше требовалось, чтобы бинарные числовые операции всегда получали аргументы одинакового типа, и поэтому PyNumber_Coerce() всегда автоматически вызывалась перед вызовом соответствующей операции. Теперь модуль расширения может установить флаг Py_TPFLAGS_CHECKTYPES среди флагов структуры PyTypeObject чтобы показать, что данный тип поддерживает новую модель приведения. При этом PyNumber_Coerce() не будет вызываться вообще, а операнды будут передаваться числовым операциям, определенным в этом типе, "как есть". Тем самым ответственность за обработку операндов разных типов перекладывается непосредственно на операцию (естественно, первый операнд всегда будет иметь заданный тип, поскольку это self). В том случае, если операция не может обработать операнд заданного типа, она может вернуть указатель на специальный глобальный объект - Py_NotImplemented. В этом случае интерпретатор вызывает симметричную операцию другого операнда. Если и эта операция возвращает Py_NotImplemented, возбуждается исключение. Этот алгоритм можно представить следующим псевдокодом на Python: # Псевдокод, описывающий выполнение бинарных арифметических операций для
новых правил приведения типов Если у типа не установлен флаг Py_TPFLAGS_CHECKTYPES, к нему применяются старые правила приведения (с вызовом PyNumber_Coerce()), что позволяет обеспечить полную обратную совместимость.
Расширенный механизм сравненияНовый механизм сравнения позволяет определять отдельные функции для реализации разных операций сравнения (<, >, <=, >=, ==, !=) как для классов, так и для расширений на С. Кроме того, он позволяет определять операции сравнения, возвращающие значения, тип которых отличен от булевского. Стандартный механизм сравнения Python предусматривает реализацию операций сравнения для класса при помощи одной функции - __cmp__ (или, соответственно, слота tp_compare для типа расширения). Эта функция должна возвращать -1, 0 или 1 (меньше, равно, больше соответственно) и возвращать булевское значение. Такой подход прост, но обладает несколькими серьезными недостатками:
Расширенный механизм сравнения позволяет преодолеть эти ограничения, вводя
возможность определения отдельных операций сравнения. В классе могут быть определены
следующие методы (все или любой набор)5,6:
Функции расширенного сравнения могут возвращать значения любых типов. Расширенное сравнение подчиняется следующим правилам:
Атрибуты функцийВ 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. Такой подход неудачен по нескольким причинам:
Чтобы решить эти проблемы, к объекту функции добавлен стандартный атрибут
__dict__ и возможность добавления произвольных пользовательских атрибутов.
Синтаксис манипуляций с атрибутами функции такой же, как с атрибутами объекта
класса, т.е. первое присваивание атрибуту создает его, # Пример создает атрибуты published и return_type функции cvt() В отличие от класса или объекта класса, атрибуту __dict__ функции
можно явно присвоить значение, но оно должно быть либо реальным словарем (т.е.,
например, UserDict присвоить нельзя), либо # Пример создает атрибуты published и return_type функции cvt() Методам класса (как связанным (bound), так и свободным (unbound)) атрибуты непосредственно присвоить нельзя, но можно присвоить атрибуты функции, являющейся реализацией метода5: class A: Следует заметить, что атрибуты можно присваивать только функциям, написанным на Python; функции расширений этого делать не позволяют. Обработчик исключений верхнего уровняДобавлен слот sys.excepthook, позволяющий переопределить обработчик
исключений верхнего уровня. Значением этого слота должна быть функция, принимающая
тип исключения, его значение и traceback. Эта функция вызывается, если исключение
не обработано ни одним блоком Обработчик вывода значений в интерактивном режиме интерпретатораДобавлен слот sys.displayhook, позволяющий переопределить вывод значений в интерактивном режиме интерпретатора. Интерактивный режим выполняется в режиме REPL - Read-Evaluate-Print Loop. По умолчанию в этом режиме результат выполнения evaluate выводится с помощью функции repr(). sys.displayhook позволяет переопределить это поведение. Значением sys.displayhook должна быть функция (точнее, любой вызываемый объект). Эта функция вызывается в качестве print-стадии цикла REPL с параметром, являющимся результатом выполнения введенной команды (стадия evaluate). Код этой функции по умолчанию эквивалентен следующему:
Пример функции вывода: # Эта функция заменяет стандартный sys.displayhook Дополнения в механизме импорта
Новые и измененные модули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(): Специальный метод __rcmp__ больше не поддерживается. Вместо него вызывается __cmp__ с инвертированным порядком операндов; repr(), примененная к строке, в которой присутствуют не-ASCII и контрольные символы, использует для их вывода шетнадцатеричное, а не восьмеричное представление (наконец-то!); Отменена поддержка Windows 3.1, DOS и OS/2 и удалены соответствующие плаформо-специфические каталоги из библиотеки. [1]. Обратите внимание, что конструкция [2]. Может возникнуть вопрос - почему бы для поддержки
семантики [3]. PyErr_Warn() вызывает для обработки предупреждения код из модуля warnings, написанного на Python. Это следует иметь в виду, например, при создании "замороженных" приложений (т.е. монолитных файлов, где все необходимые модули на Python помещены в виде байт-кода в статические данные). [4]. Это ограничение совершенно оправдано с точки зрения ясности кода. Поскольку атрибуты метода на самом деле устанавливаются для функции, его реализующей, разрешение присваивать атрибуты непосредственно через метод приводит к неочевидным результатам: class C: Причем установка атрибутов для несвязанного метода тоже может привести к неоднозначности: class D(C): pass [5]. Для типов, определнных в расширениях C, также определен механизм расширенного сравнения. Функция расширенного сравнения задается в слоте tp_richcompare и принимает дополнительноый третий параметр - целое число, сигнализирующее о том, какая именно операция сравнение вызвана. [6]. Для тех, кто не знает - имена функций сравнения взяты из соответствующих операторов языка FORTRAN (EQ - EQual, равно; NE - Not Equal, не равно, etc.). [7]. Если атрибут __dict__ функции равен
Яков Маркович, ведущий инженер-исследователь компании Intersoft Lab |
|
2000-2008 г. Все авторские права соблюдены. |
|