 | Уровень сложности: средний Сезар Отеро, независимый консультант, IBM
28.04.2009 Python 3 - это последняя версия мощного языка программирования общего назначения, автором которого является Гвидо ван Россум. Эта версия не имеет обратной совместимости с версиями 2.x, но зато в ней исправлены несколько синтаксических проблем предыдущих версий. Это вторая и последняя статья серии основана на предыдущей статье; в ней рассказывается об изменениях и новых возможностях в таких продвинутых аспектах Python, как абстрактные базовые классы, метаклассы и декораторы.
В предыдущей статье о Python 3—также известном как Python 3000 или Py3K— обсуждались некоторые простые изменения языка, нарушающие обратную совместимость, такие как новая функция print(), тип данных bytes и изменения в типе string. Здесь, во второй части, изучаются более сложные темы, большинство из которых также нарушает обратную совместимость с версиями 2.х: абстрактные базовые классы (abstract base classes или ABC),
метаклассы, аннотации функций и декораторы, поддержка целочисленных констант,
иерархия числовых типов и изменения в возбуждении и перехвате исключительных ситуаций.
Декораторы классов
В предыдущих версиях Python любые трансформации метода можно было делать только после определения этого метода. В длинных методах это отодвигало друг от друга такие важные компоненты, как определение метода и определение внешнего интерфейса (см. предложение о расширении Python (Python Enhancement Proposal, PEP) 318 в разделе Ресурсы). Следующий фрагмент кода иллюстрирует это требование:
Листинг 1. Трансформация методов в предыдущих версиях Python
def myMethod(self):
# что-то делаем
myMethod = transformMethod(myMethod)
|
Чтобы сделать код в подобных случаях более читаемым и уйти от необходимости повторного использования одного и того же имени метода несколько раз, в Python версии 2.4 были введены декораторы методов.
Декораторы - это методы, которые изменяют другие методы и возвращают либо метод, либо какой-нибудь другой вызываемый объект. Они обозначаются символом @ перед именем декоратора — как аннотации в языке Java™. В листинге 2 показан пример работы с декораторами.
Листинг 2. Декоратор метода
@transformMethod
def myMethod(self):
# что-то делаем
|
Декораторы – это чистый «синтаксический сахар», т.е., согласно Википедии, "добавление к синтаксису языка программирования, которое не придает новой функциональности, но делает его приятней в использовании". Типичное применение декораторов — это аннотации статических методов. Листинги 1 и 2 делают одно и то же, но листинг 2 легче читать.
Декораторы определяются точно так же, как другие методы:
def mod(method):
method.__name__ = "John"
return method
@mod
def modMe():
pass
print(modMe.__name__)
|
Больше того: в Python 3 имеются декораторы не только для методов, но и для классов. Поэтому теперь вместо такого кода:
class myClass:
pass
myClass = doSomethingOrNotWithClass(myClass)
|
можно писать так:
@doSomethingOrNotWithClass
class myClass:
pass
|
Метаклассы
Метаклассы - это классы, экземплярами которых являются другие классы. В Python 3 был сохранен встроенный тип данных metaclass, использовавшийся для создания других метаклассов или для динамического создания классов во время исполнения. В Python 3 по прежнему можно использовать синтаксис:
>>>aClass = type('className',
(object,),
{'magicMethod': lambda cls : print("blah blah")})
|
в котором в метод type() в качестве аргументов передаются строка с именем класса, кортеж наследуемых классов (который может быть пустым) и словарь, содержащий методы, добавляемые к этому классу (который также может быть пустым). Конечно, также вы можете наследовать от какого-либо типа и создавать свои собственные метаклассы:
class meta(type):
def __new__(cls, className, baseClasses, dictOfMethods):
return type.__new__(cls, className, baseClasses, dictOfMethods)
|
 |
Только именованные аргументы
В Python 3 был изменен механизм распределения параметров функции по "слотам параметров". Вы можете использовать звездочку (*) в списке параметров, чтобы показать, что функция не принимает переменное количество аргументов. В таком случае именованные параметры следуют за символом *— например,
def meth(*, arg1): pass. За более подробной информацией обращайтесь к документации Python
или смотрите документ PEP 3102 (см. ссылку в разделе
Ресурсы).
|
|
Замечание: Если два приведенных выше примера ничего вам не объяснили, я очень рекомендую почитать серию статей Дэвида Мерца и Мишеля Симионато о метаклассах. См. ссылку в разделе Ресурсы.
Обратите внимание, что теперь в определении класса после именованных аргументов может находиться список базовых классов — например, class Foo(*bases, **kwds): pass. Метакласс можно передавать в определение класса с помощью именованного параметра metaclass. Например:
>>>class aClass(baseClass1, baseClass2, metaclass = aMetaClass): pass
|
В старом синтаксисе метакласс приходилось передавать через встроенный атрибут __metaclass__:
class Test(object):
__metaclass__ = type
|
Также был добавлен новый атрибут —__prepare__. Этот атрибут используется для создания словаря пространства имен нового класса. Он вызывается, как показано в листинге 3, перед созданием самого класса.
Листинг 3. Простой метакласс, использующий атрибут __prepare__
def meth():
print("Calling method")
class MyMeta(type):
@classmethod
def __prepare__(cls, name, baseClasses):
return {'meth':meth}
def __new__(cls, name, baseClasses, classdict):
return type.__new__(cls, name, baseClasses, classdict)
class Test(metaclass = MyMeta):
def __init__(self):
pass
attr = 'an attribute'
t = Test()
print(t.attr)
|
В листинге 4 показан более интересный пример, скопированный из PEP 3115, в котором создается метакласс со списком имен его методов, причем с сохранением того порядка методов, в котором они были объявлены.
Листинг 4. Метакласс, сохраняющий порядок следования членов класса
# The custom dictionary
class member_table(dict):
def __init__(self):
self.member_names = []
def __setitem__(self, key, value):
# if the key is not already defined, add to the
# list of keys.
if key not in self:
self.member_names.append(key)
# Call superclass
dict.__setitem__(self, key, value)
# The metaclass
class OrderedClass(type):
# The prepare function
@classmethod
def __prepare__(metacls, name, bases): # No keywords in this case
return member_table()
# The metaclass invocation
def __new__(cls, name, bases, classdict):
# Note that we replace the classdict with a regular
# dict before passing it to the superclass, so that we
# don't continue to record member names after the class
# has been created.
result = type.__new__(cls, name, bases, dict(classdict))
result.member_names = classdict.member_names
return result
|
Изменениям, сделанным в метаклассах, есть несколько причин. Методы объектов хранятся в словаре, а, значит, неупорядоченно. Однако бывают случаи, в которых полезно сохранять порядок объявления членов класса (например, при создании C-структур). Это реализуется с помощью метаклассов, которые привлекаются к созданию объектов на ранней стадии, когда информация о порядке объявления членов еще доступна. Этот новый механизм позволит в будущем реализовать и другие интересные возможности, например, вставку символов в тело пространства имен класса на этапе его создания и опережающие ссылки. В PEP 3115 также говорится о том, что для изменения синтаксиса есть и эстетические причины, но это спорный вопрос, который сложно решить объективно. Смотрите ссылку на РЕР3115 в разделе Ресурсы.
Абстрактные базовые классы
Как я упоминал в предыдущей статье этой серии, абстрактные базовые классы (abstract base classes, ABC) - это классы, экземпляры которых нельзя создать. Программистам, знающим Java или C++ должна быть знакома эта концепция. В Python 3 добавлена новая инфраструктура — abc — которая облегчает работу с абстрактными базовыми классами.
Модуль abc содержит метакласс (ABCMeta) и декораторы (@abstractmethod и @abstractproperty). Если класс имеет декоратор
@abstractmethod или @abstractproperty, значит он является абстрактным базовым классом и его необходимо переопределить в классе-наследнике. Например, так написать можно:
>>>from abc import *
>>>class C(metaclass = ABCMeta): pass
>>>c = C()
|
а вот так уже нет:
>>>from abc import *
>>>class C(metaclass = ABCMeta):
... @abstractmethod
... def absMethod(self):
... pass
>>>c = C()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class C with abstract methods absMethod
|
Вместо этого надо делать так:
>>>class B(C):
... def absMethod(self):
... print("Now a concrete method")
>>>b = B()
>>>b.absMethod()
Now a concrete method
|
Класс ABCMeta переопределяет атрибуты класса __instancecheck__ и __subclasscheck__, предоставляя таким образом возможность для перегрузки встроенных функций isinstance() и issubclass(). Чтобы добавить виртуальный подкласс к абстрактному базовому классу, используйте метод register() класса ABCMeta. Вот простой пример:
>>>class TestABC(metaclass=ABCMeta): pass
>>>TestABC.register(list)
>>>TestABC.__instancecheck__([])
True
|
Этот код равнозначен использованию isinstance(list, TestABC). Вы могли заметить, что в Python 3 используются метод __instancecheck__ вместо __issubclass__ и метод
__subclasscheck__ вместо __issubclass__, что кажется более естественным. Причиной этому стало то, что изменение порядка следования аргументов с isinstance(subclass, superclass) скажем на superclass.__isinstance__(subclass) могло вызвать путаницу. Поэтому был выбран синтаксис superclass.__instancecheck__(subclass).
Для практики можно взять какой-нибудь абстрактный базовый класс из модуля collections и проверить, предоставляет ли он некоторый интерфейс:
>>>from collections import Iterable
>>>issubclass(list, Iterable)
True
|
В таблице 1 показаны абстрактные базовые классы инфраструктуры коллекций.
Таблица 1. Абстрактные базовые классы инфраструктуры коллекций.
| ABC | Наследует |
|---|
Container
| |
Hashable
| |
Iterable
| |
Iterator
|
Iterable
|
Sized
| |
Callable
| |
Sequence
|
Sized,
Iterable,
Container |
MutableSequence
|
Sequence
|
Set
|
Sized,
Iterable,
Container |
MutableSet
|
Set
|
Mapping
|
Sized,
Iterable,
Container |
MutableMapping
|
Mapping
|
MappingView
|
Sized
|
KeysView
|
MappingView,
Set |
ItemsView
|
MappingView,
Set |
ValuesView
|
MappingView
|
 |
Коллекции
Инфраструктура коллекций состоит из типов-контейнеров, двусторонних очередей и словаря по умолчанию (известного как defaultdict). Двусторонняя очередь поддерживает добавление и извлечение элементов как с начала, так и с конца очереди. Контейнер defaultdict - подкласс встроенного словаря, который (согласно документации Python 3) переопределяет один метод и добавляет одну переопределяемую переменную. В остальном, он работает как словарь. Инфраструктура коллекций также предоставляет фабрику объектов, называемую namedtuple().
|
|
Иерархии абстрактных базовых классов
В python 3 появилась поддержка иерархии абстрактных базовых классов, представляющих числа. Эти абстрактные базовые классы находятся в модуле numbers и включают в себя классы Number,
Complex, Real,
Rational и
Integral. На рисунке 1 показана иерархия чисел. Вы можете использовать эти метаклассы для создания своего собственного числового типа или еще одного числового абстрактного класса.
Рисунок 1. Иерархия числовых типов
 |
Числовая башня
Идея числовой иерархии в Python была навеяна числовой башней языка Scheme.
|
|
Новый модуль fractions реализует числовой абстрактный базовый класс Rational. Этот модуль поддерживает арифметику рациональных чисел. Запустив
dir(fractions.Fraction), можно увидеть, что у этого класса есть такие атрибуты, как imag,
real и
__complex__. Это связано с тем, что в соответствии с иерархией числовых типов Rationals наследуется от Reals, который наследуется от Complex.
Возбуждение и перехват исключительных ситуаций
В Python 3 был изменен вид блока except для борьбы с синтаксической неоднозначностью. Например если в Python 2.5 приведенную ниже конструкцию
try . . . except:
>>>try:
... x = float('not a number')
... except (ValueError, NameError):
... print "can't convert type"
|
по ошибке записать так:
>>> try:
... x = float('not a number')
... except ValueError, NameError:
... print "can't convert type"
|
, то исключение ValueError никогда не будет перехвачено, потому что интерпретатор будет перехватывать исключение ValueError и привязывать объект исключения к имени NameError. Это иллюстрируется в следующем примере:
>>> try:
... x = float('blah')
... except ValueError, NameError:
... print "NameError is ", NameError
...
NameError is invalid literal for float(): not a number
|
Поэтому, чтобы сделать синтаксис менее двусмысленным, запятую (,), применявшуюся при привязывании объекта исключения к имени, заменили ключевым словом as. Если вы хотите перехватывать несколько исключений, необходимо использовать скобки (()). В листинге 5 показаны два примера правильной работы с исключениями в Python
3.
Листинг 5. Обработка исключений в Python 3
# привязываем объект исключения ValueError к локальному имени ex
try:
x = float('blah')
except ValueError as ex:
print("value exception occurred ", ex)
# перехватываем одновременно две исключительные ситуации
try:
x = float('blah')
except (ValueError, NameError):
print("caught both types of exceptions")
|
Другое изменение в обработке исключений - это появление цепочек исключений, - либо явных, либо неявных. В листинге 6 показан пример неявной цепочки исключений.
Листинг 6. Неявная цепочка исключений в Python 3
def divide(a, b):
try:
print(a/b)
except Exception as exc:
def log(exc):
fid = open('logfile.txt') # забываем 'w'
print(exc, file=fid)
fid.close()
log(exc)
divide(1,0)
|
В методе divide()выполняется деление на ноль, в результате чего возбуждается исключение ZeroDivisionError. Но в блоке обработки исключения метод log() инструкцией print(exc, file=fid) пытается писать в файл, который не был открыт для записи. Python 3 возбуждает исключения так, как показано в листинге 7.
Листинг 7. Пример трассировки вызовов для неявной цепочки исключений
Traceback (most recent call last):
File "chainExceptionExample1.py", line 3, in divide
print(a/b)
ZeroDivisionError: int division or modulo by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "chainExceptionExample1.py", line 12, in <module>
divide(1,0)
File "chainExceptionExample1.py", line 10, in divide
log(exc)
File "chainExceptionExample1.py", line 7, in log
print(exc, file=fid)
File "/opt/python3.0/lib/python3.0/io.py", line 1492, in write
self.buffer.write(b)
File "/opt/python3.0/lib/python3.0/io.py", line 696, in write
self._unsupported("write")
File "/opt/python3.0/lib/python3.0/io.py", line 322, in _unsupported
(self.__class__.__name__, name))
io.UnsupportedOperation: BufferedReader.write() not supported
|
Заметьте, что оба исключения были обработаны. В предыдущих версиях Python исключение ZeroDivisionError было бы потеряно. Как это достигается? Теперь все объекты исключения имеют атрибут __context__. В нашем случае атрибут
__context__ возбужденного исключения IOError сохранил в себе информацию о предыдущем исключении ZeroDivisionError.
Кроме атрибута __context__, исключения имеют атрибут __cause__, который всегда инициализируется значением None. Назначение этого атрибута в том, чтобы предоставить способ для записи причины возбуждения исключительной ситуации. Задать значение атрибута __cause__ можно следующим образом:
>>> raise EXCEPTION from CAUSE
|
Это равносильно следующему коду:
>>>exception = EXCEPTION
>>>exception.__cause__ = CAUSE
>>>raise exception
|
но выглядит более элегантно. В листинге 8 показан пример явной цепочки исключений.
Листинг 8. Явная цепочка исключений в Python 3
class CustomError(Exception):
pass
try:
fid = open("aFile.txt") # опять забываем 'w'
print("blah blah blah", file=fid)
except IOError as exc:
raise CustomError('something went wrong') from exc
|
Также как и в предыдущем примере, функция print() возбуждает исключение, потому что файл не был открыт для записи. В листинге 9 показана трассировка вызовов.
Листинг 9. Трассировка вызовов исключения
Traceback (most recent call last):
File "chainExceptionExample2.py", line 5, in <module>
fid = open("aFile.txt")
File "/opt/python3.0/lib/python3.0/io.py", line 278, in __new__
return open(*args, **kwargs)
File "/opt/python3.0/lib/python3.0/io.py", line 222, in open
closefd)
File "/opt/python3.0/lib/python3.0/io.py", line 615, in __init__
_fileio._FileIO.__init__(self, name, mode, closefd)
IOError: [Errno 2] No such file or directory: 'aFile.txt'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "chainExceptionExample2.py", line 8, in <modulei>
raise CustomError('something went wrong') from exc
__main__.CustomError: something went wrong
|
Обратите внимание, что первая трассировка заканчивается строкой "The above exception was the direct cause
of the following exception", за которой следует еще одна трассировка - вызовов, приведших к возбуждению исключения CustomError, "something went
wrong."
И наконец, еще один атрибут, добавленный к объекту исключения - это атрибут __traceback__. Если перехваченное исключение не имеет атрибута __traceback__, то задается новая трассировка. Вот простой пример:
from traceback import format_tb
try:
1/0
except Exception as exc:
print(format_tb(exc.__traceback__)[0])
|
Обратите внимание, что format_tb возвращает список, состоящий только из одного исключения.
 |
Инструкция with
Начиная с Python 2.6, инструкция with является ключевым словом, которое больше не нужно импортировать из
__future__.
|
|
Целочисленные константы
Python поддерживает целочисленные строковые константы в различных системах счисления — восьмеричной, десятичной (конечно же!) и шестнадцатеричной, — а теперь к ним добавилась и двоичная. Изменилось представление констант в восьмеричной системе: теперь они начинаются префиксом 0o или 0O (т.е. ноль и за ним маленькая или большая буква "o") и затем значение константы. Например восьмеричное 13 или десятичное 11 представляется так:
Новые двоичные константы начинаются префиксом
0b или 0B (т.е. ноль и за ним маленькая или большая "b"). Десятичное 21 в двоичном виде представляется так:
Методы oct() и hex() были удалены.
Аннотации функций
Аннотации функцийАннотации функций ассоциируют некоторые выражения с частью функции, например параметрами, во время компиляции. Сами по себе аннотации функций не имеют смысла — они обрабатываются только в том случае, если это делает некая сторонняя библиотека. Целью аннотаций функций является стандартизация подхода к аннотированию параметров функции и возвращаемых значений. Синтаксис аннотаций функций выглядит так:
def methodName(param1: expression1, ..., paramN: expressionN)->ExpressionForReturnType:
...
|
Вот, например, аннотация параметров функции:
def execute(program:"name of program to be executed", error:"if something goes wrong"):
...
|
В следующем примере аннотируется возвращаемое значение функции. Это может быть полезно для проверки типа возвращаемого значения:
def getName() -> "isString":
...
|
Полную грамматику аннотаций функций можно найти в PEP 3107 (см. ссылку в разделе
Ресурсы).
Заключение
 |
Пасхальное яйцо
По моему мнению, лучший модуль, добавленный в Python 3 - это модуль
antigravity. Запустите Python и введите в строке интерпретатора
import antigravity. Вы не разочаруетесь.
|
|
Финальный релиз Python 3 вышел в начале декабря 2008 года. С этого времени я наблюдал за тем, как люди в некоторых блогах реагируют на проблему обратной совместимости версий. Хотя я не могу утверждать, что владею какой-либо информацией об официальном консенсусе, мнения в блогах, которые я читал, сильно поляризованы. Некоторым в сообществе Linux® разработчиков не нравится идея перехода на версию 3 ввиду большого количества кода, который нужно будет портировать. С другой стороны, многие Web-разработчики будут переходить на новую версию хотя бы из-за поддержки unicode-строк.
Прежде чем отказываться от новой версии, я рекомендую вам просмотреть документы PEP и почтовые рассылки разработки. В документах PEP разъясняется рациональность каждого конкретного изменения, связанные с ним преимущества, а также рассказывается о его реализации. Все эти изменения действительно хорошо обдумывались и долго обсуждались. Темы, раскрытые в этой серии, были представлены так, чтобы рядовой программист Python мог быстро почувствовать суть изменений, не просматривая все документы PEP.
Ресурсы Научиться
Получить продукты и технологии
Обсудить
Об авторе  | |  | Сезар Отеро - независимый консультант, специализирующийся на Java и Python. Сезар имеет ученую степень по электротехнике, а его вторая специализация - математика. |
Выскажите мнение об этой странице
|  |