我正在尝试使用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()中产生解析的行,但我真的更愿意返回这个'装饰'文件对象。
提前致谢。
答案 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)