Python: сложные аспекты::Журнал СА 5.2009
www.samag.ru
     
Поиск  
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Сетевой агент
О журнале
Журнал «БИТ»
Информация для ВАК
Звезды «СА»
Подписка
Где купить
Авторам
Рекламодателям
Магазин
Архив номеров
Форум
Вакансии
Спроси юриста
Игры
Контакты
   
Слайд шоу  
Представляем работы Виктора Чумачева
Виктор Чумачев – известный московский художник, который сотрудничает с «Системным администратором» уже несколько лет. Именно его забавные и воздушные, как ИТ, иллюстрации украшают многие серьезные статьи в журнале. Работы Виктора Чумачева хорошо знакомы читателям в России («Комсомольская правда», «Известия», «Московские новости», Коммерсант и др.) и за рубежом (США, Германия). Каждый раз, получая новый рисунок Виктора, мы в редакции улыбаемся. А улыбка, как известно, смягчает душу. Поэтому смотрите на его рисунки – и пусть у вас будет хорошее настроение!
1001 и 1 книга  
19.03.2018г.
Просмотров: 3270
Комментарии: 0
Потоковая обработка данных

 Читать далее...

19.03.2018г.
Просмотров: 1504
Комментарии: 0
Релевантный поиск с использованием Elasticsearch и Solr

 Читать далее...

19.03.2018г.
Просмотров: 1786
Комментарии: 0
Конкурентное программирование на SCALA

 Читать далее...

19.03.2018г.
Просмотров: 853
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 1402
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

Дискуссии  
17.09.2014г.
Просмотров: 18004
Комментарии: 3
Красть или не красть? О пиратском ПО как о российском феномене

Тема контрафактного ПО и защиты авторских прав сегодня актуальна как никогда. Мы представляем ...

 Читать далее...

03.03.2014г.
Просмотров: 20716
Комментарии: 1
Жизнь под дамокловым мечом

Политические события как катализатор возникновения уязвимости Законодательная инициатива Государственной Думы и силовых структур, ...

 Читать далее...

23.01.2014г.
Просмотров: 29410
Комментарии: 3
ИТ-специалист будущего. Кто он?

Так уж устроен человек, что взгляд его обращен чаще всего в Будущее, ...

 Читать далее...


  Опросы

Друзья сайта  

Форум системных администраторов  

sysadmins.ru

 Python: сложные аспекты

Архив номеров / 2009 / Выпуск №5 (78) / Python: сложные аспекты

Рубрика: Программирование /  Программирование

Дмитрий Васильев

Python: сложные аспекты

Рассматриваем метаклассы, дескрипторы атрибутов и менеджеры контекста.

В этой статье мы рассмотрим некоторые достаточно сложные аспекты языка Python, а именно:

  •  метаклассы, позволяющие создавать классы с необычным поведением;
  •  дескрипторы атрибутов, предоставляющие наиболее гибкий контроль доступа к атрибутам объектов и классов;
  •  менеджеры контекста, объекты, позволяющие управлять поведением ключевого слова with.

Метаклассы

В общем случае, как и следует из названия, метаклассы – это классы классов. Таким образом, классы являются экземплярами метаклассов. Начиная с Python 2.2 стандартным метаклассом является type, который служит метаклассом для всех встроенных типов. Это можно увидеть на следующем примере:

>>> ().__class__

<type 'tuple'>

>>> ().__class__.__class__

<type 'type'>

Здесь классом для создания кортежа является tuple и соответственно классом для создания tuple является type. В этой статье мы рассматриваем только так называемые новые классы, то есть классы, которые наследуются от встроенного класса object. На данный момент «старые» (или «классические») классы должны представлять только исторический интерес, хотя они еще используются в некоторых проектах.

В Python при выполнении выражения, описывающего класс, интерпретатор сначала определяет соответствующий классу метакласс M и затем вызывает M(name, bases, dict) для создания класса. Это происходит после того как было обработано тело класса, где определены его методы и атрибуты. Аргументами при вызове метакласса являются:

  •  name – имя класса, строка, получаемая из выражения, описывающего класс;
  •  bases – кортеж базовых классов, получаемый в начале обработки выражения класса, или () если класс не определил базовых классов;
  •  dict – словарь с методами и атрибутами класса, которые были определены в теле класса;

Затем результат вызова M присваивается переменной с именем класса. Описание вызова метакласса для создания класса можно проиллюстрировать следующим примером:

>>> T = type("test", (object,), {"name": "Test"})

>>> T

<class '__main__.test'>

>>> T.name

'Test'

>>> t = T()

>>> t

<__main__.test object at 0x2863550>

>>> t.name

'Test'

После того как мы рассмотрели, как метакласс создает класс, остается понять, как выбирается метакласс.

Для выбора метакласса используются следующие шаги:

  •  Если определен dict['__metaclass__'] (то есть в теле класса был определен атрибут __metaclass__), то он используется.
  •  Иначе, если определен хотя бы один базовый класс, используется метакласс базового класса.
  •  Иначе будет использоваться глобальная переменная __metaclass__, если она определена.
  •  В противном случае будет использоваться метакласс для «классических» классов types.ClassType и соответственно будет создан «классический» класс.

Начиная с Python 3.0 метакласс можно указывать только как именованный параметр при определении класса, следующим образом:

>>> class Test(metaclass=type):

... pass

...

 Основные ограничения, связанные с метаклассами языка Python:

  •  Нельзя наследоваться одновременно от «классического» и «нового» классов. В этом случае возможности «новых» классов, описанные в этой статье, работать не будут.
  •  Метакласс класса должен соответствовать метаклассу базового класса или быть его потомком.

Примеры метаклассов

После описания работы метаклассов обратимся к примерам собственных реализаций. Как уже было рассмотрено ранее, класс создается при вызове метакласса следующим образом: M(name, bases, dict). Более детально при создании классов (можно провести аналогию с созданием объектов класса) вызываются методы метакласса __new__() и затем __init__(), как в следующей последовательности строк:

cls = M.__new__(M, name, bases, dict)

assert cls.__class__ is M

M.__init__(cls, name, bases, dict)

Напишем наш первый метакласс, чтобы рассмотреть последовательность вызова методов при создании класса и объекта:

class MetaTest(type):

 

def __new__(cls, name, bases, dict):

klass = super(MetaTest, cls).__new__(cls, name, bases, dict)

print "__new__(%r, %r, %r) -> %r" % (name, bases, dict, klass)

return klass

 

def __init__(cls, name, bases, dict):

super(MetaTest, cls).__init__(name, bases, dict)

print "__init__(%r, %r, %r)" % (name, bases, dict)

 

def __call__(cls, *args, **kwargs):

obj = super(MetaTest, cls).__call__(*args, **kwargs)

print "__call__(%r, %r) -> %r" % (args, kwargs, obj)

return obj

Здесь мы просто выводим информацию о вызове методов __new__(), __init__() и __call__(). Вот как это работает:

>>> from meta import MetaTest

>>> class Test(object):

... __metaclass__ = MetaTest

...

 

__new__('Test', (<type 'object'>,), {'__module__': '__main__', '__metaclass__': <class 'meta.MetaTest'>}) -> <class '__main__.Test'>

__init__('Test', (<type 'object'>,), {'__module__': '__main__', '__metaclass__': <class 'meta.MetaTest'>})

 >>> test = Test()

__call__((), {}) -> <__main__.Test object at 0x7f62e95ca650>

Обратите внимание на атрибут __metaclass__ в теле класса, как уже было описано выше, это один из способов присвоения метакласса классу.

Таким образом, мы видим последовательность вызова методов метакласса:

  •  __new__() – вызывается для создания класса;
  •  __init__() – для инициализации класса;
  •  __call__() – вызывается при создании объектов класса.

Нужно также отметить, что атрибуты и методы, определенные в метаклассе, являются статическими, то есть доступны только на уровне класса, но не на уровне объектов класса:

>>> class MetaTest(type):

... def test(cls):

... print "test()"

...

>>> class Test(object):

... __metaclass__ = MetaTest

...

>>> Test.test()

test()

>>> Test().test()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AttributeError: 'Test' object has no attribute 'test'

Рассмотрим примеры более полезных метаклассов. Метакласс AutoSuper добавляет приватный атрибут __super для доступа к атрибутам и методам базовых классов:

class AutoSuper(type):

def __init__(cls, name, bases, dict):

super(AutoSuper, cls).__init__(name, bases, dict)

setattr(cls, "_%s__super" % name, super(cls))

Теперь он может быть использован так:

>>> from super import AutoSuper

>>> class A(object):

... __metaclass__ = AutoSuper

... def method(self):

... return "A"

...

>>> class B(A):

... def method(self):

... return "B" + self.__super.method()

...

>>> B().method()

'BA'

Таким образом, при работе с классом и его подклассами можно везде заменить вызов встроенной функции super на обращение к приватному атрибуту __super. Это позволяет контролировать доступ к базовым классам на уровне класса или даже объекта. Плюс к этому уменьшается вероятность ошибок, связанных с опечатками, и в случае изменения имени класса нет необходимости изменять имя в нескольких местах.

Следующий пример представляет собой метакласс, устанавливающий атрибуты для объектов, создаваемых классом без необходимости определения конструктора класса:

class AttrInit(type):

def __call__(cls, **kwargs):

obj = super(AttrInit, cls).__call__()

for name, value in kwargs.items():

setattr(obj, name, value)

return obj

Этот метакласс может быть использован так:

>>> from attr import AttrInit

>>> class Message(object):

... __metaclass__ = AttrInit

...

>>> class ResultRow(object):

... __metaclass__ = AttrInit

...

>>> msg = Message(type='text', text='text body')

>>> msg.type

'text'

>>> msg.text

'text body'

>>> row = ResultRow(id=1, name='John')

>>> row.id

1

>>> row.name

'John'

Такой метакласс может быть полезен для создания классов, объекты которых служат в основном как хранилище атрибутов. Например, классов, описывающих передаваемые по сети пакеты данных, или строки результата запроса к базе данных, к полям которых удобнее обращаться как к атрибутам.

Таким образом, метаклассы позволяют создавать классы с достаточно необычным поведением, но в то же время вряд ли стоит их использовать в каждой программе.

Дескрипторы атрибутов

Дескрипторы атрибутов (далее просто дескрипторы) описывают протокол доступа к атрибутам объекта или класса. В общем случае дескрипторы – это объекты, в которых определен один из методов: __get__(), __set__() или __delete__(). Среди уже определенных в Python дескрипторов можно назвать следующие: property, classmethod и staticmethod. Рассмотрим интерфейс дескрипторов на примере:

class TestDescriptor(object):

 

def __get__(self, obj, type=None):

print "__get__(%r, %r)" % (obj, type)

return "value"

 

def __set__(self, obj, value):

print "__set__(%r, %r)" % (obj, value)

 

def __delete__(self, obj):

print "__delete__(%r)" % obj

При доступе к атрибуту методы этого дескриптора вызываются следующим образом:

>>> from desc import TestDescriptor

>>> class Test(object):

... attribute = TestDescriptor()

...

>>> Test.attribute

__get__(None, <class '__main__.Test'>)

'value'

>>> t = Test()

>>> t.attribute

__get__(<__main__.Test object at 0x7f757d88d510>,

<class '__main__.Test'>)

'value'

>>> t.attribute = "new value"

__set__(<__main__.Test object at 0x7f757d88d510>, 'new value')

>>> del t.attribute

__delete__(<__main__.Test object at 0x7f757d88d510>)

Здесь мы видим, что при доступе к атрибуту attribute, являющемуся дескриптором, на самом деле вызываются методы дескриптора. Надо также заметить, что дескрипторы вызываются из метода __getattribute__() (который в свою очередь имеет смысл только для «новых» классов), определенного в классе object, и его переопределение может отменить автоматическое обращение к дескрипторам при доступе к атрибутам. Также следует знать, что если дескриптор определяет только метод __get__(), то атрибут, за которым стоит такой дескриптор, может быть переопределен присваиванием другого значения атрибута объекту. Если же дополнительно определен метод __set__(), то атрибут объекта не может быть переопределен таким образом.

Примеры дескрипторов

Для примера реализуем аналоги встроенных дескрипторов property, classmethod и staticmethod в Python.

Дескриптор, имеющий поведение property, может быть представлен следующим классом:

class Property(object):

 

def __init__(self, fget=None, fset=None, fdel=None, doc=None):

self.fget = fget

self.fset = fset

self.fdel = fdel

self.__doc__ = doc

 

def __get__(self, obj, type=None):

if obj is None:

return self

if self.fget is None:

raise AttributeError("unreadable attribute")

return self.fget(obj)

 

def __set__(self, obj, value):

if self.fset is None:

raise AttributeError("can't set attribute")

self.fset(obj, value)

 

def __delete__(self, obj):

if self.fdel is None:

raise AttributeError("can't delete attribute")

self.fdel(obj)

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

Поведение classmethod можно эмулировать следующим образом:

class ClassMethod(object):

 

def __init__(self, f):

self.f = f

 

def __get__(self, obj, klass=None):

if klass is None:

klass = type(obj)

def newfunc(*args, **kwargs):

return self.f(klass, *args, **kwargs)

return newfunc

Здесь первый атрибут при вызове метода заменяется классом объекта.

И наконец staticmethod может быть представлен так:

class StaticMethod(object):

 

def __init__(self, f):

self.f = f

 

def __get__(self, obj, type=None):

return self.f

Менеджеры контекста

Менеджеры контекста – это механизм, стоящий за ключевым словом with. Ключевое слово with появилось еще в Python 2.5, но к нему можно было получить доступ только через __future__ импорт: from __future__ import with_statement. Начиная с Python 2.6 ключевое слово with может быть полностью доступно без импортирования из __future__.

Ключевое слово with определяет блоки кода, которые прежде использовали try/finally. Для уверенности в выполнении кода его заключали в блок finally. With имеет следующую форму:

with выражение [as переменная]:

блок with

Здесь «выражение» должно вернуть объект, предоставляющий протокол менеджера контекста. Для некоторых встроенных объектов уже определены менеджеры контекста. Например, такой менеджер определен для файлов, чтобы быть уверенным, что файл будет закрыт при выходе из блока:

with open('file.txt', 'rb') as f:

for line in f:

print line

В простейшем случае такая конструкция эквивалентна следующей:

f = open('file.txt', 'rb')

try:

for line in f:

print line

finally:

f.close()

Протокол менеджера контекста содержит всего два метода: __enter__() и __exit__(). В начале выполнения блока кода вызывается метод __enter__(), который должен вернуть объект, присваиваемый переменной, после чего выполняется блок кода. Если блок кода выкидывает исключение, то вызывается метод __enter__() с информацией об исключении. Если выполнение блока завершилось успешно, вся информация об исключении равна None. Пример работы:

class TestContext(object):

def __init__(self, ignore_error=False):

self.ignore_error = ignore_error

def __enter__(self):

print "__enter__()"

return self

def execute(self, error=False):

print "execute()"

if error:

raise Exception("error")

 

def __exit__(self, exc_type, exc_val, exc_tb):

print "__exit__(%r, %r, %r)" % (exc_type, exc_val, exc_tb)

return self.ignore_error

Кроме методов, предоставляющих протокол менеджера контекста, здесь также определен вспомогательный метод execute(), который будет представлять код внутри блока:

>>> from context import TestContext

>>> with TestContext() as context:

... context.execute()

...

__enter__()

execute()

__exit__(None, None, None)

>>> with TestContext() as context:

... context.execute(error=True)

...

 

__enter__()

execute()

__exit__(<type 'exceptions.Exception'>, Exception('error',), <traceback object at 0x7f6da88bffc8>)

Traceback (most recent call last):

File "<stdin>", line 2, in <module>

File "context.py", line 10, in execute

raise Exception("error")

Exception: error

 

В случае, если метод __exit__() возвращает «ложь», исключение будет выкинуто за пределы блока. При этом метод __exit__() никогда не должен сам выкидывать полученное исключение, а управлять этим только через возвращаемое значение:

>>> with TestContext(ignore_error=True) as context:

... context.execute(error=True)

...

 

__enter__()

execute()

__exit__(<type 'exceptions.Exception'>, Exception('error',), <traceback object at 0x7fa35497a200>)

 

Модуль contextlib

Новый модуль contextlib (появившийся в Python 2.5) предоставляет функции и декораторы, упрощающие создание и работу с менеджерами контекста. На данный момент модуль предоставляет три функции:

Сontextmanager(функция) – декоратор, упрощающий создание менеджеров контекста. Вместо создания класса, предоставляющего интерфейс менеджера контекста, можно использовать декоратор с функцией-генератором, например:

from contextlib import contextmanager

 

@contextmanager

def test():

print "__enter__()"

try:

yield "execute()"

finally:

print "__exit__()"

Теперь мы можем использовать test() как менеджер контекста. Результат yield будет присвоен переменной:

>>> from context import test

>>> with test() as body:

... print body

...

 

__enter__()

execute()

__exit__()

 

Nested(менеджер1[, менеджер2[,...]]) – функция, комбинирующая несколько менеджеров контекста в один. Следующий код:

from contextlib import nested

 

with nested(A(), B(), C()) as (X, Y, Z):

body()

будет эквивалентен коду:

m1, m2, m3 = A(), B(), C()

with m1 as X:

with m2 as Y:

with m3 as Z:

body()

Closing(объект) – функция, возвращающая менеджер контекста, который закрывает объект по завершении блока. Например:

from contextlib import closing

from urllib import urlopen

 

with closing(urlopen('http://www.python.org')) as page:

for line in page:

print line

В этом примере в конце блока будет вызван метод page.close().

Заключение

В этой статье были рассмотрены достаточно сложные аспекты использования Python, которые вы скорее всего не будете использовать в каждой программе. Но в то же время описанный инструментарий может значительно упростить и сделать более гибким сложный код, что позволит взглянуть по-новому на все разрабатываемое приложение в целом. Плюс знание этих инструментов и описанные особенности внутренней работы интерпретатора, должны поднять на новую ступень ваш уровень как разработчика ПО.

Подробнее про особенности «новых» классов можно прочитать по ссылке: http://www.python.org/doc/newstyle.

Приложение

«Новые» классы

Новая система типов и классов (так называемые новые классы) была добавлена в Python 2.2 для унификации классов и типов. Основная причина их появления – это предоставление унифицированной объектной модели с полноценной моделью метаклассов. «Новые» классы также предоставляют следующие возможности:

  • Наследование от встроенных типов, например списков (list) и даже целых (int), которые должны работать везде, где требуется оригинальный тип.
  • Создание статических методов и методов класса.
  • Вызов методов при доступе к атрибутам. Эта функциональность реализуется с помощью дескрипторов атрибутов.

В Python 2 простейший способ создать «новый» класс – это наследовать его от object:

class Test(object):

pass

Начиная с Python 3.0 «старые» классы были удалены и по умолчанию используются «новые» классы (которые уже нет необходимости называть «новыми»).


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru