Почему метакласс должен наследовать от типа? - PullRequest
0 голосов
/ 14 сентября 2018

Мы можем использовать функции в качестве метакласса, и я понимаю, что они не являются производными от типа, как показано ниже:

def test_meta(name, bases, atts):
    print("testmeta called for " + name)
    return type(name,bases,atts);

class Computer:
    __metaclass__ = test_meta
    def __init__(self,brand,model,price):
        self.brand = brand
        self.model = model
        self.price = price

#
#
ob = Computer('p1','core2duo',21985.25)

Однако, когда мы пишем метакласс, он должен быть унаследован от типа, и я не мог понять причиныза этим:

class MyMeta:
    def __new__(meta, name, bases, dct):
        print ('-----------------------------------')
        print ("Allocating memory for class", name)
        print (meta)
        print (bases)
        print (dct)
        return type.__new__(meta, name, bases, dct)

    def __init__(cls, name, bases, dct):
        print ('-----------------------------------')
        print ("Initializing class", name)
        print (cls)
        print (bases)
        print (dct)
        type.__init__(cls,name, bases, dct)

    def __call__(mcs, *args, **kwargs):
        print ('-----------------------------------')
        print ("calling class")
        print mcs

class Computer:
    __metaclass__ = MyMeta
    def __init__(self,brand,model,price):
        self.brand = brand
        self.model = model
        self.price = price

#
#
ob = Computer('p1','core2duo',21985.25)

Например, в приведенном выше коде я не мог понять, почему класс MyMeta должен наследоваться от типа , когда мыявно вызывающие функции типа, например new , init , call .Также ошибка «дескриптор» init 'требует объекта «тип», но получил «экземпляр» », который появляется только тогда, когда создается i = instance (ob).

1 Ответ

0 голосов
/ 15 сентября 2018

В обычном коде Python единственный вызов, который фактически создает объект класса в памяти с двоичной структурой и всеми необходимыми полями, это type.__new__. При использовании функции, вызываемой как метакласс, нужно просто создать экземпляр type или создать что-то, что вообще не является классом (см. Ниже).

Можно было бы создать другой «базовый метакласс», используя собственный код C или даже в чистом Python, вызывая функции выделения памяти для собственной ОС и заполняя ту же структуру , определенную для «объекта типа» , - однако, это будет выполнять только ту же задачу, которую уже выполняет type.__new__, так что это будет сложное и подверженное ошибкам повторное изобретение колеса, которое не может быть улучшено в дальнейшем, поскольку полученная двоичная компоновка должна быть такой же как определено в этой структуре (т. е. указатели на методы, реализующие «магические функции», такие как __init__, __geitem__ и т. д., должны иметь те же смещения, что и в этой структуре)

(Дополнительные поля и даже изысканные значения для данных в этой структуре могут быть выполнены типом наследования кода - следовательно, обычным метаклассом)

Как вы говорите, любой вызываемый объект, даже простая функция, может быть указан как «метакласс» в теле класса (как в Python 2, так и в Python 3), однако, независимо от того, что делает этот вызываемый объект, в определенный момент он придется вызывать type.__new__ (функции делают это косвенно, вызывая type). Однако, как только класс построен, он сам является объектом Python, который является экземпляром класса - тип класса (что входит в его атрибут __class__) является его эффективным метаклассом. Если метакласс, указанный в коде как атрибут __metaclass__ или как метакласс kwarg в Python 3, является подклассом type, это также будет собственно метакласс. Иначе, если это обычная функция, которая просто действует как фабрика классов, вызывающая type в своем теле, эффективный метакласс это просто тип.

Другими словами:

In [1]: def function_meta(name, bases, ns):
   ...:     return type(name, bases, ns)
   ...: 

In [2]: class test(metaclass=function_meta):
   ...:     pass
   ...: 

In [3]: type(test)
Out[3]: type

In [4]: class ClassMeta(type):
   ...:     pass
   ...: 
   ...: 

In [5]: class test2(metaclass=ClassMeta):
   ...:     pass
   ...:

In [6]: type(test2)
Out[6]: __main__.ClassMeta

Итак, почему нельзя использовать класс, который не наследуется от type?

Проблема не в том, что указанный метакласс не наследуется от типа - можно использовать любой вызываемый объект, как показано выше. Ошибка, возникающая при попытке сделать это, вызвана вызовом type.__new__ с не подклассом типа в качестве первого параметра:

In [11]: class InheritFromOther(object):
    ...:     def __new__(mcls, name, bases, ns):
    ...:         return type.__new__(mcls, name, bases, ns)
    ...:         # the line above is the one that errors - as
    ...:         # mcls here is not a subclass of type.
    ...:         # type(name, bases, ns) would work.

In [12]: class test4(metaclass=InheritFromOther):
    ...:     pass
    ...: 
    ...: 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-12-bf5b1fa4bb7f> in <module>()
----> 1 class test4(metaclass=InheritFromOther):
      2     pass

<ipython-input-11-62d1fe46490b> in __new__(mcls, name, bases, ns)
      1 class InheritFromOther(object):
      2     def __new__(mcls, name, bases, ns):
----> 3         return type.__new__(mcls, name, bases, ns)
      4 

TypeError: type.__new__(InheritFromOther): InheritFromOther is not a subtype of type

Теперь, если мы вызовем type.__new__ с допустимым подклассом типа в качестве первого параметра:

In [13]: class InheritFromOtherTake2(object):
    ...:     def __new__(mcls, name, bases, ns):
    ...:         return type.__new__(type, name, bases, ns)
    ...:     

In [14]: class test5(metaclass=InheritFromOtherTake2):
    ...:     pass
    ...: 

In [15]: type(test5)
Out[15]: type

Просто для полноты, как упомянуто выше, действительно возможно, чтобы вызываемый объект, используемый в качестве метакласса, возвращал что-то отличное от экземпляра типа (или его подкласса). В этом случае объект, полученный из тела оператора class, просто не будет классом, но что бы это ни вызывало:

In [7]: def dict_maker_meta(name, bases, ns):
   ...:     return ns
   ...: 

In [8]: class test3(metaclass=dict_maker_meta):
   ...:     a = 1
   ...:     b = 2
   ...:     c = 'test'
   ...:     

In [9]: type(test3)
Out[9]: dict

In [10]: test3
Out[10]: 
{'__module__': '__main__',
 '__qualname__': 'test3',
 'a': 1,
 'b': 2,
 'c': 'test'}
...