【转自】:https://www.jianshu.com/u/130f76596b02

第2章中,我们学习了IPython shell和Jupyter notebook的基础。本章中,我们会探索IPython更深层次的功能,可以从控制台或在jupyter使用。

B.1 使用命令历史

Ipython维护了一个位于磁盘的小型数据库,用于保存执行的每条指令。它的用途有:

  • 只用最少的输入,就能搜索、补全和执行先前运行过的指令;
  • 在不同session间保存命令历史;
  • 将日志输入/输出历史到一个文件

这些功能在shell中,要比notebook更为有用,因为notebook从设计上是将输入和输出的代码放到每个代码格子中。

搜索和重复使用命令历史

Ipython可以让你搜索和执行之前的代码或其他命令。这个功能非常有用,因为你可能需要重复执行同样的命令,例如%run命令,或其它代码。假设你必须要执行:

In[7]: %run first/second/third/data_ .py

运行成功,然后检查结果,发现计算有错。解决完问题,然后修改了data_ .py,你就可以输入一些%run命令,然后按Ctrl+P或上箭头。这样就可以搜索历史命令,匹配输入字符的命令。多次按Ctrl+P或上箭头,会继续搜索命令。如果你要执行你想要执行的命令,不要害怕。你可以按下Ctrl-N或下箭头,向前移动历史命令。这样做了几次后,你可以不假思索地按下这些键!

Ctrl-R可以带来如同Unix风格shell(比如bash shell)的readline的部分增量搜索功能。在Windows上,readline功能是被IPython模仿的。要使用这个功能,先按Ctrl-R,然后输入一些包含于输入行的想要搜索的字符:

In [1]: a_command = foo(x, y, z)

(reverse-i-search)`com\': a_command = foo(x, y, z)

Ctrl-R会循环历史,找到匹配字符的每一行。

输入和输出变量

忘记将函数调用的结果分配给变量是非常烦人的。IPython的一个session会在一个特殊变量,存储输入和输出Python对象的引用。前面两个输出会分别存储在 _(一个下划线)和 __(两个下划线)变量:

In [24]: 2 ** 27
Out[24]: 134217728

In [25]: _
Out[25]: 134217728

输入变量是存储在名字类似_iX的变量中,X是输入行的编号。对于每个输入变量,都有一个对应的输出变量_X。因此在输入第27行之后,会有两个新变量_27 (输出)和_i27(输入):

In [26]: foo = \'bar\'

In [27]: foo
Out[27]: \'bar\'

In [28]: _i27
Out[28]: u\'foo\'

In [29]: _27
Out[29]: \'bar\'

因为输入变量是字符串,它们可以用Python的exec关键字再次执行:

In [30]: exec(_i27)

这里,_i27是在In [27]输入的代码。

有几个魔术函数可以让你利用输入和输出历史。%hist可以打印所有或部分的输入历史,加上或不加上编号。%reset可以清理交互命名空间,或输入和输出缓存。%xdel魔术函数可以去除IPython中对一个特别对象的所有引用。对于关于这些魔术方法的更多内容,请查看文档。

警告:当处理非常大的数据集时,要记住IPython的输入和输出的历史会造成被引用的对象不被垃圾回收(释放内存),即使你使用del关键字从交互命名空间删除变量。在这种情况下,小心使用xdel %和%reset可以帮助你避免陷入内存问题。

B.2 与操作系统交互

IPython的另一个功能是无缝连接文件系统和操作系统。这意味着,在同时做其它事时,无需退出IPython,就可以像Windows或Unix使用命令行操作,包括shell命令、更改目录、用Python对象(列表或字符串)存储结果。它还有简单的命令别名和目录书签功能。

表B-1总结了调用shell命令的魔术函数和语法。我会在下面几节介绍这些功能。

\"表B-1

Shell命令和别名

用叹号开始一行,是告诉IPython执行叹号后面的所有内容。这意味着你可以删除文件(取决于操作系统,用rm或del)、改变目录或执行任何其他命令。

通过给变量加上叹号,你可以在一个变量中存储命令的控制台输出。例如,在我联网的基于Linux的主机上,我可以获得IP地址为Python变量:

In [1]: ip_info = !ifconfig wlan0 | grep \"inet \"

In [2]: ip_info[0].strip()
Out[2]: \'inet addr:10.0.0.11  Bcast:10.0.0.255  Mask:255.255.255.0\'

返回的Python对象ip_info实际上是一个自定义的列表类型,它包含着多种版本的控制台输出。

当使用!,IPython还可以替换定义在当前环境的Python值。要这么做,可以在变量名前面加上$符号:

In [3]: foo = \'test*\'

In [4]: !ls $foo
test4.py  test.py  test. 

%alias魔术函数可以自定义shell命令的快捷方式。看一个简单的例子:

In [1]: %alias ll ls -l

In [2]: ll /usr
total 332
drwxr-xr-x   2 root root  69632 2012-01-29 20:36 bin/
drwxr-xr-x   2 root root   4096 2010-08-23 12:05 games/
drwxr-xr-x 123 root root  20480 2011-12-26 18:08 include/
drwxr-xr-x 265 root root 126976 2012-01-29 20:36 lib/
drwxr-xr-x  44 root root  69632 2011-12-26 18:08 lib32/
lrwxrwxrwx   1 root root      3 2010-08-23 16:02 lib64 -> lib/
drwxr-xr-x  15 root root   4096 2011-10-13 19:03 local/
drwxr-xr-x   2 root root  12288 2012-01-12 09:32 sbin/
drwxr-xr-x 387 root root  12288 2011-11-04 22:53 share/
drwxrwsr-x  24 root src    4096 2011-07-17 18:38 src/

你可以执行多个命令,就像在命令行中一样,只需用分号隔开:

In [558]: %alias test_alias (cd examples; ls; cd ..)

In [559]: test_alias
macrodata.csv  spx.csv	tips.csv

当session结束,你定义的别名就会失效。要创建恒久的别名,需要使用配置。

目录书签系统

IPython有一个简单的目录书签系统,可以让你保存常用目录的别名,这样在跳来跳去的时候会非常方便。例如,假设你想创建一个书签,指向本书的补充内容:

In [6]: %bookmark py4da /home/wesm/code/pydata-book

这么做之后,当使用%cd魔术命令,就可以使用定义的书签:

In [7]: cd py4da
(bookmark:py4da) -> /home/wesm/code/pydata-book
/home/wesm/code/pydata-book

如果书签的名字,与当前工作目录的一个目录重名,你可以使用-b标志来覆写,使用书签的位置。使用%bookmark的-l选项,可以列出所有的书签:

In [8]: %bookmark -l
Current bookmarks:
py4da -> /home/wesm/code/pydata-book-source

书签,和别名不同,在session之间是保持的。

B.3 软件开发工具

除了作为优秀的交互式计算和数据探索环境,IPython也是有效的Python软件开发工具。在数据分析中,最重要的是要有正确的代码。幸运的是,IPython紧密集成了和加强了Python内置的pdb调试器。第二,需要快速的代码。对于这点,IPython有易于使用的代码计时和分析工具。我会详细介绍这些工具。

交互调试器

IPython的调试器用tab补全、语法增强、逐行异常追踪增强了pdb。调试代码的最佳时间就是刚刚发生错误。异常发生之后就输入%debug,就启动了调试器,进入抛出异常的堆栈框架:

In [2]: run examples/ipython_bug.py
---------------------------------------------------------------------------
Asserti                             Traceback (most recent call last)
/home/wesm/code/pydata-book/examples/ipython_bug.py in <module>()
     13     throws_an_exception()
     14
---> 15 calling_things()

/home/wesm/code/pydata-book/examples/ipython_bug.py in calling_things()
11 def calling_things():
     12     works_fine()
---> 13     throws_an_exception()
     14
     15 calling_things()

/home/wesm/code/pydata-book/examples/ipython_bug.py in throws_an_exception()
      7     a = 5
      8     b = 6
----> 9     assert(a + b == 10)
     10
     11 def calling_things():

Asserti :

In [3]: %debug
> /home/wesm/code/pydata-book/examples/ipython_bug.py(9)throws_an_exception()
      8     b = 6
----> 9     assert(a + b == 10)
     10

ipdb>

一旦进入调试器,你就可以执行任意的Python代码,在每个堆栈框架中检查所有的对象和数据(解释器会保持它们活跃)。默认是从错误发生的最低级开始。通过u(up)和d(down),你可以在不同等级的堆栈踪迹切换:

ipdb> u
> /home/wesm/code/pydata-book/examples/ipython_bug.py(13)calling_things()
     12     works_fine()
---> 13     throws_an_exception()
     14

执行%pdb命令,可以在发生任何异常时让IPython自动启动调试器,许多用户会发现这个功能非常好用。

用调试器帮助开发代码也很容易,特别是当你希望设置断点或在函数和脚本间移动,以检查每个阶段的状态。有多种方法可以实现。第一种是使用%run和-d,它会在执行传入脚本的任何代码之前调用调试器。你必须马上按s(step)以进入脚本:

In [5]: run -d examples/ipython_bug.py
Breakpoint 1 at /home/wesm/code/pydata-book/examples/ipython_bug.py:1
NOTE: Enter \'c\' at the ipdb>  prompt to start your  .
> <string>(1)<module>()

ipdb> s
--Call--
> /home/wesm/code/pydata-book/examples/ipython_bug.py(1)<module>()
1---> 1 def works_fine():
      2     a = 5
      3     b = 6

然后,你就可以决定如何工作。例如,在前面的异常,我们可以设置一个断点,就在调用works_fine之前,然后运行脚本,在遇到断点时按c(continue):

ipdb> b 12
ipdb> c
> /home/wesm/code/pydata-book/examples/ipython_bug.py(12)calling_things()
     11 def calling_things():
2--> 12     works_fine()
     13     throws_an_exception()

这时,你可以step进入works_fine(),或通过按n(next)执行works_fine(),进入下一行:

ipdb> n
> /home/wesm/code/pydata-book/examples/ipython_bug.py(13)calling_things()
2    12     works_fine()
---> 13     throws_an_exception()
     14

然后,我们可以进入throws_an_exception,到达发生错误的一行,查看变量。注意,调试器的命令是在变量名之前,在变量名前面加叹号!可以查看内容:

ipdb> s
--Call--
> /home/wesm/code/pydata-book/examples/ipython_bug.py(6)throws_an_exception()
      5
----> 6 def throws_an_exception():
      7     a = 5

ipdb> n
> /home/wesm/code/pydata-book/examples/ipython_bug.py(7)throws_an_exception()
      6 def throws_an_exception():
----> 7     a = 5
      8     b = 6

ipdb> n
> /home/wesm/code/pydata-book/examples/ipython_bug.py(8)throws_an_exception()
      7     a = 5
----> 8     b = 6
      9     assert(a + b == 10)

ipdb> n
> /home/wesm/code/pydata-book/examples/ipython_bug.py(9)throws_an_exception()
      8     b = 6
----> 9     assert(a + b == 10)
     10

ipdb> !a
5
ipdb> !b
6

提高使用交互式调试器的熟练度需要练习和经验。表B-2,列出了所有调试器命令。如果你习惯了IDE,你可能觉得终端的调试器在一开始会不顺手,但会觉得越来越好用。一些Python的IDEs有很好的GUI调试器,选择顺手的就好。

\"表B-2

使用调试器的其它方式

还有一些其它工作可以用到调试器。第一个是使用特殊的set_trace函数(根据pdb.set_trace命名的),这是一个简装的断点。还有两种方法是你可能想用的(像我一样,将其添加到IPython的配置):

from IPython.core.debugger import Pdb

def set_trace():
    Pdb(color_scheme=\'Linux\').set_trace(sys._get ().f_back)

def debug(f, *args, **kwargs):
    pdb = Pdb(color_scheme=\'Linux\')
    return pdb.runcall(f, *args, **kwargs)

第一个函数set_trace非常简单。如果你想暂时停下来进行仔细检查(比如发生异常之前),可以在代码的任何位置使用set_trace:

In [7]: run examples/ipython_bug.py
> /home/wesm/code/pydata-book/examples/ipython_bug.py(16)calling_things()
     15     set_trace()
---> 16     throws_an_exception()
     17

按c(continue)可以让代码继续正常行进。

我们刚看的debug函数,可以让你方便的在调用任何函数时使用调试器。假设我们写了一个下面的函数,想逐步分析它的逻辑:

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

普通地使用f,就会像f(1, 2, z=3)。而要想进入f,将f作为第一个参数传递给debug,再将位置和关键词参数传递给f:

In [6]: debug(f, 1, 2, z=3)
> <ipython-input>(2)f()
      1 def f(x, y, z):
----> 2     tmp = x + y
      3     return tmp / z

ipdb>

这两个简单方法节省了我平时的大量时间。

最后,调试器可以和%run一起使用。脚本通过运行%run -d,就可以直接进入调试器,随意设置断点并启动脚本:

In [1]: %run -d examples/ipython_bug.py
Breakpoint 1 at /home/wesm/code/pydata-book/examples/ipython_bug.py:1
NOTE: Enter \'c\' at the ipdb>  prompt to start your  .
> <string>(1)<module>()

ipdb>

加上-b和行号,可以预设一个断点:

In [2]: %run -d -b2 examples/ipython_bug.py

Breakpoint 1 at /home/wesm/code/pydata-book/examples/ipython_bug.py:2
NOTE: Enter \'c\' at the ipdb>  prompt to start your  .
> <string>(1)<module>()

ipdb> c
> /home/wesm/code/pydata-book/examples/ipython_bug.py(2)works_fine()
      1 def works_fine():
1---> 2     a = 5
      3     b = 6

ipdb>

代码计时:%time 和 %timeit

对于大型和长时间运行的数据分析应用,你可能希望测量不同组件或单独函数调用语句的执行时间。你可能想知道哪个函数占用的时间最长。幸运的是,IPython可以让你开发和测试代码时,很容易地获得这些信息。

手动用time模块和它的函数time.clock和time.time给代码计时,既单调又重复,因为必须要写一些无趣的模板化代码:

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

因为这是一个很普通的操作,IPython有两个魔术函数,%time和%timeit,可以自动化这个过程。

%time会运行一次语句,报告总共的执行时间。假设我们有一个大的字符串列表,我们想比较不同的可以挑选出特定开头字符串的方法。这里有一个含有600000字符串的列表,和两个方法,用以选出foo开头的字符串:

# a very large list of strings
strings = [\'foo\', \'foobar\', \'baz\', \'qux\',
           \'python\', \'Guido Van Rossum\'] * 100000

method1 = [x for x in strings if x.startswith(\'foo\')]

method2 = [x for x in strings if x[:3] == \'foo\']

看起来它们的性能应该是同级别的,但事实呢?用%time进行一下测量:

In [561]: %time method1 = [x for x in strings if x.startswith(\'foo\')]
CPU times: user 0.19 s, sys: 0.00 s, total: 0.19 s
Wall time: 0.19 s

In [562]: %time method2 = [x for x in strings if x[:3] == \'foo\']
CPU times: user 0.09 s, sys: 0.00 s, total: 0.09 s
Wall time: 0.09 s

Wall time(wall-clock time的简写)是主要关注的。第一个方法是第二个方法的两倍多,但是这种测量方法并不准确。如果用%time多次测量,你就会发现结果是变化的。要想更准确,可以使用%timeit魔术函数。给出任意一条语句,它能多次运行这条语句以得到一个更为准确的时间:

In [563]: %timeit [x for x in strings if x.startswith(\'foo\')]
10 loops, best of 3: 159 ms per loop

In [564]: %timeit [x for x in strings if x[:3] == \'foo\']
10 loops, best of 3: 59.3 ms per loop

这个例子说明了解Python标准库、NumPy、pandas和其它库的性能是很有价值的。在大型数据分析中,这些毫秒的时间就会累积起来!

%timeit特别适合分析执行时间短的语句和函数,即使是微秒或纳秒。这些时间可能看起来毫不重要,但是一个20微秒的函数执行1百万次就比一个5微秒的函数长15秒。在上一个例子中,我们可以直接比较两个字符串操作,以了解它们的性能特点:

In [565]: x = \'foobar\'

In [566]: y = \'foo\'

In [567					
收藏 打印