2014-09-02 54 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)