2016-10-13 37 views
3

在Python 2中,用一個技巧就可以創建一個帶有幾個鹼基的類,儘管鹼基的元類是而不是的子類。在Python 3中,是否有可能爲具有多個基類的類動態創建元類?

訣竅是這些元類本身具有一個元類(將其命名爲「metametaclass」),並且此metameta類爲元類提供了一個調用方法,以便在必要時動態創建基類元類的公共子元類。最終,一個類的結果,其元類是新的子元類。下面是代碼:

>>> 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:(How)是否有可能使上面的例子在Python 3中工作?我確實嘗試使用abc.ABCMeta的子類作爲metameta類,但即使使用定製__subclasscheck__使得issubclass(Meta1, Meta2)返回True,但創建C3仍然會導致錯誤。

注:我當然可以使Python 3高興通過靜態定義Meta1Meta2並明確利用它作爲C3元類。但是,那不是我想要的。我希望公共的子元類是動態創建的。

+0

爲了使事情變得複雜起見,這感覺有點像複雜性:-)。爲什麼你想這樣做,而不是通過明確地將'C1'和'C2'的元類混合在一起來爲'C3'創建一個新的元類的更容易閱讀和理解的變體? – 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() 

應當指出的是,我們是在玩火這裏(就像您在原始示例中使用火一樣)。不能保證元類會在合作多重繼承中很好地發揮作用。如果他們不是爲它設計的,那麼很有可能你會遇到一些bug,使用這個在的一些點。如果他們是爲它設計的,那麼沒有理由做這種黑客運行時混合:-)。

+0

我知道一個動態元類框架的實現是hacky,但我認爲它會讓最終用戶的生活更加輕鬆。 –

+0

供參考:我同意你的意見,但請看我的答案。 Python 3.6中有一個令人興奮的發展。 –

+1

@NeilG - 哦整齊。起初我以爲你正在談論PEP 0487中的一些事情(我沒有花太多時間去看看/ grok)。 python3.6在創建類時似乎有很多... – mgilson

1

在Python3中,當使用元類時,它必須準備好,並且無法知道最終(非元)類的基礎,以便在該點動態創建元類。你可以簡單地使用普通的類層次結構,爲你的元類使用super的協同使用。你可以簡單地使用普通的類層次結構,爲你的元類使用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'不需要明確構造嗎?我的意思是,有一個''A'和'B'實例'ExpA',然後做'class Example(ExpA,ExpB):pass'而不是'class Example(ExpA,ExpB,metaclass = C) :通過? –

+0

啊,我對動態創作的更新也幾乎與@mgilson在下面發佈的內容相同。 – jsbueno

0

95%的病例,應該可以使用在Python 3.6引入了機械由於PEP 447大多數元類可以使用特殊的新鉤子。在這種情況下,你不需要組合元類,因爲你的鉤子可以調用super,並且他們的行爲由於繼承而組合在一起。

至於你的一般情況,我相信mgilson是正確的,你可能使事情太複雜。我還沒有看到結合PEP 447未涵蓋的元類的情況。

相關問題