如何通过IPython的嵌入使输入/关闭工作?

blu*_*e10 6 python import closures ipython

我有时会embed在脚本中的某个位置使用它来快速充实一些本地功能.最小的例子:

#!/usr/bin/env python

# ...

import IPython
IPython.embed()
Run Code Online (Sandbox Code Playgroud)

开发本地功能通常需要重新导入.但是,在函数中使用时,在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
Run Code Online (Sandbox Code Playgroud)

这是相当令人困惑的,特别是因为我甚至可以使用制表符完成来编写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
Run Code Online (Sandbox Code Playgroud)

模块名称可能只是"关闭"最常见的事情......

有没有解决这个问题的方法?

更新:问题不仅适用于闭包,还适用于嵌套列表推导.

免责声明:我会自己发布一个(不满意的)问题答案 - 但仍然希望有更好的解决方案.

Liu*_*Sha 1

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

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

ipy = get_ipython()
setattr(ipy.__class__, 'user_global_ns', property(lambda self: self.user_ns))
Run Code Online (Sandbox Code Playgroud)

另一种方法是子类化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
Run Code Online (Sandbox Code Playgroud)

并稍微修改 的代码,IPython.terminal.embed.embed()以便将其全部InteractiveShellEmbed更改为InteractiveShellEmbedEnhancedand call shell.init_frame(frame)after the line shell = InteractiveShellEmbed.instance(...)

这是基于以下观察:

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

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

通过此设置,以下代码应该可以工作,但不能使用默认的嵌入式 shell:

a=3
(lambda :a)()    # default behavior: name 'a' is not defined
import time
(lambda: time.time())()  # default behavior: name 'time' is not defined
Run Code Online (Sandbox Code Playgroud)

(默认行为是由于a并且time不会添加到本地函数(上面定义的 lambda)globals()并且ipython不会对其进行闭包,并且坚持在全局范围内查找变量。closure 在此页面中搜索)