У меня есть общее представление о метаклассах. Это классы, на которых основаны объекты класса (поскольку классы являются объектами в Python). Но кто-то может объяснить (с кодом), как это делается для его создания.Как создать метакласс?
ответ
Есть (на данный момент) два основных методов в метаклассе:
__prepare__
и__new__
__prepare__
позволяет поставлять пользовательское отображение (например, в OrderedDict
) для использования в качестве пространства имен во время создания класса. Вы должны вернуть экземпляр любого пространства имен, которое вы выберете. Если вы не применяете __prepare__
, используется обычный dict
.
__new__
несет ответственность за фактическое создание/модификацию окончательного класса.
голой кости, делать-ничего-экстра метаклассом будет выглядеть так:
class Meta(type):
def __prepare__(metaclass, cls, bases):
return dict()
def __new__(metacls, cls, bases, clsdict):
return super().__new__(metacls, cls, bases, clsdict)
Простой пример:
Допустим, вы хотите, чтобы некоторые простой код проверки запускать на своих атрибутов - как это должен всегда быть int
или str
. Без метакласса, ваш класс будет выглядеть примерно так:
class Person:
weight = ValidateType('weight', int)
age = ValidateType('age', int)
name = ValidateType('name', str)
Как вы можете видеть, вы должны повторять имя атрибута дважды. Это делает опечатки возможными наряду с раздражающими ошибками.
Простой метакласс может решить эту проблему:
class Person(metaclass=Validator):
weight = ValidateType(int)
age = ValidateType(int)
name = ValidateType(str)
Это то, что метакласс будет выглядеть (не используя __prepare__
, так как это не требуется):
class Validator(type):
def __new__(metacls, cls, bases, clsdict):
# search clsdict looking for ValidateType descriptors
for name, attr in clsdict.items():
if isinstance(attr, ValidateType):
attr.name = name
attr.attr = '_' + name
# create final class and return it
return super().__new__(metacls, cls, bases, clsdict)
Пробы пробег:
p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'
производит:
9
Traceback (most recent call last):
File "simple_meta.py", line 36, in <module>
p.weight = '9'
File "simple_meta.py", line 24, in __set__
(self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')
Примечание
Этот пример достаточно прост, он может также осуществляться с классом декоратором, но предположительно фактический метакласс будет делать гораздо больше.
В Python 2.х, метод __prepare__
не существует, а класс speficies его метакласса, включив переменную класса __metaclass__ = ...
, как это:
class Person(object):
__metaclass__ = ValidateType
Класс «» ValidateType для справки:
class ValidateType:
def __init__(self, type):
self.name = None # will be set by metaclass
self.attr = None # will be set by metaclass
self.type = type
def __get__(self, inst, cls):
if inst is None:
return self
else:
return inst.__dict__[self.attr]
def __set__(self, inst, value):
if not isinstance(value, self.type):
raise TypeError('%s must be of type(s) %s (got %r)' %
(self.name, self.type, value))
else:
inst.__dict__[self.attr] = value
Я просто написал полностью комментарий к метаклассу. Это в Python 2.7. Я рассказываю об этом здесь и надеюсь, что это поможет вам лучше понять методы __new__
, __init__
, __call__
, __dict__
и концепцию ограниченного/неограниченного в Python, а также использование метаклассов.
Проблемы с метаклассом, я чувствую, что он имеет слишком много мест, где вы можете сделать то же самое, или аналогичное но с некоторыми небольшими различиями . Поэтому мои комментарии и тестовые примеры в основном подчеркивают , где написать то, что, Что происходит, где в некоторых точках, и , что доступно определенному объекту.
В примере попытается построить фабрику классов с сохранением корректных определений классов.
from pprint import pprint
from types import DictType
class FactoryMeta(type):
""" Factory Metaclass """
# @ Anything "static" (bounded to the classes rather than the instances)
# goes in here. Or use "@classmethod" decorator to bound it to meta.
# @ Note that these members won't be visible to instances, you have to
# manually add them to the instances in metaclass' __call__ if you wish
# to access them through a instance directly (see below).
extra = "default extra"
count = 0
def clsVar(cls):
print "Class member 'var': " + str(cls.var)
@classmethod
def metaVar(meta):
print "Metaclass member 'var': " + str(meta.var)
def __new__(meta, name, bases, dict):
# @ Metaclass' __new__ serves as a bi-functional slot capable for
# initiating the classes as well as alternating the meta.
# @ Suggestion is putting majority of the class initialization code
# in __init__, as you can directly reference to cls there; saving
# here for anything you want to dynamically added to the meta (such
# as shared variables or lazily GC'd temps).
# @ Any changes here to dict will be visible to the new class and their
# future instances, but won't affect the metaclass. While changes
# directly through meta will be visible to all (unless you override
# it later).
dict['new_elem'] = "effective"
meta.var = "Change made to %s by metaclass' __new__" % str(meta)
meta.count += 1
print "================================================================"
print " Metaclass's __new__ (creates class objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(meta)
print "Bounded object's __dict__: "
pprint(DictType(meta.__dict__), depth = 1)
print "----------------------------------------------------------------"
print "Parameter 'name': " + str(name)
print "Parameter 'bases': " + str(bases)
print "Parameter 'dict': "
pprint(dict, depth = 1)
print "\n"
return super(FactoryMeta, meta).__new__(meta, name, bases, dict)
def __init__(cls, name, bases, dict):
# @ Metaclass' __init__ is the standard slot for class initialization.
# Classes' common variables should mainly goes in here.
# @ Any changes here to dict won't actually affect anything. While
# changes directly through cls will be visible to the created class
# and its future instances. Metaclass remains untouched.
dict['init_elem'] = "defective"
cls.var = "Change made to %s by metaclass' __init__" % str(cls)
print "================================================================"
print " Metaclass's __init__ (initiates class objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(cls)
print "Bounded object's __dict__: "
pprint(DictType(cls.__dict__), depth = 1)
print "----------------------------------------------------------------"
print "Parameter 'name': " + str(name)
print "Parameter 'bases': " + str(bases)
print "Parameter 'dict': "
pprint(dict, depth = 1)
print "\n"
return super(FactoryMeta, cls).__init__(name, bases, dict)
def __call__(cls, *args):
# @ Metaclass' __call__ gets called when a class name is used as a
# callable function to create an instance. It is called before the
# class' __new__.
# @ Instance's initialization code can be put in here, although it
# is bounded to "cls" rather than instance's "self". This provides
# a slot similar to the class' __new__, where cls' members can be
# altered and get copied to the instances.
# @ Any changes here through cls will be visible to the class and its
# instances. Metaclass remains unchanged.
cls.var = "Change made to %s by metaclass' __call__" % str(cls)
# @ "Static" methods defined in the meta which cannot be seen through
# instances by default can be manually assigned with an access point
# here. This is a way to create shared methods between different
# instances of the same metaclass.
cls.metaVar = FactoryMeta.metaVar
print "================================================================"
print " Metaclass's __call__ (initiates instance objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(cls)
print "Bounded object's __dict__: "
pprint(DictType(cls.__dict__), depth = 1)
print "\n"
return super(FactoryMeta, cls).__call__(*args)
class Factory(object):
""" Factory Class """
# @ Anything declared here goes into the "dict" argument in the metaclass'
# __new__ and __init__ methods. This provides a chance to pre-set the
# member variables desired by the two methods, before they get run.
# @ This also overrides the default values declared in the meta.
__metaclass__ = FactoryMeta
extra = "overridng extra"
def selfVar(self):
print "Instance member 'var': " + str(self.var)
@classmethod
def classFactory(cls, name, bases, dict):
# @ With a factory method embedded, the Factory class can act like a
# "class incubator" for generating other new classes.
# @ The dict parameter here will later be passed to the metaclass'
# __new__ and __init__, so it is the right place for setting up
# member variables desired by these two methods.
dict['class_id'] = cls.__metaclass__.count # An ID starts from 0.
# @ Note that this dict is for the *factory product classes*. Using
# metaclass as callable is another way of writing class definition,
# with the flexibility of employing dynamically generated members
# in this dict.
# @ Class' member methods can be added dynamically by using the exec
# keyword on dict.
exec(cls.extra, dict)
exec(dict['another_func'], dict)
return cls.__metaclass__(name + ("_%02d" % dict['class_id']), bases, dict)
def __new__(cls, function):
# @ Class' __new__ "creates" the instances.
# @ This won't affect the metaclass. But it does alter the class' member
# as it is bounded to cls.
cls.extra = function
print "================================================================"
print " Class' __new__ (\"creates\" instance objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(cls)
print "Bounded object's __dict__: "
pprint(DictType(cls.__dict__), depth = 1)
print "----------------------------------------------------------------"
print "Parameter 'function': \n" + str(function)
print "\n"
return super(Factory, cls).__new__(cls)
def __init__(self, function, *args, **kwargs):
# @ Class' __init__ initializes the instances.
# @ Changes through self here (normally) won't affect the class or the
# metaclass; they are only visible locally to the instances.
# @ However, here you have another chance to make "static" things
# visible to the instances, "locally".
self.classFactory = self.__class__.classFactory
print "================================================================"
print " Class' __init__ (initiates instance objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(self)
print "Bounded object's __dict__: "
pprint(DictType(self.__dict__), depth = 1)
print "----------------------------------------------------------------"
print "Parameter 'function': \n" + str(function)
print "\n"
return super(Factory, self).__init__(*args, **kwargs)
# @ The metaclass' __new__ and __init__ will be run at this point, where the
# (manual) class definition hitting its end.
# @ Note that if you have already defined everything well in a metaclass, the
# class definition can go dummy with simply a class name and a "pass".
# @ Moreover, if you use class factories extensively, your only use of a
# manually defined class would be to define the incubator class.
Выход выглядит следующим образом (адаптировано для лучшей демонстрации):
================================================================
Metaclass's __new__ (creates class objects)
----------------------------------------------------------------
Bounded to object: <class '__main__.FactoryMeta'>
Bounded object's __dict__:
{ ...,
'clsVar': <function clsVar at 0x00000000029BC828>,
'count': 1,
'extra': 'default extra',
'metaVar': <classmethod object at 0x00000000029B4B28>,
'var': "Change made to <class '__main__.FactoryMeta'> by metaclass' __new__"}
----------------------------------------------------------------
Parameter 'name': Factory
Parameter 'bases': (<type 'object'>,)
Parameter 'dict':
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'overridng extra',
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>}
================================================================
Metaclass's __init__ (initiates class objects)
----------------------------------------------------------------
Bounded to object: <class '__main__.Factory'>
Bounded object's __dict__:
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'overridng extra',
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>,
'var': "Change made to <class '__main__.Factory'> by metaclass' __init__"}
----------------------------------------------------------------
Parameter 'name': Factory
Parameter 'bases': (<type 'object'>,)
Parameter 'dict':
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'overridng extra',
'init_elem': 'defective',
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>}
Последовательность вызова является метаклассом __new__
то его __init__
. __call__
не будет вызываться в это время.
И если мы создаем экземпляр,
func1 = (
"def printElems(self):\n"
" print \"Member new_elem: \" + self.new_elem\n"
" print \"Member init_elem: \" + self.init_elem\n"
)
factory = Factory(func1)
Выход:
================================================================
Metaclass's __call__ (initiates instance objects)
----------------------------------------------------------------
Bounded to object: <class '__main__.Factory'>
Bounded object's __dict__:
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'overridng extra',
'metaVar': <bound method type.metaVar of <class '__main__.FactoryMeta'>>,
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>,
'var': "Change made to <class '__main__.Factory'> by metaclass' __call__"}
================================================================
Class' __new__ ("creates" instance objects)
----------------------------------------------------------------
Bounded to object: <class '__main__.Factory'>
Bounded object's __dict__:
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'def printElems(self):\n print "Member new_elem: " + self.new_elem\n print "Member init_elem: " + self.init_elem\n',
'metaVar': <bound method type.metaVar of <class '__main__.FactoryMeta'>>,
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>,
'var': "Change made to <class '__main__.Factory'> by metaclass' __call__"}
----------------------------------------------------------------
Parameter 'function':
def printElems(self):
print "Member new_elem: " + self.new_elem
print "Member init_elem: " + self.init_elem
================================================================
Class' __init__ (initiates instance objects)
----------------------------------------------------------------
Bounded to object: <__main__.Factory object at 0x00000000029BB7B8>
Bounded object's __dict__:
{'classFactory': <bound method FactoryMeta.classFactory of <class '__main__.Factory'>>}
----------------------------------------------------------------
Parameter 'function':
def printElems(self):
print "Member new_elem: " + self.new_elem
print "Member init_elem: " + self.init_elem
Метакласс __call__
вызывается первым, то класс __new__
и __init__
.
Сравнивая печатные элементы каждого объекта, вы можете узнать, когда и где они добавлены или изменены, так же, как я прокомментировал код.
Я также выполнить следующие тестовые случаи:
factory.clsVar() # Will raise exception
Factory.clsVar()
factory.metaVar()
factory.selfVar()
func2 = (
"@classmethod\n"
"def printClassID(cls):\n"
" print \"Class ID: %02d\" % cls.class_id\n"
)
ProductClass1 = factory.classFactory("ProductClass", (object,), { 'another_func': func2 })
product = ProductClass1()
product.printClassID()
product.printElems() # Will raise exception
ProductClass2 = Factory.classFactory("ProductClass", (Factory,), { 'another_func': "pass" })
ProductClass2.printClassID() # Will raise exception
ProductClass3 = ProductClass2.classFactory("ProductClass", (object,), { 'another_func': func2 })
Что вы можете запустить самостоятельно, чтобы увидеть, как это работает.
Обратите внимание, что я намеренно оставил имена динамически сгенерированных классов, отличные от имен переменных, которым они были назначены. Это означает, какие имена фактически действуют.
Другое примечание: я ставил «статические» в кавычках, которые я называю концепцией типа C++, а не декоратором Python. Традиционно я программист на C++, поэтому мне все же нравится думать на своем пути.
Подробный взгляд на * почему * метаклассов см. В [этом ответе] (http://stackoverflow.com/a/6581949/208880). –
@PeterMortensen: Нет. Этот вопрос ищет ресурс вне сайта. –