Метаклассы: почему метод не унаследован от Базового класса? - PullRequest
0 голосов
/ 13 мая 2018

Я пытаюсь понять метакласс черной магии в Python. AFAIK, метаклассы могут использоваться, например, чтобы гарантировать, что какой-то метод реализован в производном классе, но у меня есть проблема внучат . Кажется, мне нужно явно реализовать все требуемые производные методы, даже если для этого нет причины (?).

Посмотрите на это:

~ $ ipython
Python 3.6.5 (default, Apr 14 2018, 13:17:30) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: class Meta(type):
   ...:     def __new__(cls, name, bases, body):
   ...:         if 'some_method' not in body:
   ...:             raise AttributeError('some_method should be implemented')
   ...:         return super().__new__(cls, name, bases, body)
   ...: 
   ...: class Base(metaclass=Meta):
   ...:     def some_method(self):
   ...:         print('Yay')
   ...: 
   ...: class Derived(Base):
   ...:     pass
   ...: 
   ...: 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-1-51c072cc7909> in <module>()
      9         print('Yay')
     10 
---> 11 class Derived(Base):
     12     pass

<ipython-input-1-51c072cc7909> in __new__(cls, name, bases, body)
      2     def __new__(cls, name, bases, body):
      3         if 'some_method' not in body:
----> 4             raise AttributeError('some_method should be implemented')
      5         return super().__new__(cls, name, bases, body)
      6 

AttributeError: some_method should be implemented

Насколько я понимаю, этого не должно происходить, потому что some_method должен быть получен из Base класса, как здесь:

In [3]: class Base:
   ...:     def some_method(self):
   ...:         print('Yay')
   ...: 
   ...: class Derived(Base):
   ...:     pass
   ...: 
   ...: 

In [4]: Derived().some_method()
Yay

Это ожидается? Если да, не могли бы вы дать мне подсказку, как исправить такое поведение, чтобы мне не нужно было переопределять методы?

Ответы [ 3 ]

0 голосов
/ 13 мая 2018

body содержит то, что определено в определяемом вами классе.В вашем случае some_method не определено в Derived, оно не существует в нем.

Когда вы используете Derived().some_method() (или даже Derived.some_method), оно выполнит поиск.Он не найдет его в Derived, но затем попробует родительские классы, или базы, используя MRO (Порядок разрешения методов).

См. Параметры в def __new__(cls, name, bases, body):, базы отделены от body это то, что вы определили в своем классе, прежде чем MRO вступит в силу.

0 голосов
/ 13 мая 2018

Вы, похоже, пытаетесь заново изобрести колесо, которое является модулем abc и его декоратором @abstractmethod.Я не уверен, почему вы хотите это сделать, но если вы это сделаете, вы должны посмотреть на его источник (который связан с документами).

Ваше решение проверяет, что требуетсяИмя реализовано в теле класса.Это не будет правдой для унаследованных методов, но вы, кажется, знаете это, поэтому я не буду объяснять.Что вы хотите знать: что вы можете сделать вместо этого?Вам нужно явно проверить унаследованные методы.

Способ, которым abc делает это, довольно прост: перед проверкой того, реализованы ли все абстрактные методы, он вызывает getattr для каждого base.(Лучший способ имитировать то, что getattr будет делать в классе, это, в конце концов, вызвать getattr.) Все, что там найдено, не должно появляться в теле.

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

if (not any(hasattr(base, 'some_method') for base in bases)
    and 'some_method' not in body):
0 голосов
/ 13 мая 2018

Наследование - это не то, что вводит определение в класс.Скорее, он позволяет lookup для атрибута выходить за пределы непосредственного класса, если он не определен локально.

В отсутствие вашего метакласса вызов Defined().some_method() выполняется примерно какследует:

  1. Создан экземпляр Defined.
  2. Атрибут с именем some_method ищется для атрибута __dict__ этого экземпляра и не найден.
  3. some_method ищется в Derived.__dict__ и не обнаруживается.
  4. some_method ищется в атрибутах __dict__ классов в классах Derived, начиная с Base.
  5. Base.__dict__['some_method'] найдено, и его значение вызывается с экземпляром Derived в качестве неявного первого аргумента.

с вашимmetaclass, Derived создается с тем же метаклассом, который используется его базовым классом, то есть Meta.__new__ (не type.__new__) используется для создания Derived.Так как some_method не находится в теле оператора класса, он не появляется в объекте dict, переданном в Meta.__new__, что приводит к повышению AttributeError.(Обратите внимание, что вы должны повышать NotImplemented, а не AttributeError, поскольку вы еще не пытаетесь получить доступ к какому-либо конкретному атрибуту.)

...