Метаклассы в Python: пара вопросов для уточнения - PullRequest
5 голосов
/ 03 июля 2011

После сбоя в работе с метаклассами я углубился в тему метапрограммирования в Python, и у меня есть пара вопросов, которые, imho, не совсем четко даны в доступных документах.

  1. При использовании в метаклассе __new__ и __init__ их аргументы должны быть определены одинаково?
  2. Какой самый эффективный способ определения класса __init__ в метаклассе?
  3. Можно ли ссылаться на экземпляр класса (обычно self ) в метаклассе?

Ответы [ 2 ]

2 голосов
/ 03 июля 2011
  1. При использовании в метаклассе __new__ и __init__ их аргументы должны быть определены одинаково?

    Я думаю, Алекс Мартелли объясняет это наиболее кратко:

    class Name(Base1,Base2): <<body>>
        __metaclass__==suitable_metaclass
    

    означает

    Name = suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
    

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

    suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
    

    , он говорит вам, что метод __new__ подходящего_метакласса должен иметь сигнатуру типа

    def __new__(metacls, name, bases, dct)
    

    и метод __init__, например

    def __init__(cls, name, bases, dct)
    

    То есть подписи не совсем одинаковы, но они отличаются только первым аргументом.

  2. Какой самый эффективный способ определения класса __init__ в метаклассе?

    Что вы подразумеваете под эффективным?Нет необходимости определять __init__, если вы не хотите.

  3. Есть ли способ ссылки на экземпляр класса (обычно self) в метаклассе?

    Нет, и вам не нужно.Все, что зависит от экземпляра класса, должно рассматриваться в определении класса, а не в метаклассе.

1 голос
/ 03 июля 2011

Для 1: __init__ и __new__ из любого класса должны принимать одинаковые аргументы, потому что они будут вызываться с одинаковыми аргументами.Обычно __new__ принимает больше аргументов, которые игнорирует (например, object.__new__ принимает любые аргументы и игнорирует их), так что __new__ не нужно переопределять во время наследования, но вы обычно делаете это только тогда, когда у вас естьвообще нет __new__.

Здесь это не проблема, поскольку, как было сказано, метаклассы всегда вызываются с одним и тем же набором аргументов, поэтому вы не можете столкнуться с проблемами.С аргументами хотя бы.Но если вы изменяете аргументы, передаваемые родительскому классу, вам нужно изменить их в обоих.

Для 2: Обычно вы не определяете класс __init__ в метаклассе.Вы можете написать оболочку и заменить __init__ класса либо в __new__, либо в __init__ метакласса, либо вы можете переопределить __call__ в метаклассе.Первый будет вести себя странно, если вы используете наследование.

import functools

class A(type):
    def __call__(cls, *args, **kwargs):
        r = super(A, cls).__call__(*args, **kwargs)
        print "%s was instantiated" % (cls.__name__, )
        print "the new instance is %r" % (r, )
        return r


class B(type):
    def __init__(cls, name, bases, dct):
        super(B, cls).__init__(name, bases, dct)
        if '__init__' not in dct:
            return
        old_init = dct['__init__']
        @functools.wraps(old_init)
        def __init__(self, *args, **kwargs):
            old_init(self, *args, **kwargs)
            print "%s (%s) was instantiated" % (type(self).__name__, cls.__name__)
            print "the new instance is %r" % (self, )
        cls.__init__ = __init__


class T1:
    __metaclass__ = A

class T2:
    __metaclass__ = B
    def __init__(self): 
        pass

class T3(T2):
    def __init__(self):
        super(T3, self).__init__()

И результат его вызова:

>>> T1()
T1 was instantiated
the new instance is <__main__.T1 object at 0x7f502c104290>
<__main__.T1 object at 0x7f502c104290>
>>> T2()
T2 (T2) was instantiated
the new instance is <__main__.T2 object at 0x7f502c0f7ed0>
<__main__.T2 object at 0x7f502c0f7ed0>
>>> T3()
T3 (T2) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
T3 (T3) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
<__main__.T3 object at 0x7f502c104290>

Для 3: Да, с __call__, как показано выше.

...