Аргументы метакласса Python
Я пытаюсь динамически генерировать классы в python 2.7, и мне интересно, можете ли вы легко передать аргументы в метакласс из объекта класса.
Я прочитал эту статью, которая является удивительной, но не совсем ответила на вопрос. на данный момент я делаю:
def class_factory(args, to, meta_class):
Class MyMetaClass(type):
def __new__(cls, class_name, parents, attrs):
attrs['args'] = args
attrs['to'] = to
attrs['eggs'] = meta_class
class MyClass(object):
metaclass = MyMetaClass
...
но это требует от меня выполнения следующих
MyClassClass = class_factory('spam', 'and', 'eggs')
my_instance = MyClassClass()
Есть ли более чистый способ сделать это?
Ответы
Ответ 1
Да, есть простой способ сделать это. В методе metaclass __new__()
просто проверьте словарь классов, прошедший как последний аргумент. Все, что определено в инструкции class
, будет там. Например:
class MyMetaClass(type):
def __new__(cls, class_name, parents, attrs):
if 'meta_args' in attrs:
meta_args = attrs['meta_args']
attrs['args'] = meta_args[0]
attrs['to'] = meta_args[1]
attrs['eggs'] = meta_args[2]
del attrs['meta_args'] # clean up
return type.__new__(cls, class_name, parents, attrs)
class MyClass(object):
__metaclass__ = MyMetaClass
meta_args = ['spam', 'and', 'eggs']
myobject = MyClass()
from pprint import pprint
pprint(dir(myobject))
print myobject.args, myobject.to, myobject.eggs
Вывод:
['__class__',
'__delattr__',
'__dict__',
'__doc__',
'__format__',
'__getattribute__',
'__hash__',
'__init__',
'__metaclass__',
'__module__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'args',
'eggs',
'to']
spam and eggs
Ответ 2
В то время как вопрос для Python 2.7 и уже имеет отличный ответ, у меня был такой же вопрос для Python 3.3, и этот поток был самым близким к ответу, который я мог найти в Google. Я нашел лучшее решение для Python 3.x, копаясь в документации Python, и я делюсь своими выводами для всех, кто придет сюда, ищет версию Python 3.x.
Фон
Python 3.x предлагает собственный метод передачи аргументов в метакласс. Обратите внимание, что большая часть этой информации получена из Документация на языке Python в разделе "Настройка создания классов" .
В Python 3 вы указываете метаклас через ключевой аргумент, а не атрибут класса:
class MyClass(metaclass=MyMetaClass):
pass
Этот оператор преобразуется в:
MyClass = metaclass(name, bases, **kargs)
... где metaclass
- это значение для аргумента метакласса, который вы передали, name
- это MyClass, bases
- кортеж нулевой длины, потому что мы не предоставляли никаких базовых классов, а kargs
- пустой словарь, потому что мы не предоставили никаких аргументов с неподтвержденным ключевым словом.
Игнорируя мелкие детали, этот оператор примерно переводит:
namespace = metaclass.__prepare__(name, bases, **kargs)
#..."cls" object gets created somewhere in here...
MyClass = metaclass.__new__(metacls, name, bases, namespace, **kargs)
metaclass.__init__(MyClass, name, bases, namespace, **kargs)
... где kargs
- это тот же словарь аргументов ключевого слова, который мы передавали в определение класса, хотя и разные dict
объекты каждый раз.
Передача аргументов в метакласс
Если вы хотите передать аргументы в свой метакласс, вы просто добавляете дополнительные аргументы ключевого слова:
class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
pass
... и он переходит в ваш метакласс, как описано выше:
class MyMetaClass(type):
@classmethod
def __prepare__(metacls, name, bases, **kargs):
#kargs = {"myArg1": 1, "myArg2": 2}
return super().__prepare__(name, bases, **kargs)
def __new__(metacls, name, bases, namespace, **kargs)
#kargs = {"myArg1": 1, "myArg2": 2}
return super().__new__(metacls, name, bases, namespace)
#DO NOT send "**kargs" to "type.__new__". It won't catch them and
#you'll get a "TypeError: type() takes 1 or 3 arguments" exception.
def __init__(cls, name, bases, namespace, myArg1=7, **kargs):
#myArg1 = 1
#kargs = {"myArg2": 2}
super().__init__(name, bases, namespace)
#DO NOT send "**kargs" to "type.__init__". You'll get a
#"TypeError: type.__init__() takes no keyword arguments" exception.
Обратите особое внимание на то, чтобы оставить kargs
вне вызова type.__new__
и type.__init__
. В частности, вам нужно реализовать MyMetaClass.__new__
и MyMetaClass.__init__
, чтобы отключить аргументы ключевого слова до того, как они будут переданы базовому классу "type". Тем не менее, __prepare__
является полностью необязательным, если, конечно, вы не хотите использовать аргументы ключевого слова в этом контексте.