2016-10-13 8 views
3

В Python 2 с трюком можно создать класс с несколькими базами, хотя базы имеют метаклассы, которые являются не подклассами друг друга.Возможно ли динамическое создание метакласса для класса с несколькими базами в Python 3?

Фокус в том, что эти метаклассы сами по себе являются метаклассами (назовите это «метаметаклас»), и этот метаметалкласс предоставляет метаклассы методу вызова, который при необходимости динамически создает общий подметалкласс базовых метаклассов. В конце концов, результаты класса, чей метакласс является новым суб-метаклассом. Вот код:

>>> 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'> 

Этот пример (конечно, изменяя синтаксис для class C1(metaclass=Meta1) и т.д.) не работает в Python 3.

Вопрос 1: Правильно ли я понимаю, что в Python 2, первый C3 построен с использованием метакласса первой базы, и ошибка будет возникать только в том случае, если type(C3) не являются общим подклассом type(C1) и type(C2), тогда как в Python 3 ошибка возникает ранее?

Вопрос 2: (Как) Возможно ли, чтобы приведенный выше пример работал в Python 3? Я попытался использовать подкласс abc.ABCMeta в качестве метаметаллов, но даже если использование пользовательского __subclasscheck__ приведет к возврату issubclass(Meta1, Meta2)True, создание C3 все равно приведет к ошибке.

Примечания: Конечно, я мог бы сделать Python 3 счастливым статический определяя Meta1Meta2 и явно использовать его в качестве метакласса для C3. Однако этого я не хочу. Я хочу, чтобы общий суб-метакласс был создан динамически.

+0

Это немного похоже на сложность для создания сложных вещей :-). Почему вы хотите сделать это вместо более простого для чтения и понимания варианта, где вы создаете новый метакласс для 'C3', смешивая метаклассы C1 и CF2 явно? – mgilson

+0

Я разработчик SageMath, языком пользователя которого в настоящее время является Python 2.7. В этом CAS используются разные метаклассы, реализующие отдельные функции. Скажем, пять функций, что означает, что существует 32 возможных сочетания функций. Немного неудобно статически определять каждую возможную комбинацию, и на самом деле SageMath в настоящее время предоставляет только * некоторые * комбинации. Кроме того, создание новых классов должно быть легким для пользователя: пользователю не нужно будет искать метаклассы всех баз и явно выбирать правильный комбинированный метакласс. –

ответ

0

Вот пример, который показывает некоторые параметры, которые у вас есть в python3.x. В частности, у C3 есть метакласс, который создается динамически, но во многом, явно. C4 имеет метакласс, который динамически создается в метаклассе функции. C5 - это просто показать, что у него тоже есть те же свойства, что и у C4. (Мы ничего через наследование, которое может произойти, если вы используете функцию метакласса вместо type не потерять ...)

class Meta1(type): 
    def foo(cls): 
     print(cls) 


class Meta2(type): 
    def bar(cls): 
     print(cls) 


class C1(object, metaclass=Meta1): 
    """C1""" 


class C2(object, metaclass=Meta2): 
    """C2""" 


class C3(C1, C2, metaclass=type('Meta3', (Meta1, Meta2), {})): 
    """C3""" 

def meta_mixer(name, bases, dct): 
    cls = type('MixedMeta', tuple(type(b) for b in bases), dct) 
    return cls(name, bases, dct) 


class C4(C1, C2, metaclass=meta_mixer): 
    """C4""" 


C1.foo() 
C2.bar() 

C3.foo() 
C3.bar() 

C4.foo() 
C4.bar() 

class C5(C4): 
    """C5""" 

C5.foo() 
C5.bar() 

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

+0

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

+0

FYI: Я согласен с вами, но, пожалуйста, см. Мой ответ. В Python 3.6 есть захватывающая разработка. –

+1

@NeilG - О, аккуратно. Сначала я подумал, что вы что-то говорите в PEP 0487 (у меня не было много времени, чтобы посмотреть хотя бы/grok). Похоже, в python3.6 происходит много, когда дело доходит до создания класса ... – mgilson

1

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

Но вместо того, чтобы усложнять ситуацию (я признаюсь, я не мог окутать голову вашей потребности в мета-мета-классе) - вы можете просто использовать обычную иерархию классов с совместным использованием super для ваших метаклассов. Вы можете даже создать окончательный метакласса динамически с помощью простого вызова type:

class A(type): 
    def __new__(metacls, name, bases,attrs): 
     attrs['A'] = "Metaclass A processed" 
     return super().__new__(metacls, name, bases,attrs) 


class B(type): 
    def __new__(metacls, name, bases,attrs): 
     attrs['B'] = "Metaclass A processed" 
     return super().__new__(metacls, name, bases,attrs) 


C = type("C", (A, B), {}) 

class Example(metaclass=C): pass 

И:

In[47] :Example.A 
Out[47]: 'Metaclass A processed' 

In[48]: Example.B 
Out[48]: 'Metaclass A processed' 

Если метаклассы не предназначены для совместной работы, в первую очередь, это будет очень сложно создать какой-либо автоматический метод для их комбинирования - и, возможно, это потребует обезвреживания исправления вызова до type.__new__ в некоторых конструкторах метаклассов.

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

def Auto(name, bases, attrs): 
    basemetaclasses = [] 
    for base in bases: 
     metacls = type(base) 
     if isinstance(metacls, type) and metacls is not type and not metacls in basemetaclasses: 
      basemetaclasses.append(metacls) 
    dynamic = type(''.join(b.__name__ for b in basemetaclasses), tuple(basemetaclasses), {}) 
    return dynamic(name, bases, attrs) 

(Этот код очень похож на ваш, - но я использовал три строки явного for вместо set, чтобы сохранить порядок метакласса - что может материя)

У вас есть их передать Авто как метакласса для производных классов, но в остальном он работает как в вашем примере:

In [61]: class AA(metaclass=A):pass 

In [62]: class BB(metaclass=B):pass 

In [63]: class CC(AA,BB): pass 
--------------------------------------------------------------------------- 
... 
TypeError: metaclass conflict 
... 

In [66]: class CC(AA,BB, metaclass=Auto): pass 

In [67]: type(CC) 
Out[67]: __main__.AB 

In [68]: CC.A 
Out[68]: 'Metaclass A processed' 
+0

Конечно, я не говорю о «произвольных» метаклассах. Я говорю о системе компьютерной алгебры, которая предоставляет пару метаклассов, каждая из которых предоставляет единую функцию для создания класса, и они должны быть сконструированы таким образом, чтобы функции могли свободно объединяться. –

+0

И спасибо за то, что вы указали на совместное использование 'super()'. Но может ли быть достигнуто, что 'C' не нужно строить явно? Я имею в виду, имея экземпляр 'ExpA'' A' и 'ExpB'' B', а затем выполнив «Пример класса (ExpA, ExpB): pass', а не« class Example »(ExpA, ExpB, metaclass = C) : pass'? –

+0

Ах, мое обновление с динамическим созданием тоже почти что-то, что @mgilson написал ниже. – jsbueno

0

в 95% случаев, это должно быть возможно использовать технику, введенную в Python 3.6 из-за PEP 447 делать большинство того, что метаклассы могут делать с помощью специальных новых крючков. В этом случае вам не нужно будет комбинировать метаклассы, так как ваши крючки могут вызвать супер, и их поведение сочетается из-за наследования.

Что касается вашего общего случая, я считаю, что милльсон прав, и вы, вероятно, делаете вещи слишком сложными. Я еще не видел случая для объединения метаклассов, которые не охватываются PEP 447.

 Смежные вопросы

  • Нет связанных вопросов^_^