2012-01-15 79 views
6

我有一個奇怪的和不常見的用例,我想在基類的__metaclass__被定義後修改它的子類,使它的子類自動使用新的__metaclass__。但是,奇怪的是不起作用:爲什麼我無法更改類的__metaclass__屬性?

class MetaBase(type): 
    def __new__(cls, name, bases, attrs): 
     attrs["y"] = attrs["x"] + 1 
     return type.__new__(cls, name, bases, attrs) 

class Foo(object): 
    __metaclass__ = MetaBase 
    x = 5 

print (Foo.x, Foo.y) # prints (5, 6) as expected 

class MetaSub(MetaBase): 
    def __new__(cls, name, bases, attrs): 
     attrs["x"] = 11 
     return MetaBase.__new__(cls, name, bases, attrs) 

Foo.__metaclass__ = MetaSub 

class Bar(Foo): 
    pass 

print(Bar.x, Bar.y) # prints (5, 6) instead of (11, 12) 

我在做什麼很可能是不明智的/不支持/不確定的,但我不能爲我的生活弄清楚如何舊元類被調用,我想最不瞭解的可能性。

編輯:基於由jsbueno提出的建議,我替換爲以下行的行Foo.__metaclass__ = MetaSub,這也正是我想要的東西:

Foo = type.__new__(MetaSub, "Foo", Foo.__bases__, dict(Foo.__dict__)) 

回答

2

類的元類信息在創建時使用(或者作爲類塊進行解析,或者對元類進行顯式調用時動態地使用)。它不能改變,因爲元類通常會在類創建時進行更改 - 創建的類類型是元類型。它的__metaclass__屬性一旦創建就無關緊要。

但是,可以創建給定類的副本,並使該副本具有與原始類不同的metclass。

在你的榜樣,如果不是做:

Foo.__metaclass__ = MetaSub

你這樣做:

Foo = Metasub("Foo", Foo.__bases__, dict(Foo.__dict__))

你會實現你的目的是什麼。新的Foo適用於所有與其前任相同的效果,但具有不同的元類。

然而,Foo以前存在的情況下,將不考慮新的Foo的實例 - 如果你需要,你最好使用不同的名稱創建Foo複印件。

+0

感謝您的提示,這並沒有做到我想要的,因爲我仍然希望'Foo'的子類具有元類,但是我已經編輯了我的問題以包含根據您的需要做了我需要的解決方案建議。 – 2012-01-15 06:32:34

3

的問題是不使用__metaclass__屬性時繼承,違揹你所期望的。對於Bar,「舊」元類別不會被調用。該文件說以下有關元類是如何發現:

適當元類是由以下優先 規則確定:

  • 如果字典存在,它是用來[「__ metaclass__」]。
  • 否則,如果至少有一個基類,則使用其元類(這首先查找__class__屬性,如果找不到,則使用其類型)。
  • 否則,如果存在名爲__metaclass__的全局變量,則會使用它。
  • 否則,使用舊式的經典元類(types.ClassType)。

那麼在你Bar類實際上作爲元類的父的__class__屬性,而不是在父母的__metaclass__屬性被發現。

更多信息可在this StackOverflow answer上找到。

1

子類使用父母的__metaclass__

您的使用案例的解決方案是對父級的__metaclass__進行編程,以便它對於父級的行爲不同於其子類。也許讓它檢查類字典中的類變量,並根據其值執行不同的行爲 (這是技術類型用於根據是否定義是否定義實例來控制實例是否有字典)。

+0

在我的實際示例中,父類來自第三方模塊,我想從該模塊擴展一個類,但擁有自己的元類,它覆蓋了第三方模塊元類的一些行爲。否則,我一定會做你的建議。 – 2012-01-15 04:53:33

+0

@EliCourtwright也許你可以推出自己的類,委託給第三方模塊,而不是繼承它。這應該讓您可以完全控制類創建過程,同時最大限度地實現代碼重用。 – 2012-01-15 05:01:02

相關問題