定义自定义魔法命令

定义自定义魔法命令#

定义自己的魔法命令主要有两种方式:从独立的函数定义,或者通过继承 IPython 提供的基类:IPython.core.magic.Magics。下面展示了一些代码,可以将其放在文件中,并通过你的配置加载,例如放在默认 IPython 配置文件的 startup 子目录下的任意文件中。

首先,看看最简单的情况。以下展示了如何使用纯函数创建行魔法、单元魔法以及同时适用于两种模式的魔法命令:

from IPython.core.magic import (register_line_magic, register_cell_magic,
                                register_line_cell_magic)

@register_line_magic
def lmagic(line):
    "my line magic"
    return line

@register_cell_magic
def cmagic(line, cell):
    "my cell magic"
    return line, cell

@register_line_cell_magic
def lcmagic(line, cell=None):
    "Magic that works both as %lcmagic and as %%lcmagic"
    if cell is None:
        print("Called as line magic")
        return line
    else:
        print("Called as cell magic")
        return line, cell

# 在交互式会话中,需要删除这些以避免
# 自动魔法(automagic)在行魔法上工作时发生名称冲突。
del lmagic, lcmagic

你还可以通过继承 IPython.core.magic.Magics 类来创建所有三种类型的魔法命令。这使你可以创建能够在调用之间保持状态的魔法命令,并且能够完全访问主 IPython 对象:

Hide code cell content
# This code can be put in any Python module, it does not require IPython
# itself to be running already.  It only creates the magics subclass but
# doesn't instantiate it yet.
from __future__ import print_function
from IPython.core.magic import (Magics, magics_class, line_magic,
                                cell_magic, line_cell_magic)

# The class MUST call this class decorator at creation time
@magics_class
class MyMagics(Magics):

    @line_magic
    def lmagic(self, line):
        "my line magic"
        print("Full access to the main IPython object:", self.shell)
        print("Variables in the user namespace:", list(self.shell.user_ns.keys()))
        return line

    @cell_magic
    def cmagic(self, line, cell):
        "my cell magic"
        return line, cell

    @line_cell_magic
    def lcmagic(self, line, cell=None):
        "Magic that works both as %lcmagic and as %%lcmagic"
        if cell is None:
            print("Called as line magic")
            return line
        else:
            print("Called as cell magic")
            return line, cell


# In order to actually use these magics, you must register them with a
# running IPython.

def load_ipython_extension(ipython):
    """
    Any module file that define a function named `load_ipython_extension`
    can be loaded via `%load_ext module.path` or be configured to be
    autoloaded by IPython at startup time.
    """
    # You can register the class itself without instantiating it.  IPython will
    # call the default constructor on it.
    ipython.register_magics(MyMagics)

如果你想创建具有不同构造函数并持有额外状态的类,那么你应该始终调用父类构造函数并在注册之前实例化该类:

@magics_class
class StatefulMagics(Magics):
    "Magics that hold additional state"

    def __init__(self, shell, data):
        # You must call the parent constructor
        super(StatefulMagics, self).__init__(shell)
        self.data = data

    # etc...

def load_ipython_extension(ipython):
    """
    Any module file that define a function named `load_ipython_extension`
    can be loaded via `%load_ext module.path` or be configured to be
    autoloaded by IPython at startup time.
    """
    # This class must then be registered with a manually created instance,
    # since its constructor has different arguments from the default:
    magics = StatefulMagics(ipython, some_data)
    ipython.register_magics(magics)

访问用户命名空间和局部作用域#

在创建行魔法时,你可能需要访问周围的作用域以获取用户变量(例如在函数内部调用时)。IPython提供了 @needs_local_scope 装饰器,可以从 IPython.core.magic 导入。当用 @needs_local_scope 装饰时,魔法函数将被传递 local_ns 作为参数。为了方便起见,@needs_local_scope 也可以应用于单元魔法,即使单元魔法不能出现在局部作用域上下文中。

静默魔法输出

有时定义一个可以像非魔法表达式一样静默输出的魔法可能很有用,即通过在要执行的Python代码末尾附加分号来实现。这可以通过使用从 IPython.core.magic 导入的 @output_can_be_silenced 装饰器来实现。当使用此装饰器时,IPython将解析魔法使用的Python代码,如果最后一个标记是 ;,则魔法创建的输出将不会显示在屏幕上。如果你想查看此装饰器的实际应用示例,请查看 IPython.core.magics.execution.py 中定义的 time 魔法。

完整示例#

以下是完整的魔法包示例。你可以使用 setuptoolsdistutils 或任何其他分发工具(如 flit)来分发纯Python包的魔法。

当将魔法作为包的一部分分发时,推荐的最佳实践是在 load_ipython_extension 中执行注册,如下面示例所示,而不是直接在模块中注册(如最初使用 @register_* 装饰器的示例)。这意味着用户需要显式选择加载你的魔法,使用 %load_ext,而不是在导入模块时隐式获取它。这在加载魔法有副作用、加载速度慢或可能覆盖同名魔法时尤为重要。

.
├── example_magic
│   ├── __init__.py
│   └── abracadabra.py
└── setup.py
$ cat example_magic/__init__.py
"""An example magic"""
__version__ = '0.0.1'

from .abracadabra import Abracadabra

def load_ipython_extension(ipython):
    ipython.register_magics(Abracadabra)
$ cat example_magic/abracadabra.py
from IPython.core.magic import (Magics, magics_class, line_magic, cell_magic)

@magics_class
class Abracadabra(Magics):

    @line_magic
    def abra(self, line):
        return line

    @cell_magic
    def cadabra(self, line, cell):
        return line, cell