Подтвердить что ты не робот

Аргументы метакласса 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()

Есть ли более чистый способ сделать это?

4b9b3361

Ответ 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__ является полностью необязательным, если, конечно, вы не хотите использовать аргументы ключевого слова в этом контексте.