2011-11-23 43 views
4

我正在嘗試使用PEP 3115(即,可以按聲明順序訪問其成員的類)中描述的'ordered class'。給出的實現是Python 3中的有序類

# The custom dictionary 
class member_table(dict): 
    def __init__(self): 
     self.member_names = [] 

    def __setitem__(self, key, value): 
     # if the key is not already defined, add to the 
     # list of keys. 
     if key not in self: 
      self.member_names.append(key) 

     # Call superclass 
     dict.__setitem__(self, key, value) 

# The metaclass 
class OrderedClass(type): 

    # The prepare function 
    @classmethod 
    def __prepare__(metacls, name, bases): # No keywords in this case 
     return member_table() 

    # The metaclass invocation 
    def __new__(cls, name, bases, classdict): 
     # Note that we replace the classdict with a regular 
     # dict before passing it to the superclass, so that we 
     # don't continue to record member names after the class 
     # has been created. 
     result = type.__new__(cls, name, bases, dict(classdict)) 
     result.member_names = classdict.member_names 
     return result 

class MyClass(metaclass=OrderedClass): 
    # method1 goes in array element 0 
    def method1(self): 
     pass 

    # method2 goes in array element 1 
    def method2(self): 
     pass 

有幾件事我很困惑。首先,__prepare__classmethod的原因是什麼?該定義不使用metacls - 這只是一個約定?

其次,當我嘗試此代碼,'__module__'最終在MyClass.member_names'method1''method2'之前,顯然與該權利要求'method1'是第一要素的評論。爲什麼這個特殊的屬性會出現在列表中,而沒有其他的屬性呢?有沒有其他人可能會讓我感到驚訝(除了__doc__,如果該類有一個文檔字符串,並且我明確定義)?

最後,此實現不從基類檢索member_names。如果我想達到這個目的,那麼對__prepare__進行如下更改會有什麼不妥(除了不檢查重複的事實)嗎?

@classmethod 
def __prepare__(metacls, name, bases): 
    prep_dict = member_table() 
    for base in bases: 
     try: 
      prep_dict.member_names.extend(base.member_names) 
     except AttributeError: 
      pass 
    return prep_dict 
+1

'__prepare__'將與2個參數一起使用,但當被稱爲MyClass .__ prepare__'時,它將作爲MyClass的方法綁定。無論你是否會這樣做,最好將它綁定到元類並使用第一個參數來獲取'mcls'實例。我想你可以把它變成一個「靜態方法」。 – eryksun

+2

PEP表示「在評估類體之前,將」__prepare__「屬性作爲函數調用」。所以它必須是classmethod或staticmethod,而不是實例方法,因爲此時尚未調用(實例化)元類。 – yak

+0

感謝eryksun和犛牛,但我不確定自己完全理解 - 我想我對'classmethod'確實有什麼天真的想法。我明白,如果'__prepare__'沒有被聲明爲'classmethod',它接收'name'和'bases'作爲參數,而如果它是'classmethod'它也會接收這個類 - 但是classmethod有什麼其他效果在這種情況下?另外,爲什麼從元類繼承的方法不會出現在'dir(MyClass)'中? – James

回答

5

首先,是有一個原因__prepare__是類方法?定義不使用metacls - 這只是一個約定嗎?

正如在評論中指出的那樣,當調用__prepare__時,類本身還沒有實例化。這意味着,如果它在一個普通的方法(以self作爲第一個參數) - 在這個調用中沒有價值成爲self

它是有道理的,因爲__prepare__所做的就是返回一個類似於對象的字典而不是普通的字典來解析類體 - 所以它不依賴於所有正在創建的類。

如果像在評論中提到的,它是一個staticmethod(這意味着它不會得到metacls第一個參數),那麼__prepare__將無法​​訪問該元類的任何其他方法 - 這是不必要的限制。

是的,就像selfmetacls在這種情況下只是一個約定 - 一個普通的Python標識符。 (請注意,當classmethods普通類使用,第一個參數通常表示的cls代替metacls。)

其次,當我嘗試這個代碼,__module__method1method2之前結束在MyClass.member_names,顯然與要求method1是第一要素的意見相矛盾。爲什麼這個特殊的屬性會出現在列表中,而沒有其他的屬性呢?有沒有其他人可能會讓我感到驚訝(除了__doc__,如果該類有一個文檔字符串,並且我明確定義)?

大概是因爲上課的__module__特殊成員被認爲關閉後,他們認爲有關的元類方法__prepare__。 (我無法通過谷歌搜索來確定__module__的PEP,但我敢打賭,情況就是這樣。)

不,不能保證未來版本的Python不會添加更多魔術屬性到可能出現在字典中的顯式類成員之前的類。例如,你可以完全控制 - 你可以調整你的字典,如對象 (member_table在你的代碼中),不計算任何以「__」開頭的屬性。或者甚至不要添加到最終的類實例中(可能會導致您的類定義爲不使用某些Python功能)。

最後,此實現不從基類中檢索member_names。如果我想達到這個目的,__prepare__有什麼不對嗎?

通過閱讀它,我發現您的提議實現沒有任何問題,但是您當然必須對其進行測試。

更新(2013-06-30):這個問題正在積極的Python開發列表在Python 3.4的討論,並itlooks像所有的類都被默認排序,無需使用元類或__prepare__僅用於訂購

+0

底層數據結構是什麼?一個命令? – Marcin