如何从 IPython 的嵌入中导入/关闭?

How to make imports / closures work from IPython's embed?

我有时会在脚本的某个点使用 embed 来快速充实一些本地功能。最小示例:

#!/usr/bin/env python

# ...

import IPython
IPython.embed()

开发本地功能通常需要重新导入。但是,在函数中使用时,在 IPython 会话中导入模块似乎不起作用。例如:

In [1]: import os

In [2]: def local_func(): return os.path.sep

In [3]: local_func()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-f0e5d4635432> in <module>()
----> 1 local_func()

<ipython-input-2-c530ce486a2b> in local_func()
----> 1 def local_func(): return os.path.sep

NameError: global name 'os' is not defined

这相当令人困惑,尤其是因为我什至可以使用制表符补全来编写 os.path.sep

我注意到这个问题更为根本:一般来说,在 IPython 嵌入会话中创建的函数不会关闭来自嵌入范围的变量。例如,这也失败了:

In [4]: x = 0

In [5]: def local_func(): return x

In [6]: local_func()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-6-f0e5d4635432> in <module>()
----> 1 local_func()

<ipython-input-5-2116e9532e5c> in local_func()
----> 1 def local_func(): return x

NameError: global name 'x' is not defined

模块名称可能只是 "close over"...

最常见的东西

这个问题有什么解决办法吗?

更新:问题不仅适用于闭包,而且nested list comprehensions

免责声明:我将 post 自己对问题给出一个(不令人满意的)答案——不过仍然希望有更好的解决方案。

更新:同样只是一个解决方法,但更简单一些:globals().update(locals())


我没有通用的解决方案,但至少有一个变通方法:定义一个本地函数后,可以将会话的 locals() 添加到会话的 func_globals刚刚定义的函数,例如:

In [1]: import os

In [2]: def local_func(): return os.path.sep

In [3]: local_func.func_globals.update(locals())

In [4]: local_func()
Out[4]: '/'

但是,应该注意这只是一个 "manual closure",在这种情况下不能用作常规闭包:

In [1]: x = 1

In [2]: def local_func(): return x

In [3]: local_func.func_globals.update(locals())

In [4]: local_func()
Out[4]: 1

In [5]: x = 42

In [6]: local_func() # true closure would return 42
Out[6]: 1

In [7]: local_func.func_globals.update(locals()) # but need to update again

In [8]: local_func()
Out[8]: 42

至少它可以解决臭名昭著的 global name '...' is not defined 进口问题。

我也遇到了同样的问题。我用这个技巧来处理在函数外部调用 embed() 的情况,因此 globals()locals() 应该是同一个字典。

最简单的方法是在ipython启动后调用下面的函数

ipy = get_ipython()
setattr(ipy.__class__, 'user_global_ns', property(lambda self: self.user_ns))

另一种方法是subclass InteractiveShellEmbed

class InteractiveShellEmbedEnhanced(InteractiveShellEmbed):
    @property
    def user_global_ns(self):
        if getattr(self, 'embedded_outside_func', False):
            return self.user_ns
        else:
            return self.user_module.__dict__

    def init_frame(self, frame):
        if frame.f_code.co_name == '<module>':
            self.embedded_outside_func = True
        else:
            self.embedded_outside_func = False

并稍微修改 IPython.terminal.embed.embed() 的代码,以便将其中的所有 InteractiveShellEmbed 更改为 InteractiveShellEmbedEnhanced 并在 shell = InteractiveShellEmbed.instance(...) 行之后调用 shell.init_frame(frame)

这是基于以下观察:

  • 在 ipython 会话中,我们总是有 id(globals()) == id(ipy.user_module.__dict__) == id(ipy.user_global_ns)user_global_ns 是 class 属性 的超 class InteractiveShellEmbed,其中 returns ipy.user_module.__dict__)
  • 我们还有id(locals()) == id(ipy.user_ns)
  • 对于正常的 ipython 会话,id(locals()) == id(globals())
  • user_global_ns(一个属性)和user_ns(一个dict)定义执行上下文
  • 在嵌入式 ipython 中,ipy.user_moduleipy.user_ns 在函数 ipy.__call__() 中设置并传递给 ipy.mainloop()。它们不是同一个对象,因为 ipy.user_ns 是在函数内部构造的。

如果您要在函数外部(例如在脚本中)启动 ipython,那么可以安全地假设 globals() 应该与 locals().[=43 相同=]

使用此设置,以下代码在使用默认嵌入式 shell 不工作时应该可以工作:

a=3
(lambda :a)()    # default behavior: name 'a' is not defined
import time
(lambda: time.time())()  # default behavior: name 'time' is not defined

(默认行为是由于 atime 未添加到 globals() 并且 ipython 不会对局部函数(上面定义的 lambda)进行闭包并坚持在全局范围内查找变量。搜索 closure in this page)