如何在 Python3 中检查 repl/ipython 中的生成器
How to inspect generators in the repl/ipython in Python3
我一直在尝试切换到 Python3。令人惊讶的是,我的困难不在于模块或我自己的代码破坏。我的问题是,在编写代码时,我总是在 IPython 中尝试和测试我的代码的不同方面,默认情况下使用生成器会让人恼火。我希望我的知识存在差距,或者有某种解决方法可以解决这个问题。
我的问题是:
每当我测试几行代码或一个函数并得到一个生成器时,我都不知道里面是什么,因为我收到这样的响应:<generator object <genexpr> at 0x0000000007947168>
。绕过它意味着我不能直接从我的编辑器 运行 代码——我需要将输出转储到一个变量 and/or 将它包装在 list().
一旦我开始检查生成器,我要么(全部或部分)消耗它,如果我想进一步测试它,它就会把它弄乱。部分消耗特别烦人,因为有时我没有注意到并看到后续代码的奇怪结果。
奇怪的是,我一直发现我引入了错误(或无关代码),不是因为我不理解惰性求值,而是因为我在控制台中求值的内容与造成它的原因不匹配进入我的编辑器的方式从我的视图中滑过。
在我的脑海中,我想执行以下操作之一:
- 以某种方式配置 IPython 以强制执行某种严格的评估(除非我明确关闭它)
- 在不消耗发电机的情况下检查它(或者检查它然后自行重启?)
在一般情况下,您的预览或倒带发电机的想法是不可能的。这是因为生成器可能有副作用,您可能会比预期更早(预览时)或多次(倒带前后)。考虑以下生成器,例如:
def foo_gen():
print("start")
yield 1
print("middle")
yield 2
print("end")
如果您可以预览此生成器(1
和 2
)产生的结果,您是否也希望得到打印输出?
也就是说,您可能有一些方法可以让您的代码更易于处理。
考虑使用列表理解而不是生成器表达式。在大多数情况下这很简单,只需将您已有的 genexp 放在方括号中即可。在您将生成器传递给其他代码的许多情况下,任何可迭代对象(例如 list
)都可以正常工作。
同样,如果您要从其他地方将生成器传递到代码中,通常可以将生成器传递到 list
并在以后的代码中使用该列表。这当然不是很有效的内存,因为你在前面消耗了整个生成器,但如果你想在交互式控制台中查看值,那可能是必要的。
您还可以使用 itertools.tee
获取两个(或更多)迭代器,它们将产生与您传入的可迭代对象相同的值。这将允许您检查一个的值,同时传递另一个在。请注意,tee
代码将需要存储任何迭代器产生的所有值,直到它也被所有其他迭代器产生(所以如果你 运行 一个迭代器远远领先于其他人,您最终可能会使用与仅使用 list
).
一样多或更多的内存
万一它对其他人有帮助,这是 IPython 我拼凑起来以回应答案的魔法。它使疼痛减轻一点:
%ins <var>
将使用 itertools.tee
创建 <var>
的两个副本。一个将被重新分配给 <var>
(因此您可以在其原始状态下重新使用它),另一个将被传递给 print(list()) 因此它输出到终端。
%ins <expr>
将表达式传递给 print(list())
要安装,请在 ~/.ipython/profile_default/startup
中另存为 ins.py
from IPython.core.magic import register_line_magic
import itertools
@register_line_magic
def ins(line):
if globals().get(line, None):
gen1, gen2 = eval("itertools.tee({})".format(line))
globals()[line] = gen2
print(list(gen1))
else:
print(list(eval(line)))
# You need to delete this item from the namespace
del ins
我一直在尝试切换到 Python3。令人惊讶的是,我的困难不在于模块或我自己的代码破坏。我的问题是,在编写代码时,我总是在 IPython 中尝试和测试我的代码的不同方面,默认情况下使用生成器会让人恼火。我希望我的知识存在差距,或者有某种解决方法可以解决这个问题。
我的问题是:
每当我测试几行代码或一个函数并得到一个生成器时,我都不知道里面是什么,因为我收到这样的响应:
<generator object <genexpr> at 0x0000000007947168>
。绕过它意味着我不能直接从我的编辑器 运行 代码——我需要将输出转储到一个变量 and/or 将它包装在 list().一旦我开始检查生成器,我要么(全部或部分)消耗它,如果我想进一步测试它,它就会把它弄乱。部分消耗特别烦人,因为有时我没有注意到并看到后续代码的奇怪结果。
奇怪的是,我一直发现我引入了错误(或无关代码),不是因为我不理解惰性求值,而是因为我在控制台中求值的内容与造成它的原因不匹配进入我的编辑器的方式从我的视图中滑过。
在我的脑海中,我想执行以下操作之一:
- 以某种方式配置 IPython 以强制执行某种严格的评估(除非我明确关闭它)
- 在不消耗发电机的情况下检查它(或者检查它然后自行重启?)
在一般情况下,您的预览或倒带发电机的想法是不可能的。这是因为生成器可能有副作用,您可能会比预期更早(预览时)或多次(倒带前后)。考虑以下生成器,例如:
def foo_gen():
print("start")
yield 1
print("middle")
yield 2
print("end")
如果您可以预览此生成器(1
和 2
)产生的结果,您是否也希望得到打印输出?
也就是说,您可能有一些方法可以让您的代码更易于处理。
考虑使用列表理解而不是生成器表达式。在大多数情况下这很简单,只需将您已有的 genexp 放在方括号中即可。在您将生成器传递给其他代码的许多情况下,任何可迭代对象(例如 list
)都可以正常工作。
同样,如果您要从其他地方将生成器传递到代码中,通常可以将生成器传递到 list
并在以后的代码中使用该列表。这当然不是很有效的内存,因为你在前面消耗了整个生成器,但如果你想在交互式控制台中查看值,那可能是必要的。
您还可以使用 itertools.tee
获取两个(或更多)迭代器,它们将产生与您传入的可迭代对象相同的值。这将允许您检查一个的值,同时传递另一个在。请注意,tee
代码将需要存储任何迭代器产生的所有值,直到它也被所有其他迭代器产生(所以如果你 运行 一个迭代器远远领先于其他人,您最终可能会使用与仅使用 list
).
万一它对其他人有帮助,这是 IPython 我拼凑起来以回应答案的魔法。它使疼痛减轻一点:
%ins <var>
将使用 itertools.tee
创建 <var>
的两个副本。一个将被重新分配给 <var>
(因此您可以在其原始状态下重新使用它),另一个将被传递给 print(list()) 因此它输出到终端。
%ins <expr>
将表达式传递给 print(list())
要安装,请在 ~/.ipython/profile_default/startup
ins.py
from IPython.core.magic import register_line_magic
import itertools
@register_line_magic
def ins(line):
if globals().get(line, None):
gen1, gen2 = eval("itertools.tee({})".format(line))
globals()[line] = gen2
print(list(gen1))
else:
print(list(eval(line)))
# You need to delete this item from the namespace
del ins