2012-05-01 21 views
0

我有一個用於分析我的運動數據(我刮runkeeper的網站)的API。在循環中動態添加方法時的範圍gotcha

我的主類是pandas.DataFrame的子類,它基本上是表格數據的容器。它支持按列名進行索引,並返回列值的數組。

我想添加一些基於數據中存在的「健身活動」類型的便利性。因此,例如,我想添加屬性「跑步」:

@property 
def running(self): 
    return self[self['type'] == 'running'] 

這將返回已在「類型」列「跑步」的DataFrame的所有行。

我試圖爲數據中存在的所有類型動態地執行此操作。這是我天真地做:

class Activities(pandas.DataFrame): 
    def __init__(self,data): 
     pandas.DataFrame.__init__(self,data) 
     # The set of unique types in the 'type' column: 
     types = set(self['type']) 
     for type in types: 
      method = property(lambda self: self[self['type'] == type]) 
      setattr(self.__class__,type,method) 

結果是,所有這些屬性的最終返回數據表爲(「走」)相同類型的活動。

發生什麼事是,當訪問屬性時,lambda被調用,並且它們在名稱'type'中定義的範圍內查找。他們發現它綁定到字符串'walking',因爲那是for循環的最後一次迭代。 for循環的每次迭代都沒有自己的命名空間,所以所有的lambda只能看到最後一次迭代,而不是'type'在實際定義時的值。

任何人都可以解決這個問題嗎?我能想到兩個,但他們似乎並不特別理想:

  1. 定義__getattr__檢查屬性是活動類型,並返回相應的行。

  2. 使用遞歸函數調用而不是for循環,以便每個遞歸級別都有自己的名稱空間。

這兩者都是我的口味有點太巧了,pandas.DataFrame已經有一個__getattr__,我不得不小心地用,如果我做一個太互動。遞歸會起作用,但是感覺非常錯誤,因爲這組類型沒有任何內在樹狀結構。它很平坦,應該在代碼中看起來平坦!

回答

2

修改lambda以將值拉入新範圍。

method = property(lambda self=self, type=type: self[self['type'] == type]) 
+0

完美,這正是我需要的!但是,如果這個對象有多個實例,lambda(self)的第一個參數可能不應該有默認集合。這是因爲,正如我剛剛發現的那樣,您必須爲類添加屬性而不是實例(在我的問題中編輯)。所以我們不希望該屬性指向特定的實例,否則這些對象將會返回對方的數據! –

0

我建議使數據幀的一個子類,說實話,如果你能避免它。在我的經驗中,古老的Java格言「傾向於構成而不是繼承」。

+0

我幾乎總是同意你的看法,但在這種情況下,我想要的對象是一個'DataFrame',唯一的區別就是添加了這些方法。 所以,如果我用組合物代替,我會逐字包裝每個'DataFrame'方法。我猜想,當你創建子類時,你經常只需要父類的一個子集,在這種情況下,我確實需要整個子類。 –