Почему аргумент ключевого слова metaclass определения класса принимает вызываемый?

class MyMetaclass ( type ): передать def metaclass_callable ( имя , базы , пространство имен ): print ( "Called with" , name ) return MyMetaclass ( имя , базы , пространство имен ) класс MyClass ( metaclass = metaclass_callable ): pass class MyDerived ( MyClass ): pass print ( тип ( MyClass ), тип ( MyDerived )) > Фон

Python 3 документация четко описывает , как определяется metaclass_callable класса:

  • если не указаны MyMetaclass и явный метакласс, то используется MyMetaclass ()
  • если задан явный метакласс, и он не является экземпляром MyClass (), то он используется непосредственно в качестве метакласса
  • если экземпляр MyDerived () указан как явный метакласс или определены базы, тогда используется самый производный метакласс

Поэтому, согласно второму правилу, можно указать метакласс с помощью вызываемого. Например,

metaclass_callable

Вопрос 1

Является метаклассом metaclass_callable: MyMetaclassили type? Второе правило в документации гласит, что предоставленный вызываемый «используется непосредственно как метакласс». Тем не менее, это , кажется, больше смысла говорить о том , что метаклассом это MyMetaclassтак

  • In [7]: print(type(MyClass), type(MyDerived)) <class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'>и name, bases, ns, **kwdsимеют тип new_class,
  • __call__ вызывается один раз и затем оказывается неустранимым,
  • производные классы не используют (насколько я могу судить) каким-либо образом (они используют ).Metaclass.__call__()In [21]: def metaclass_callable(name, bases, namespace): def inner(): return MyMetaclass(name, bases, namespace) return inner() ....: In [22]: class MyClass(metaclass=metaclass_callable): pass ....: In [23]: print(type(MyClass), type(MyDerived)) <class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'>

вопрос 2

Есть ли что-нибудь, что вы можете сделать с вызываемым, который вы не можете сделать с экземпляром type? Какова цель принятия произвольного вызываемого?

python,class,python-3.x,metaclass,

8

Ответов: 3


Что касается вашего первого вопроса, должен быть метакласс new_class(что это так):

prepare_class

Причина в том, что если метакласс не является экземпляром типа python, он вызывает метакласс, передавая ему эти аргументы prepare_class(см. __prepare__), И поскольку вы возвращаете свой реальный метакласс в этой функции, он получает правильный тип для метакласса.

И о втором вопросе:

Какова цель принятия произвольного вызываемого?

Специальной цели нет, на самом деле это характер метаклассов, потому что из-за того, что создание экземпляра из класса всегда вызывает метакласс, вызывая его _calculate_metaметод:

__prepare__

Это означает, что вы можете передать любой вызываемый как ваш метакласс. Например, если вы проверите его с помощью вложенной функции, результат будет по-прежнему остальным:

__call__

Для получения дополнительной информации о том, как Python разбивает класс:

Он вызывает __new__функцию, которую он вызывает __init__внутри себя, а затем, как вы можете видеть внутри питона, вызывает метод соответствующего метакласса, помимо поиска правильной мета (с помощью функции) и создания соответствующего пространства имен для класса.# Provide a PEP 3115 compliant mechanism for class creation def new_class(name, bases=(), kwds=None, exec_body=None): """Create a class object dynamically using the appropriate metaclass.""" meta, ns, kwds = prepare_class(name, bases, kwds) if exec_body is not None: exec_body(ns) return meta(name, bases, ns, **kwds) def prepare_class(name, bases=(), kwds=None): """Call the __prepare__ method of the appropriate metaclass. Returns (metaclass, namespace, kwds) as a 3-tuple *metaclass* is the appropriate metaclass *namespace* is the prepared class namespace *kwds* is an updated copy of the passed in kwds argument with any 'metaclass' entry removed. If no kwds argument is passed in, this will be an empty dict. """ if kwds is None: kwds = {} else: kwds = dict(kwds) # Don't alter the provided mapping if 'metaclass' in kwds: meta = kwds.pop('metaclass') else: if bases: meta = type(bases[0]) else: meta = type if isinstance(meta, type): # when meta is a type, we first determine the most-derived metaclass # instead of invoking the initial candidate directly meta = _calculate_meta(meta, bases) if hasattr(meta, '__prepare__'): ns = meta.__prepare__(name, bases, **kwds) else: ns = {} return meta, ns, kwds def _calculate_meta(meta, bases): """Calculate the most derived metaclass.""" winner = meta for base in bases: base_meta = type(base) if issubclass(winner, base_meta): continue if issubclass(base_meta, winner): winner = base_meta continue # else: raise TypeError("metaclass conflict: " "the metaclass of a derived class " "must be a (non-strict) subclass " "of the metaclasses of all its bases") return winnertypeMyMetaClass

Итак, все в одном - это иерархия выполнения методов метакали:

  1. metaclass_callable 1
  2. metaclass
  3. __call__
  4. print

И вот исходный код:

MyMetaClass.__call__

1. Обратите внимание, что он получает неявное выражение внутри функции new_class и перед возвратом.


2

Ну, typeконечно type.__call__. __call__изначально «выбран» в качестве метакласса, поскольку он указан в metaclasskwarg и как таковой, он MyMetaClass(простой вызов функции) будет выполнен.

Так получилось, что его вызов будет printвызван, а затем будет вызван cls.__class__(что вызовы MyMetaClassс тех metaclass_callableпор не были переопределены MyMetaClass). Там назначение metaclass_callableпроизводится в metaclass.

bases вызывается один раз и затем оказывается неустранимым

Да, он только изначально вызывается, а затем контролирует руки MyClass. Я не знаю ни одного атрибута класса, который хранит эту информацию.

производные классы не используют (насколько я могу судить) MyMetaClassкаким-либо образом.

Нет, если no metaclassявно определено, будет использоваться наилучшее соответствие для метаклассовbases (здесь 2) (в результате __call__).


Что касается вопроса 2, достаточно уверенно, что все, что вы можете сделать с вызываемым, также возможно, используя экземпляр типа с clsсоответствующим переопределением. Что касается того , почему , вы можете не захотеть полностью размыто создавать класс, если просто хотите внести небольшие изменения при создании класса.


2

Что касается вопроса 1, я думаю, что «метакласс» класса type(cls)следует понимать как . Этот способ понимания совместим с сообщением об ошибке Python в следующем примере:>>> class Meta1(type): pass ... >>> class Meta2(type): pass ... >>> def metafunc(name, bases, methods): ... if methods.get('version') == 1: ... return Meta1(name, bases, methods) ... return Meta2(name, bases, methods) ... >>> class C1: ... __metaclass__ = metafunc ... version = 1 ... >>> class C2: ... __metaclass__ = metafunc ... version = 2 ... >>> type(C1) <class '__main__.Meta1'> >>> type(C2) <class '__main__.Meta2'> >>> class C3(C1,C2): pass ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

>>> class Mockup(type):
...     def __new__(cls, name, bases, methods):
...         return Meta1(name, bases, methods)
... 
>>> class Foo:
...     __metaclass__ = Mockup
... 
>>> type(Foo)
<class '__main__.Meta1'>
>>> isinstance(Foo, Mockup)
False
>>> Foo.__metaclass__
<class '__main__.Mockup'>

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

Что касается второго вопроса, действительно, с подклассом типа, используемым в качестве метакласса, вы можете делать то же, что и с любым другим вызываемым. В частности, возможно, что он дает то, что не является его примером:

type

Что касается того, почему Python дает свободу использования любого вызываемого: предыдущий пример показывает, что на самом деле не имеет значения, является ли вызываемый типом или нет.

Кстати, вот забавный пример: возможно метаклассы кода, которые сами по себе имеют метакласс, отличный от type--- назовем его метаметаллом. Метаметакласс выполняет то, что происходит при вызове метакласса. Таким образом, можно создать класс с двумя базами, метаклассы которых не являются подклассами друг друга (сравните с сообщением об ошибке Python в примере выше!). Действительно, только метакласс полученного класса является подклассом метакласса баз, и этот метакласс создается «на лету»:

>>> class MetaMeta(type):
...     def __call__(mcls, name, bases, methods):
...         metabases = set(type(X) for X in bases)
...         metabases.add(mcls)
...         if len(metabases) > 1:
...             mcls = type(''.join([X.__name__ for X in metabases]), tuple(metabases), {})
...         return mcls.__new__(mcls, name, bases, methods)
... 
>>> class Meta1(type):
...     __metaclass__ = MetaMeta
... 
>>> class Meta2(type):
...     __metaclass__ = MetaMeta
... 
>>> class C1:
...     __metaclass__ = Meta1
... 
>>> class C2:
...     __metaclass__ = Meta2
... 
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
... 
>>> type(C3)
<class '__main__.Meta1Meta2'>

Что менее интересно: предыдущий пример не будет работать на Python 3. Если я правильно понимаю, Python 2 создает класс и проверяет, является ли его метакласс классом всех его оснований, тогда как Python 3 сначала проверяет, есть ли одна база, метакласс является суперклассом метаклассов всех других баз и только тогда создает новый класс. С моей точки зрения, это регресс. Но это будет тема нового вопроса, который я собираюсь опубликовать ...

Изменить : Новый вопрос здесь

питон, класс, питон-3.x, метаклассом,
Похожие вопросы