Гостевая
Форум
Разделы
Главная страница
Js скрипты
Php скрипты
Html шаблоны
Книги по Web дизайну
Статьи


Главная страница статей --> Хитрости при программировании php, заметки по базам данных

Метаклассы вчера и сегодня

Источник: realcoding.net

Что такое метаклассы?


Если при истанциировании классов вы получаете экземпляр этого класса, то при инстанциировании метакласса вы получаете класс. То есть, классы можно рассматривать как экземпляры метаклассов. Метаклассы совмещают в себе две функции. Во-первых, они являются конструкторами классов, во-вторых, существует возможность определять производые метаклассы, дополняя и/или переопределяя функциональность базового. Более развернутое объяснение, что же такое метаклассы, вы можете найти в письме Владимира Марангозова.

Вчера (Python 1.5–2.1)


Python, начиная с версии 1.5, также позволяет создавать метаклассы. В качестве псевдоклассов могут выступать экземпляры классов, метод __init__ которых имеет строго определенный интерфейс:

class MetaClass:
def __init__(self, name, bases, dict):
# ...

Однако, чтобы реазизовать на практике эту возможность, прийдется потрудиться и обойти множество острых углов. Пожалеем вас и не будем описывать все те ухищрения, на которые нужно идти для получения нужного результата, об этом вы можете прочитать в статье Гвидо ван Россума Metaclasses in Python 1.5 или сразу взглянуть на результат — пример реализации метаклассов вы найдете в каталоге Demo/metaclasses/ пакета с исходными кодами Python — скорее всего, это сразу отобъет желание вникать в подробности. Следует отметить, скорость работы классов, созданных на базе экземплров таких метаклассов, значительно уступает скорости работы обычных классов.

Попробуем рассмотреть альтернативные реализации. В большинстве случаев от метакласса тебуется только одно: служить в качестве конструктора классов. В этом случае вы можете создать исходный код класса по шаблону и воспользоваться инструкцией exec:

CLASS_TEMPLATE =
class %(name)s(%(bases)s):
# шаблон реализация класса

params = {
name: name,
bases: string.join(bases, , ),
# другие подстановки для шаблона
}
exec CLASS_TEMPLATE % params

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

Рассмотрим другой вариант, основанный на той же особенности языка:

def createClass(name, bases=(), dict={}):

class
NewClass:
# определения класса

NewClass.__name__ = name
NewClass
.__bases__ = bases
NewClass
.__dict__.update(dict)

return
NewClass

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

Пусть у нас будет класс, на который возложена обязанность вытаскивать список документов из базы данных. SQL запрос констуируется на основе данных (о таблицах, полях, условии и др.), возвращаемых методом queryParts:

class Documents(SomeBaseClass):

# ...

def queryParts(self):
# ...
return tables, fields, condition

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

class DocumentsInSection(Documents):

def queryParts(self):
tables, fields, condition = Documents.queryParts(self)
tables = %s NATURAL JOIN %s % (tables, documents_sections)
condition = %s AND section=%s % (condition, self.section)
return
tables, fields, condition

А теперь представьте, что таких сущностей "привязанных" к разделам достаточно много, и в каждом случае нужно сделать аналогичное переопределение. Самое время воспользоваться конструктором классов.

def createInSection(base_class, link_table):

class
InSection:
def queryParts(self):
tables, fields, condition = self.base_class.queryParts(self)
tables = %s NATURAL JOIN %s % (tables, self.link_table)
condition = %s AND section=%s % (condition, self.section)
return
tables, fields, condition

InSection
.base_class = base_class
InSection
.__bases__ = (base_class,)
InSection.link_table = link_table
InSection
.__name__ = base_class.__name__+InSection
return InSection

Следует заметить, что мы вынуждены запоминать базовый класс в качестве атрибута, так как поиск имени базового класса при наследовании производится в глобальном пространстве имен. Точно также базовый класс не доступен внутри методов. Воспользовавшись таким новшеством Python 2.1, как вложенные области видимости (в Python 2.1 для их использования нужно в начале модуля выполнить from __future__ import nested_scopes, начиная с версии 2.2 области видимости всегда вложенные), код можно сделать чуть более красивым:

def createInSection(base_class, link_table):

class
InSection(base_class):
def queryParts(self):
tables, fields, condition = base_class.queryParts(self)
tables = %s NATURAL JOIN %s % (tables, self.link_table)
condition = %s AND section=%s % (condition, self.section)
return
tables, fields, condition

InSection
.link_table = link_table
InSection
.__name__ = base_class.__name__+InSection
return InSection

Описанный выше метод обладает одним недостатком: он не будет работать с классами нового типа, появившимися в версии 2.2, так как атрибуты __name__, __bases__ и __dict__ в них доступны только для чтения. Для создания любых классов вы можете воспользоваться функцией classobj из модуля new или, начиная с версии 2.2, воспользоваться одним из конструкторов types.ClassType или type (в будущем они, скорее всего, будут синонимами), которые, в отличие от classobj, можно использовать и в качестве базового класса (чем мы и воспользуемся чуть позже). Перепишем наш пример с использованием new.classobj, по-прежнему для удобства пользуясь вложенными областями видимости:

from new import classobj

def createInSection
(base_class, link_table):

def queryParts(self):
tables, fields, condition = base_class.queryParts(self)
tables = %s NATURAL JOIN %s % (tables, self.link_table)
condition = %s AND section=%s % (condition, self.section)
return
tables, fields, condition

return return classobj(base_class.__name__+InSection, (base_class,),
{
queryParts: queryParts})

Следует заметить, что конструктор type (в отличие от new.classobj и types.ClassType) требует, чтобы в качестве "базы" выступал класс нового типа. Это можно гарантировать добавив в список базовых классов тип object, то есть используя в качестве "базы" (base_class, object) вместо (base_class,).

Сегодня (Python 2.2)


А теперь взгляните на встроенную поддержку полноценных метаклассов в Python 2.2. Как и в самом первом варианте, метаклассом может быть любой класс, для которого методы __new__ и __init__ воспринимают четыре аргумента: создаваемый класс, имя класса, кортеж базовых классов и словарь пространства имен класса (чтобы не определять сразу оба метода, достаточно унаследовать метакласс от type). Но теперь не нужно никаких ухищрений, а чтобы воспользоваться метаклассом достаточно определить __metaclass__ в пространстве имен имен класса или глобальном пространстве имен модуля, указывая таким образом альтернативный конструктор для конкретного класса или всех классов модуля (точнее, "пострадают" только те классы, в момент выполнения которых глобальное имя __metaclass__ будет определено) соответственно. Приведем пример метакласса, который автоматически создает свойство name, если определен хотябы один из методов _get_name, _set_name или _del_name.

class AutoProperties(type):

def __new__(cls, name, bases, dict):
properties = {}
for
name in dict.keys():
if
name[:5] in (_get_, _set_, _del_):
properties[name[5:]] = 1
for name in properties.keys():
fget = dict.get(_get_+name)
fset = dict.get(_set_+name)
fdel = dict.get(_del_+name)
dict[name] = property(fget, fset, fdel)
return
type.__new__(cls, name, bases, dict)

Покажем работу этого метакласса на примере класса, представляющего химический элемент.

PeriodicTable = {
1: (H, 1.0079, hydrogen),
2: (He, 4.0026, helium),
3: (Li, 6.9410, lithium),
4: (Be, 9.0122, berillium),
# Информация о других элементах
}

_symbol2number = {}
for
number, (symbol, mass, name) in PeriodicTable.iteritems():
_symbol2number[symbol.lower()] = number


class Element:

__metaclass__ = AutoProperties

def __init__
(self, element):
if
isinstance(element, Element):
self.number = element.number
elif isinstance
(element, int):
self.number = element
else:
self.symbol = element

def __repr__
(self):
return
Element(%s) % self.symbol

def _get_number
(self):
return
self.__number

def _set_number
(self, number):
self.__number = number

def _get_symbol
(self):
return
PeriodicTable[self.__number][0]

def _set_symbol(self, symbol):
self.__number = _symbol2number[symbol.lower()]

def _get_mass(self):
return
PeriodicTable[self.__number][1]

def _get_name(self):
return
PeriodicTable[self.__number][2]

Попробуем теперь воспользоваться классом Element (если сохранить приведенный выше код, включая определение класса AutoProperties, в файле Element.py, то достаточно набрать комманду python -i Element.py). Атрибуты number и symbol можно менять:

>>> el = Element(H)
>>>
el
Element
(H)
>>>
el.number = 2
>>> el
Element
(He)
>>>
el.symbol = li
Element(Li)

А атрибуты mass и name доступны только для чтения:

>>> el.mass
6.9409999999999998
>>> el.mass = 8.0
Traceback
(most recent call last):
File <stdin>, line 1, in ?
AttributeError: cant set attribute
>>> el.name
lithium
>>> el.name =
hydrogen
Traceback (most recent call last):
File <stdin>, line 1, in ?
AttributeError: can
t set attribute



Похожие статьи:
- Стоит ли отменять пространства имен XML?
- Программирование Web-клиента на языке Python
- Ч1. Как получить максимальный результат от регистрации в каталогах
- Подходы языка Python - забавный пример оптимизации
- Куда приполз Питон?
- Как сделать свой сайт полезным
- Как увеличить посещаемость
- Применение Python в качестве калькулятора
- Слоган для сайта
- Копирайтер - новая профессия в Интернет
- Коммерческий текст - виды и назначения
- Контент сайта - основа успеха
- Что такое XML Sapiens


Оглавление | Обсудить на форуме | Главная страница сайта | Карта сайта |
[0.001]