Как передать аргументы метаклассу из определения класса в Python 3.x?
это Python 3.x версия как передать аргументы метаклассу из определения класса? вопрос, указанный отдельно по запросу, так как ответ значительно отличается от Python 2.x.
В Python 3.x, как передать аргументы метаклассу __prepare__
, __new__
и __init__
функции, чтобы автор класса мог дать ввод метаклассу о том, как должен быть создан класс?
как мой случай использования, я использую metaclasses для автоматической регистрации классов и их подклассы в PyYAML для загрузки / сохранения файлов YAML. Это включает в себя некоторую дополнительную логику выполнения, недоступную в запасе PyYAML YAMLObjectMetaClass
. Кроме того, я хочу разрешить авторам классов дополнительно указывать тег/тег-формат-шаблон, который PyYAML использует для представления класса и/или объектов функций для построения и представления. Я уже понял, что не могу использовать подкласс PyYAML YAMLObjectMetaClass
чтобы выполнить это-- " потому что у нас нет доступа к фактическому объекту класса в __new__
" согласно моему комментарию кода-поэтому я пишу свой собственный метакласс, который обертывает регистрационные функции PyYAML.
в конечном счете, я хочу сделать что-то вдоль линий:
from myutil import MyYAMLObjectMetaClass
class MyClass(metaclass=MyYAMLObjectMetaClass):
__metaclassArgs__ = ()
__metaclassKargs__ = {"tag": "!MyClass"}
...где __metaclassArgs__
и __metaclassKargs__
были бы аргументы, идущие к __prepare__
, __new__
и __init__
способы MyYAMLObjectMetaClass
когда MyClass
объект класса становится создан.
Of конечно, я мог бы использовать подход "зарезервированные имена атрибутов", указанный в Python 2.X версия этого вопроса, но я знаю, что есть более элегантный подход.
4 ответов
после копания в официальной документации Python, я обнаружил, что Python 3.x предлагает собственный метод передачи аргументов в метакласс, хотя и не без его недостатков.
просто добавьте дополнительные аргументы ключевого слова в объявление класса:
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 #Included as an example of capturing metaclass args as positional args.
#kargs = {"myArg2": 2}
super().__init__(name, bases, namespace)
#DO NOT send "**kargs" to "type.__init__" in Python 3.5 and older. You'll get a
#"TypeError: type.__init__() takes no keyword arguments" exception.
ты должен уйти kargs
из вызова type.__new__
и type.__init__
(язык Python 3.5 и старше; см. "обновление" ниже) или вам TypeError
исключение из-за передачи слишком много аргументов. Это означает,что при передаче аргументов метакласса таким образом мы всегда должны реализовать MyMetaClass.__new__
и MyMetaClass.__init__
чтобы наши пользовательские аргументы ключевых слов не достигали базового класса type.__new__
и type.__init__
методы. type.__prepare__
кажется, обрабатывает дополнительные аргументы ключевого слова изящно (следовательно, почему я передаю их в Примере, на случай, если есть какая-то функциональность, о которой я не знаю, которая зависит от **kargs
), поэтому определение type.__prepare__
является необязательным.
обновление
в Python 3.6, кажется type
был отрегулирован и type.__init__
теперь можно обрабатывать дополнительные аргументы ключевых слов изящно. Вам все равно нужно определить type.__new__
(бросает TypeError: __init_subclass__() takes no keyword arguments
исключения).
распад
в Python 3 вы указываете метакласс через аргумент ключевого слова, а не атрибут класса:
class MyClass(metaclass=MyMetaClass):
pass
это утверждение примерно переводится как:
MyClass = metaclass(name, bases, **kargs)
...где metaclass
является ли значение для аргумента "metaclass", который вы передали,name
- это имя строки вашего класса ('MyClass'
), bases
- это любые базовые классы, которые вы передали (кортеж нулевой длины ()
в данном случае), и kargs
любые аргументы сайта неуловленные (пустой dict
{}
в данном случае).
разбивая это дальше, утверждение примерно переводится на:
namespace = metaclass.__prepare__(name, bases, **kargs) #`metaclass` passed implicitly since it's a class method.
MyClass = metaclass.__new__(metaclass, name, bases, namespace, **kargs)
metaclass.__init__(MyClass, name, bases, namespace, **kargs)
...где kargs
всегда dict
аргументов сайта неуловленные мы прошли в к определению класса.
разбивая пример, который я дал выше:
class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
pass
...грубо говоря:
namespace = MyMetaClass.__prepare__('C', (), myArg1=1, myArg2=2)
#namespace={'__module__': '__main__', '__qualname__': 'C'}
C = MyMetaClass.__new__(MyMetaClass, 'C', (), namespace, myArg1=1, myArg2=2)
MyMetaClass.__init__(C, 'C', (), namespace, myArg1=1, myArg2=2)
большая часть этой информации пришла из документация Python по "настройке создания класса".
вот версия кода из моего ответа на это другой вопрос о аргументах метакласса, которые были обновлены, чтобы он работал в и Python 2 и 3. По сути, он делает то же самое, что и Бенджамин Петерсон шесть модуль with_metaclass()
функция делает-а именно, чтобы явно создать новый базовый класс, используя желаемый метакласс на лету, когда это необходимо и тем самым избежать ошибок из-за синтаксические различия метакласса между двумя версиями Python (потому что способ сделать это не изменился).
from __future__ import print_function
from pprint import pprint
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)
# Creates base class on-the-fly using syntax which is valid in both
# Python 2 and 3.
class MyClass(MyMetaClass("NewBaseClass", (object,), {})):
meta_args = ['spam', 'and', 'eggs']
myobject = MyClass()
pprint(vars(MyClass))
print(myobject.args, myobject.to, myobject.eggs)
выход:
dict_proxy({'to': 'and', '__module__': '__main__', 'args': 'spam',
'eggs': 'eggs', '__doc__': None})
spam and eggs
в Python 3 вы указываете метакласс через аргумент ключевого слова, а не атрибут класса:
стоит сказать, что этот стиль не совместим с Python 2. Если вы хотите поддерживать Python 2 и 3, Вы должны использовать:
from six import with_metaclass
# or
from future.utils import with_metaclass
class Form(with_metaclass(MyMetaClass, object)):
pass
вот самый простой способ передать аргументы метаклассу в Python 3:
Python 3.x
class MyMetaclass(type):
def __new__(mcs, name, bases, namespace, **kwargs):
return super().__new__(mcs, name, bases, namespace)
def __init__(cls, name, bases, namespace, custom_arg='default'):
super().__init__(name, bases, namespace)
print('Argument is:', custom_arg)
class ExampleClass(metaclass=MyMetaclass, custom_arg='something'):
pass
вы также можете создать базовый класс для метаклассов, которые используют только __init__
дополнительные аргументы:
class ArgMetaclass(type):
def __new__(mcs, name, bases, namespace, **kwargs):
return super().__new__(mcs, name, bases, namespace)
class MyMetaclass(ArgMetaclass):
def __init__(cls, name, bases, namespace, custom_arg='default'):
super().__init__(name, bases, namespace)
print('Argument:', custom_arg)
class ExampleClass(metaclass=MyMetaclass, custom_arg='something'):
pass