2014-09-02 51 views
0

我試圖使用types.MethodType來修改某些迭代器的行爲。types.MethodType和for循環

def parse(line): 
    return line.upper() 

def reader(f): 
    f.__next__ = types.MethodType(lambda x: parse(_io.TextIOWrapper.readline(x)), f) 
    f.__iter__ = types.MethodType(lambda x: x, f) 
    return f 

我想我正確地使用types.MethodType,因爲運行下面的代碼我得到預期的結果:

>>with open("myfile.txt") as f: 
>> x = reader(f) 
>> print(f.__next__()) 
NORMAL LINE 

然而,當我使用for循環,似乎不調用parse()函數。

>>with open("myfile.txt") as f: 
>> for line in reader(f): 
>>  print(line) 
normal line 

這是因爲如果for循環使用,而不是覆蓋我的一個對象的原始下一個()方法。

我在這裏錯過了什麼?我知道我可以以更簡單的方式實現相同的結果,例如在reader()中產生分析的行,但是我真的更喜歡返回這個'裝飾'文件對象。

在此先感謝。

+1

在'file'實例上更改方法不太可能;解釋器會在某些地方注意到何時存在內置類型,並快速切入某些方法查找。對於被設計爲子分類的'dict',它將爲覆蓋這些方法的子類執行「正確」的事情。但'file'不是爲此設計的。你運氣不好。 – SingleNegationElimination 2014-09-02 00:38:59

+1

@IfLoop你寫的是假的。 *解釋器*不會**「優化對內置插件的查找」。當它們被隱式調用時(例如在for循環中),它將* all *訪問*特殊方法*。然而,這是爲所有**類,而不僅僅是內置的,所以即使是繼承'file'代碼也能正常工作。你所描述的是用C編寫的其他*函數,它可能已經被優化了,但是這與手頭的問題和例子完全無關。 – Bakuriu 2014-09-02 07:29:17

回答

3

還有就是你的兩個例子之間的巨大差異。在第一個中你明確地調用了__next__方法,而在後者中你讓迭代器協議爲你調用它。事實上,你可以看到,即使在第一種情況下的行爲是不是你想要的:

In [5]: with open('myfile.txt') as f: 
    ...:  print(next(reader(f))) # next here calls the original implementation! 
normal line 

In [6]: with open('myfile.txt') as f: 
    ...:  print(reader(f).__next__()) 
NORMAL LINE 

你可以看到什麼解釋通過使用dis模塊檢查字節碼做。 例如:

In [8]: import dis 

In [9]: def f(): 
    ...:  for x in iterable: 
    ...:   pass 

In [10]: dis.dis(f) 
    2   0 SETUP_LOOP    14 (to 17) 
       3 LOAD_GLOBAL    0 (iterable) 
       6 GET_ITER 
     >> 7 FOR_ITER     6 (to 16) 
      10 STORE_FAST    0 (x) 

    3   13 JUMP_ABSOLUTE   7 
     >> 16 POP_BLOCK 
     >> 17 LOAD_CONST    0 (None) 
      20 RETURN_VALUE 

注意如何有到GET_ITER一個電話,但LOAD_ATTR沒有呼叫。但是,如果您明確提及屬性:

In [11]: def f(): 
    ...:  for x in iterable.__iter__(): 
    ...:   pass 

In [12]: dis.dis(f) 
    2   0 SETUP_LOOP    20 (to 23) 
       3 LOAD_GLOBAL    0 (iterable) 
       6 LOAD_ATTR    1 (__iter__) 
       9 CALL_FUNCTION   0 (0 positional, 0 keyword pair) 
      12 GET_ITER 
     >> 13 FOR_ITER     6 (to 22) 
      16 STORE_FAST    0 (x) 

    3   19 JUMP_ABSOLUTE   13 
     >> 22 POP_BLOCK 
     >> 23 LOAD_CONST    0 (None) 
      26 RETURN_VALUE 

請注意LOAD_ATTR字節碼。

當您看到LOAD_ATTR字節碼時,這意味着解釋器將對實例執行全面的屬性查找(並因此找到您剛剛設置的屬性)。 然而,像GET_ITER這樣的字節碼執行特殊的方法查找,它避免了實例屬性查找。

當口譯員作爲陳述的結果調用特殊方法時,他確實而不是在實例中查找它們,但在類中。這意味着他將而不是檢查您剛剛創建的__iter__屬性。

這是記錄在一些地方。例如object.__getattribute__,這是用來實現屬性查找的方法下,有一張紙條:

注意:此方法仍可能被繞過仰視時通過語言的語法特殊 方法爲隱式調用的結果或 內置函數。見Special method lookup

據我所知,因爲文件是用C寫的,你不能修改類的屬性,所以只需無法達到你想要的東西。

但是它是非常容易的,只需創建一個新的包裝類:

class Wrapper: 
    def __init__(self, fobj): 
     self.fobj = fobj 

    def __iter__(self): 
     return self 

    def __next__(self): 
     return parse(next(self.fobj)) 

另一種方法是創建文件的子類。在python3中,這樣做有點複雜,因爲你必須子類io.TextIOWrapper,它的構造函數需要一個緩衝區而不是文件名,所以它比python2更簡單。

但是,如果你確實創建了一個子類它可以正常工作。將實例傳遞給某些函數時可能會遇到一些問題,這些函數可能決定調用原始文件方法,但解釋器本身會調用您定義的方法__next____iter__

+0

感謝您的詳細評論。在等待答案的時候,我不知何故放棄了(我做的好事!),並且確實使用了一個類似你寫的封裝類。 – vermillon 2014-09-02 16:34:14

0

實例的修改方法對我來說看起來很棘手,我會盡量避免這種情況。如果你只需要一種預處理的文本文件,你可以在一個單獨的功能,如:

def preprocess(f): 
    for l in f: 
     yield parse(l) 

with open("myfile.txt") as f: 
    for line in preprocess(f): 
     print(line)