types.MethodType和for循环

时间:2014-09-02 00:01:24

标签: python

我正在尝试使用types.MethodType来修改某些迭代器的行为。

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循环使用了我的对象的原始 next ()方法而不是被覆盖的方法。

我在这里缺少什么?我知道我可以用更简单的方式获得相同的结果,例如在reader()中产生解析的行,但我真的更愿意返回这个'装饰'文件对象。

提前致谢。

2 个答案:

答案 0 :(得分: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

AFAIK,因为文件是用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__方法。

答案 1 :(得分:0)

修改实例的方法看起来很棘手,我会尽量避免这种情况。如果您只需要对文本文件进行预处理,可以在单独的函数中执行,例如:

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

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