IPython系统相关内容及其配置信息

一.使用命令历史

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

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

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

1.1搜索和复用命令历史

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

%run first/second/third/data_script.py

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

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

a_command = foo(x, y, z)

I-search backward:a_command = foo(x, y, z)

1.2输入和输出变量

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

2 ** 27
134217728
_
134217728

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

foo = 'bar'
foo
'bar'
_i6

'foo'
_6

'bar'

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

exec(_i6)

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

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

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

二.与操作系统交互

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

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

在这里插入图片描述

2.1shell命令及其别名

用叹号开始一行,是告诉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.xml

%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结束,你定义的别名就会失效。要创建恒久的别名,需要使用配置。

2.2目录书签系统

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

%bookmark py4da

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

cd py4da

(bookmark:py4da) -> F:\python\利用python进行数据分析
F:\python\利用python进行数据分析

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

%bookmark -l

Current bookmarks:
py4da -> F:\python\利用python进行数据分析

三.软件开发工具

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

3.1交互调试器

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

In [2]: run examples/ipython_bug.py
---------------------------------------------------------------------------
AssertionError                            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():

AssertionError:

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 script.
> <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调试器,选择顺手的就好。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vxqgrm4d-1592315304168)(https://camo.githubusercontent.com/d4f938bbb8e8984fbbce022ce2900a934072d8eb/687474703a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f373137383639312d393061346231376532306235623033612e706e673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430)]

3.2 对代码测时: %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进行一下测量:

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

Wall time: 87.8 ms
%time method2 = [x for x in strings if x[:3] == 'foo']

Wall time: 58.8 ms

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

%timeit [x for x in strings if x.startswith('foo')]

84.4 ms ± 4.26 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit [x for x in strings if x[:3] == 'foo']

53 ms ± 984 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

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

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

x = 'foobar'
y = 'foo'
%timeit x.startswith(y)

161 ns ± 5.87 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit x[:3] == y

122 ns ± 1.25 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

四.使用python进行高效代码开发的技巧

方便快捷地写代码、调试和使用是每个人的目标。除了代码风格,流程细节(比如代码重载)也需要一些调整。

因此,这一节的内容更像是门艺术而不是科学,还需要你不断的试验,以达成高效。最终,你要能结构优化代码,并且能省时省力地检查程序或函数的结果。我发现用IPython设计的软件比起命令行,要更适合工作。尤其是当发生错误时,你需要检查自己或别人写的数月或数年前写的代码的错误。

4.1 重载模块依赖项

在Python中,当你输入import some_lib,some_lib中的代码就会被执行,所有的变量、函数和定义的引入,就会被存入到新创建的some_lib模块命名空间。当下一次输入some_lib,就会得到一个已存在的模块命名空间的引用。潜在的问题是当你%run一个脚本,它依赖于另一个模块,而这个模块做过修改,就会产生问题。假设我在test_script.py中有如下代码:

import some_lib

x = 5
y = [1, 2, 3, 4]
result = some_lib.get_answer(x, y)

如果你运行过了%run test_script.py,然后修改了some_lib.py,下一次再执行%run test_script.py,还会得到旧版本的some_lib.py,这是因为Python模块系统的“一次加载”机制。这一点区分了Python和其它数据分析环境,比如MATLAB,它会自动传播代码修改。解决这个问题,有多种方法。第一种是在标准库importlib模块中使用reload函数:

import some_lib
import importlib

importlib.reload(some_lib)

这可以保证每次运行test_script.py时可以加载最新的some_lib.py。很明显,如果依赖更深,在各处都使用reload是非常麻烦的。对于这个问题,IPython有一个特殊的dreload函数(它不是魔术函数)重载深层的模块。如果我运行过some_lib.py,然后输入dreload(some_lib),就会尝试重载some_lib和它的依赖。不过,这个方法不适用于所有场景,但比重启IPython强多了。

4.2代码设计准则

为命令行写一个下面示例中的程序是很少见的:

from my_functions import g

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

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

if __name__ == '__main__':
    main()

在IPython中运行这个程序会发生问题,你发现是什么了吗?运行之后,任何定义在main函数中的结果和对象都不能在IPython中被访问到。更好的方法是将main中的代码直接在模块的命名空间中执行(或者在__name__ == ‘main’:中,如果你想让这个模块可以被引用)。这样,当你运行代码时,就可以查看所有定义在main中的变量。这等价于在Jupyter notebook的代码格中定义一个顶级变量。

五.高阶IPython特性

要全面地使用IPython系统需要用另一种稍微不同的方式写代码,或深入IPython的配置。

5.1 使你定义的类对IPthon友好

IPython会尽可能地在控制台美化展示每个字符串。对于许多对象,比如字典、列表和元组,内置的pprint模块可以用来美化格式。但是,在用户定义的类中,你必自己生成字符串。假设有一个下面的简单的类:

class Message:
    def __init__(self, msg):
        self.msg = msg

如果这么写,就会发现默认的输出不够美观:

x = Message('I have a secret')
x

<__main__.Message at 0x1d3034ef308>

IPython会接收__repr__魔术方法返回的字符串(通过output = repr(obj)),并在控制台打印出来。因此,我们可以添加一个简单的__repr__方法到前面的类中,以得到一个更有用的输出:

class Message:
    def __init__(self, msg):
        self.msg = msg

    def __repr__(self):
        return 'Message: %s' % self.msg
x = Message('I have a secret')
x

Message: I have a secret

5.2 文件与配置

通过扩展配置系统,大多数IPython和Jupyter notebook的外观(颜色、提示符、行间距等等)和动作都是可以配置的。通过配置,你可以做到:

改变颜色主题
改变输入和输出提示符,或删除输出之后、输入之前的空行
执行任意Python语句(例如,引入总是要使用的代码或者每次加载IPython都要运行的内容)
启用IPython总是要运行的插件,比如line_profiler中的%lprun魔术函数
启用Jupyter插件
定义自己的魔术函数或系统别名

IPython的配置存储在特殊的ipython_config.py文件中,当你启动IPython,就会默认加载这个存储在profile_default文件夹中的默认文件

这个文件中的内容留给读者自己探索。这个文件有注释,解释了每个配置选项的作用。

猜你喜欢

转载自blog.csdn.net/qq_43328040/article/details/106796205