2016-11-22 12 views
3

Возможно ли переопределить поле __bases__ метакласса (то есть класс, полученный из type) с использованием свойства get-set? Следующий код работает для получатьC.__bases__, но не установки это:Переопределение __bases__ в метаклассе

class Meta(type): 
    @property 
    def __bases__(cls): 
     print('Getting __bases__ via Meta.__bases__') 
     return super().__bases__ 

    @__bases__.setter 
    def __bases__(cls, value): 
     print('Setting __bases__ via Meta.__bases__') 
     super().__bases__ = value 

class A: pass 
class B: pass 
class C(A, B, metaclass=Meta): pass 

# >>> C.__bases__ 
# Getting __bases__ via Meta.__bases__ 
# (<class '__main__.A'>, <class '__main__.B'>) 
# >>> C.__bases__ = (B, A) 
# Setting __bases__ via Meta.__bases__ 
# AttributeError: 'super' object has no attribute '__bases__' 

Я попробовал несколько замен для super() в функции сеттера, но ни один из них не работает:

type.__setattr__(cls, '__bases__', value) приводит к рекурсии.

object.__setattr__(cls, '__bases__', value) дает TypeError: can't apply this __setattr__ to type object

Итак, что это сводится к тому, как установить cls.__bases__ поле, когда он омрачены собственности на метакласса. Есть идеи?


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

+2

Нет, вы не можете. Зачем вам это нужно, какая проблема этого решения? –

+0

И 'super()' поддерживает только дескрипторы без данных (так что только '__get__'). Нет '__set__' поддерживается, поэтому' super() .__ bas__ = value' попытается использовать атрибут '__bases__' в суперпрокси, а не в каком-либо базовом классе. '__bases__' не может быть изменен. –

+0

Re: почему я хочу это сделать: идея заключается в реализации своего рода «прокси класса», который пересылает метод и атрибут доступа к прокси-классу путем копирования его '__mro__'. Было бы хорошо, если '__bases__' прокси-класса класса также соответствовал бы« __bases__ »прокси-класса, следовательно, необходимость его переопределения. –

ответ

2

super() не поддерживает данные дескрипторы, только простые дескрипторы, поскольку реализовано только super().__get__.

Иными словами, назначение

super().__bases__ = value 

терпит неудачу, потому что объект super() прокси не реализует descriptor.__set__() method, и, таким образом, что назначение пытается установить __bases__ как атрибут на этом прокси-объект.

Вам придется вручную открыть дескриптор на type объекта:

class Meta(type): 
    @property 
    def __bases__(cls): 
     print('Getting __bases__ via Meta.__bases__') 
     return type.__dict__['__bases__'].__get__(cls) 

    @__bases__.setter 
    def __bases__(cls, value): 
     print('Setting __bases__ via Meta.__bases__') 
     return type.__dict__['__bases__'].__set__(cls, value) 

Ради симметрии, я уже использовал тот же ручной доступ дескриптора в поглотителе, хотя super().__bases__ будет работать тоже.

Выше, я hardcoded type, а не поиск MRO; Вы можете также использовать вспомогательную функцию, чтобы найти правильный дескриптор с полным перебором MRO:

class Meta(type): 
    def _find_super(cls, name): 
     mro = type(cls).__mro__ 
     idx = mro.index(__class__) 
     for base in mro[idx + 1:]: 
      if name in base.__dict__: 
       return base.__dict__[name] 
     return 

    @property 
    def __bases__(cls): 
     print('Getting __bases__ via Meta.__bases__') 
     return cls._find_super('__bases__').__get__(cls) 

    @__bases__.setter 
    def __bases__(cls, value): 
     print('Setting __bases__ via Meta.__bases__') 
     return cls._find_super('__bases__').__set__(cls, value) 

В любом случае, теперь вы можете перехватить __bases__ быть установлено:

>>> class A: pass 
... 
>>> class B: pass 
... 
>>> class C(A, B, metaclass=Meta): pass 
... 
>>> C.__bases__ 
Getting __bases__ via Meta.__bases__ 
(<class '__main__.A'>, <class '__main__.B'>) 
>>> C.__bases__ = (B, A) 
Setting __bases__ via Meta.__bases__ 
>>> C.__bases__ 
Getting __bases__ via Meta.__bases__ 
(<class '__main__.B'>, <class '__main__.A'>) 
+1

'__bases__' * * доступен для записи. Например, определение C без метакласса: '>>> класс C (A, B): пройти >>> C .__ bases__ = (B, A)' Но ваш пункт о 'супер()' не поддерживающие дескрипторы данных, объясняют «AttributeError». Благодарю. –

+0

@ SJPalt: Я стою исправлено! И на этой ноте я включил, как получить прямой доступ к дескриптору. –

+0

Это отлично, и работает как ожидалось. Я не думал о повторной реализации 'super()' like this. Спасибо за ваш ответ. –