2015-12-26 20 views
0

我編寫了一個metaclass,它定義了在運行時使用它的類的屬性。它只是讓從REQUIRED_KEYS這些屬性的屬性,然後宣佈property,例如:在Python中使用元類,setattr和屬性以及奇怪的行爲

class Base(object): 
    REQUIRED_KEYS =() 

    class __metaclass__(type): 
     def __init__(cls, name, bases, nmspc): 

      type.__init__(cls, name, bases, nmspc) 
      for attr in cls.REQUIRED_KEYS: 
       setattr(cls, attr, property(lambda self: self._dict.get(attr, None))) 

    def __init__(self, **kwargs): 
     self._dict = dict(**kwargs) 


class Config(Base): 
    REQUIRED_KEYS = ('foo', 'bar') 

如果一個鍵沒有給出在運行時定義的屬性返回None值。我會預期正確的行爲以下執行,返回1None

config = Config(**{'foo': 1}) 
config.foo 
config.bar 

但對於foo屬性在正確的價值預計將1返回無。

如果我通過一個閉包修改property函數使用的lambda,如下面的代碼片段所示,它工作正常。

class __metaclass__(type): 
    def __init__(cls, name, bases, nmspc): 

     def get(attr): 
      def _get(self): 
       return self._dict.get(attr, None) 
      return _get 

     type.__init__(cls, name, bases, nmspc) 
     for attr in cls.REQUIRED_KEYS: 
      setattr(cls, attr, property(get(attr))) 

繼第一個片段的執行,我意識到,lambda函數總是調用最後attr值。這意味着當我們試圖訪問foo屬性時,lambda使用bar值。

有人知道這裏發生了什麼嗎?

回答

2

這是關閉如何在Python中工作。

值的attr沒有存儲在拉姆達,只對可變的參考。

在你的代碼中所有的lambda表達式包含相同的參考attr變量,當拉姆達被調用時,它將包含其最後值。

您可以通過使用默認參數強制值的副本:

setattr(cls, attr, property(lambda self, attr=attr: self._dict.get(attr, None))) 
             ^^^^^^^^^ 
+0

謝謝,我沒有在這裏參考瞭解這個問題。 – pfreixes

0

PEP 227,如果名字是一個代碼塊中使用,但它並不必然存在(並沒有聲明全局),該用法被視爲對最近的封閉函數的引用。 (格式化我的)。名稱attr未在lambda getter中聲明,因此它是的引用變量attr中的元類__init__函數,該函數始終指向最後一個屬性。此外,dict(**kwargs)(在Base類初始化函數)是沒有必要的,因爲kwargs已經是一個字典(您可以鍵入self._dict = kwargs),並在你的榜樣,你cank初始化類爲Config(foo=1)代替Config(**{'foo':1})

+0

關於第二種情況,給出一個隱含的句子來構建一個kwars或明確的dictionari是不同的方式來做同樣的事情,但不是一個問題。關於最後一條評論,'''Config(** {'foo':'1'})'''實際上來自'''d = {'foo':1};配置(** d)'''只是放鬆了我的評論,使其可讀性。 – pfreixes