jupyter notebook :一个交互式计算和开发环境

一、 IPython基础

代码自动补全:Tab键

可补全内容包括:变量名、函数名、成员变量函数、目录文件

内省(Itrospection)

  • 在变量名之前或之后加上问号(?),这样可以显示这个对象的相关信息。
    如果这个对象是个函数或实例方法,那么它的docstring也会被显示出来。
    • 使用??还将显示该函数的源代码
  • ?还能搜索IPython命名空间。一些字符再配以通配符(*)即可显示出所有与其相匹配的名称。
    如,我们可以列出NumPy顶级命名空间中含有load的所有函数:
    np.load?

%run 命令

在IPython环境中,所有文件都可以通过%run命令当做Python程序来运行。
脚本的行为和在标准命令行环境(通过python xxx.py启动的)中执行时一样,此后,在文件中所定义的全部变量就可以在当前IPython shell中访问了。

  • Ctrl-C 终止当前正在执行的代码(引发一个KeyboardInterrupt)
    IPython有一些特殊的命令。有的为常见任务提供便利,有的则使你能够轻松控制IPython系统的行为。

魔术命令(以%为前缀的命令)

  • 魔术命令可以看做运行于IPython系统中的命令行程序,它们大都还有一些参数选项。在命令后面加问号(?)可以查看。
  • 魔术命令默认是可以不带百分号使用的,只要没有定义与其同名的变量即可。这个功能可以通过%automagic命令打开或者关闭。
  • 通过%quickref%magic命令可以查看所有的命令。

常用的魔术命令如下:

  • %quickref thon快速参考
  • %magic 显示magic command详细文档
  • %debug 从最新的异常跟踪的底部进入交互式调试器
  • %hist 打印命令输入历史
  • %pdb 在发生异常后自动进入调试器
  • %paste 执行剪贴板中的Python代码
  • %cpaste 打开一个特殊的提示符以便手工粘贴待执行的代码
  • %reset 删除interactive空间中的全部变量/名称
  • %run 执行一个python脚本
  • %page 分页显示一个对象
  • %time 报告statement执行的时间
  • %timeit 多次执行statement以计算平均执行时间,用于执行时间非常小的代码。
  • %who、%who_is、%whos 显示Interactive命名空间的中定义的变量,信息级别/冗余度可变
  • %xdel 删除变量,并尝试清楚其在IPython中的对象上的一切引用

matplotlib集成与pylab模式

导致IPython广泛应用于科学计算领域的重要原因在于它能够跟matplotlib这样的库及其他GUI工具的默契配合。
如果在标准 python shell 中创建一个matplotlib绘图窗口,就会发现GUI时间循环会接管Python回话的控制权,知道该窗口关闭。这显然无法实现交互式的数据分析和可视化,因此IPython对各个GUI框架进行了专门的处理以使其能够与shell配合得天衣无缝。
集成matplotlib方法:

%pylab
这将会使IPython完成以下工作:

import numpy as np
import matplotlib as plt
from matplotlib import pylab, mlab, pyplot
from IPython.display import display
from IPython.core.pylabtools import figsize, getfigs

from pylab import *
from numpy import *

使用示例:

%pylab  inline
plot(randn(1000).cumsum())

二、 使用历史命令

IPython维护着一个位于硬盘上的一个小型数据库,包含执行过的每一天命令。这样的目的在于:

  • 方便的搜索、自动完成之前执行过的命令
  • 在回话间持久化历史命令
  • 将输入输出历史记录到日志文件

搜索并重用命令历史

上箭头键:搜索出命令历史中第一个与你输入的字符相匹配的命令。多次按将会在历史中不断搜索。
下箭头键:子命令历史中向前搜索。
Ctrl-R:部分增量搜素,循环在命令历史中搜素与输入相符的行。

输入和输出变量

IPython会将输入和输出的引用保存在一些特殊变量中。
最近的输入和输出分别保存在_(一个下划线)和__(两个下划线)两个变量中。
输入被保存在_iX变量中,其中X是输入的行号。
输出被保存在_X变量中,其中X是输出的行号
几个与输入输出有关的魔术命令:

  • %hist 打印输入历史
  • %reset 清空interactive命名空间,可选择是否清空输入和输出缓存
  • %xdel 从IPython中移除特定对象的一切引用

记录输入和输出

执行%logstart能够开始记录控制台回话,包括输入和输出。与之配合的命令有:%logoff%logon%logstate%logstop

三、 与操作系统交互

IPython与操作系统shell结合的非常紧密,可以在IPython中执行shell命令。
与系统相关的命令:

  • !cmd在系统shell中执行cmd
  • output=!cmd args 执行cmd,并将结果放在output中
  • %bookmark 书签名 使用IPython的目录书签系统
  • %cd directory(文件路径) 更改工作目录
  • %pwd 返回系统当前工作目录
  • %env 以字典形式返回系统环境变量

shell命令

在IPython中以感叹号(!)开头的命令表示其后的所有内容将会在系统shell中执行。
使用!时,还允许使用当前环境中定义的Python值,只需在变量名前加上美元($)符号即可:

foo = 'E:\学习*'
!dir $foo
 驱动器 E 中的卷是 LENOVO
 卷的序列号是 8635-9A21

 E:\ 的目录

2017/08/13  19:54    <DIR>          学习
               0 个文件              0 字节
               1 个目录 124,378,099,712 可用字节

目录书签系统

IPython的目录书签系统能够保存常用目录的别名以便实现快速跳转。书签能够持久化保存。
如:

  • %bookmark pys 'C:/User/xxx/PyWorkSpace'
  • 定义好书签之后,就可以在执行魔术命令%cd时使用这些书签了: cd pys
  • 列出所有书签: %bookmark -l
  • 书签名与目录冲突:%bookmark -b pys #强制使用书签目录

四、 软件开发工具

交互式调试器

代码调试的最佳时机就是错误发生的时候:

  • 在发生异常之后马上输入 %debug 命令将会调用调试器,并直接跳转到引发异常的那个帧。
    • pdb中,可以查看各个帧中的一切对象和数据。默认是从最低级开始的。输入u(p)d(own)可以在栈的各级别之间切换。
  • 使用 %pdb on命令可以开启自动调试功能, 即当程序异常时直接开始进行调试。%pdb off命令可关闭自动调试。如果不加参数,只输入%pdb命令则在两种模式之间切换。
  • 如果你想设置断点或对函数进行单步调试。 可以使用带有 -d 选项的%run命令,这将会在执行脚本文件中的代码之前打开调试器。然后,如果你想套设置断点,那么使用 b(reak) x 命令,其中x为行号。前进一行使用 n(ext) 命令,进入函数体使用 s(tep into) 然后使用 c(ontinue) 命令即可直行至断点处。要查看变量可以使用 !x ,其中x为变量名。

虽然大部分IDE都拥有优秀的GUI调试器,但是在IPython中调试程序却往往会带来更高的生产率。
pdb主要命令如下:

  • h(elp) 显示命令列表
  • help command 显示命令的说明文档
  • c(continue) 回复程序的执行
  • q(uit) 退出调试器
  • b(reak) number 在指定行设置断点
  • s(tep) 单步进入(step into)函数
  • n(next) 执行当前行(step over),并进入下一行
  • u(p)/d(own) 在函数调用栈中向上或向下移动
  • a(rgs) 显示当前函数的参数
  • debug statement 在新的调试器中调用语句statement
  • l(ist) statement 显示当前行,以及当前栈级别上的上下文参考代码
  • w(here) 打印当前位置的完整栈跟踪

调试器的其他使用场景

除了上面提到的,还有另外的调试方法。可以使用set_trace这个特别的函数。下面两个函数很有用:

def set_trace():
    from IPython.core.debugger import Pdb
    Pdb(color_sheme='Linux').set_trace(sys._getframe().f_back)

def debug(f, *args, **kwargs):
    from IPython.core.debugger import Pdb
    pdb = Pdb(color_scheme='Linux')
    return pdb.runcall(f, *args, **kwargs)

第一个函数set_trace很简单,你可以将它放在代码中任何希望停下来查看一番的地方:

def calling_things():
    set_trace()  #自定义的调试函数
    works_fine()
    throws_an_exception()

第二个函数(debug),使你能够直接在任意函数上使用调试器。
假设我们有如下函数:

def f(x, y, z=1):
    tmp = x + y
    return tmp / z

现在想对其进行单步调试,按如下方式即可:debug(f, 1, 2, z=3)4.3

debug(f, 1, 2, z=3)
C:\Users\xiner\Anaconda3\lib\site-packages\ipykernel_launcher.py:7: DeprecationWarning: The `color_scheme` argument is deprecated since version 5.1
  import sys


> <ipython-input-18-b1f62d757512>(2)f()
      1 def f(x, y, z=1):
----> 2     tmp = x + y
      3     return tmp / z

ipdb> 4.3
4.3
ipdb> %time
*** SyntaxError: invalid syntax
ipdb> exit()

测试代码执行时间:%time%timeit

对于大规模运行时间长的程序,你可能希望测试一下各个部分的执行时间。了解某个复杂计算过程到底是哪些函数占用时间最多。
使用内置的time.clock和time.time函数手工测试代码执行时间的方式如下:

import time 
start = time.time()
iterations = 10000
for i in range(iterations):
    # some code
    elapsed_per = (time.time() - start) / iterations

由于这是一个非常常用的功能,所以IPython专门提供了两个魔术函数%time%timeit以便自动完成该过程。

  • %time 一次执行一条语句,然后报告总体执行时间。
  • %timeit会自动多次执行以产生一个非常准确的平均执行时间。

举例来说,有一个拥有60万字符换的数组,你希望选出其中以foo开头的字符串。现有两种实现方法:

strs = ['foo', 'foobar', 'baz', 'qux', 'python', 'Guido Van Rossum'] * 100000
method1 = [x for x in strs if x.startswith('foo')]
method2 = [x for x in strs if x[:3] == 'foo']

看上去它们差不多是吧?那么我们通过%time来验证一下:

%time method1 = [x for x in strs if x.startswith('foo')]
Wall time: 121 ms
%time method2 = [x for x in strs if x[:3] == 'foo']
Wall time: 87 ms
%timeit method1 = [x for x in strs if x.startswith('foo')]
106 ms ± 963 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit method2 = [x for x in strs if x[:3] == 'foo']
80 ms ± 1.33 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

基本性能分析:%prun%run -p

代码性能分析与代码执行时间分析的区别在于,它关注的是耗费时间的位置。
python使用cProfile模块进行性能分析,它会记录各函数所耗费的时间。
使用方法如下:
python -m cProfile cprof_example.py

执行之后会发现输出结果是按函数名排序的,这我们很难发现哪里才是最花时间的地方,一次通常会再加一个 -s 指定排序规则:
python -m cProfile -s cumulative cprof_example.py
除此自外,cProfile还能以编程的方式分析任意代码块的性能。IPython为此提供了一个方便的接口。即 %prun 和带 -p 命令的%run
%prun 格式跟cProfle相同,但它分析的是部分语句而不是整个文件。

%prun -l 7 -s cumulative run_experiment()
利用 %run -p 也能达到上面的系统命令的效果去无需退出IPython:
%run -p -s cumulative cprof_example.py

逐行分析函数性能

有时从%prun得到的信息要么不足以说明函数的执行时间,要么复杂到难以理解。这是我们可以使用一个叫做line_profiler的小型库(可以通过PyPI或其他包管理工具获取)。其中有一个新的魔术函数%lprun,它可以对一个或多个函数进行逐行性能分析。

四、 IPython HTML Notebook

IPython Notebook目前已成为一种非常棒的交互式计算工具,同时还是科研和教学的一种理想媒介。它有一种基于JSON的文档格式.ipynb,一种流行的演示手段就是使用IPython Notebook再将.ipynb文件发布到网上以供所有人查阅。

IPython Notebook是一个运行于命令行上的轻量级服务器进程。

由于我们是在一个Web浏览器上使用Notebook的,因此该服务器进程可以运行于任何其他地方。甚至可以连接到那些运行在云服务上的Notebook

五、 使用IPython进行高效开发的提示

重新加载模块依赖项

在Python中,当你输入import some_lib时,其中的代码就会执行,且其中的变量、函数和引入项都会被保存在一个新建的some_lib模块命名空间中。下次再输入import some_lib时,就会得到这个模块命名空间的一个引用。
这种"一次加载"方式对于IPython的交互式开发就会带来问题。

如果一个文件import了另一个模块some_lib,在执行了这个文件之后,需要对some_lib进行修改,然后再重新执行文件。这时仍然会使用老版本的some_lib。因为原来的some_lib在交互式环境中已经被加载过了。

为了解决这个问题,有两个方法:

  • 使用reload函数
import some_lib 
reload(some_lib)

显然,当依赖变得更强的时候,就需要在很多地方插入很多的reload。

  • 下面的方法用于解决模块的”深度“(递归)重加载。
    • 使用dreload函数:它会尝试重新加载some_lib以及其所有的依赖项。
      遗憾的是,以上方法不是万灵丹,如果真的失效了,重启IPython试试看。

代码设计提示

保留有意义的对象和数据
如果你在IPython中执行了一下代码:

from my_funcs import g

def f(x, y):
    return g(x + y)

def main():
    x = 6
    y = 7.5
    result = x + y

if __name__ == '__main__':
    main()

那么我们将访问不到任何结果以及main函数中定义的对象。
更好的方法是直接在该模块的全局命名空间中执行main中的代码(当然如果你希望该模块是可引用的,可以将这些代码放到 if __name__ == '__main__'中)。这样当你 %run 这段代码时就可以访问到main中定义的所有变量了。
对这个简单的例子而言可能意义不大,但是对于以后将会遇到的针对大数据集的复杂数据分析问题而言就很重要了。
扁平结构要比嵌套结构好。
这个思想来自”Zen of Python“,它对交互式的代码开发模式同样有效。编写函数和类时应尽量注意低耦合和模块化,这样能使它们更易于测试、调试和交互式使用。不要怕大文件。
在IPython开发时,处理10个小的文件会比处理一个相对较大的文件来的更头疼。更少的文件意味着需要重新加载的模块更少,编辑时需要在各个文件之间跳转的次数也越少。
当然,不建议将此原则极端化。对于一个大型代码库而言,要找到一种合乎逻辑的模块/包架构要花点功夫,这对团队工作非常重要。每个模块都应该高内聚,并能足够直观地找到对应各种功能的函数和类。

六、 IPython高级用法

6.1 让你的类对IPython更加友好

许多对象(如字典、列表、数组等)在IPython下都能以漂亮的格式输出。但是对于自定义的类,就需要自己生成所需的字符串输出。做法是重载 __repr__ 函数即可:

class Message:
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return 'Message: %s' % self.msg

类似的重载 __str__ 能够在print函数时输出相应内容

posted @ 2017-08-15 19:39  xinet  阅读(2576)  评论(0编辑  收藏  举报