《Python数据科学手册》抄书笔记,第一章: Ipython 超越Python
首先启动IPython shell 和Jupyter Notebook
在命令行输入ipython启动IPython
mac启动Jupyter使用Python3 -m IPython notebook
在IPython中,符号?用于浏览文档,??用于浏览源代码,而Tab用于自动补全。
每一个Python对象独有一个字符串的引用,该字符串即为docstring。大多数情况下,该字符串包含对象的简要介绍和使用方法。
内置的help()函数可以获取这些信息。
?可以查看对象的相关信息
In [5]: len? Signature: len(obj, /) Docstring: Return the number of items in a container. Type: builtin_function_or_method In [6]: l = [] In [7]: l? Type: list String form: [] Length: 0 Docstring: Built-in mutable sequence. If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified. In [8]: l.index? Signature: l.index(value, start=0, stop=9223372036854775807, /) Docstring: Return first index of value. Raises ValueError if the value is not present. Type: builtin_function_or_method In [9]:
无论是函数,方法还是具体实例对象。
在自己编写函数的时候,写好相关docstring
1.2.2通过符号??获取源代码。
1.2.3用Tab补全的方式探索模块
1.对象内容的Tab自动补全(相对简单)
2.导入时的Tab自动补全
3.超越Tab自动补全:通配符适配
当想适配中间或者末尾的几个字符时,可以通过*符号来实现
列举除名字空间中以Warning结尾的所有对象。
In [11]: *Warning? BytesWarning DeprecationWarning FutureWarning ImportWarning PendingDeprecationWarning ResourceWarning RuntimeWarning SyntaxWarning UnicodeWarning UserWarning Warning
不要忘记了后面有一个?
寻找一个字符串方法,它的名字中有find
In [12]: str.*find*? str.find str.rfind In [13]:
1.3IPython shell中的快捷键
书中分为:导航快捷键、文本输入快捷键、命令历史快捷键和其他快捷键
1.3.1 导航快捷键
Crrl+a 光标移到本行开始处
Ctrl+e 光标移动本行结尾处
Ctrl+b(或左箭头键) 光标向后移动一个
Ctrl+f(或右箭头键) 光标向前移动一个
1.3.2 文本输入快捷键
Backspace键 删除前一个字符
Ctrl + d 删除后一个字符
Ctrl + k 从光标开始剪切至行的末尾
Ctrl + u 从行的开头剪切至光标
Ctrl + y yank(即粘贴)之前剪切的文本
Ctrl + t transpose(即交换)前两个字符
1.3.3 命令历史快捷键
Ctrl + p (向上箭头) 获取前一个历史命令
Ctrl + n (向下箭头) 获取下一个历史命令
Ctrl + r 对历史命令的反向搜索
这是一个非常好用的功能,对话框中输入关键字会自动匹配相关信息。
使用Ctrl + P与Ctrl + N 可以上下查看相关搜索记录。
使用上下箭头可以查看匹配到的相关信息。
1.3.4 其他快捷键
Ctrl + l 清楚终端屏幕的内容
Ctrl + c 中断当前的Python命令
Ctrl + d 退出IPython会话
1.4 IPython魔法命令
1.4.1粘贴代码块: %paste和%cpaste
当复制,粘贴的时候,出现了多于的符号,解释器报错
可以用%paste与%cpaste来进行复制,粘贴
1.4.2 执行外部代码: %run
执行外部的模块文件
In [2]: %run myscript.py 1 squared is 1 2 squared is 4 3 squared is 9 In [3]: square(55) Out[3]: 3025 In [4]:
square函数定义在myscript.py模块中,这感觉也是一种比较好的加载模块内对象的方式。
myscript.py文件内容:
def square(x): """square a number""" return x ** 2 for N in range(1, 4): print(N, "squared is", square(N))
1.4.3 计算代码运行时间: %timeit
In [4]: %timeit L = [n ** 2 for n in range(1000)] 241 µs ± 4.27 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) In [5]:
这个命令会多次执行简短命令,以获取更加稳定的结果。
多行的输入测试,可以使用两个%%来实现.
%%timeit ...: L = [] ...: for n in range(1000): ...: L.append(n ** 2) ## -- End pasted text -- 278 µs ± 2.05 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1.4.4 魔法函数的帮助:?、%magic、 %lsmagic
获取文档注释
%timeit?
为了获得可用魔法函数的通用描述以及一些示例,可以输入一下命令
%magic
为了快速而简单地获取所有可用魔法函数的列表,可以输入以下命令:
%lsmagic
1.5 输入和输出历史
前面已经讲过可以使用Ctrl+p或者Ctrl+n来翻看历史
1.5.1IPython的输入和输出对象
In [1]: import math In [2]: math.sin(2) Out[2]: 0.9092974268256817 In [3]: math.cos(2) Out[3]: -0.4161468365471424 In [4]: print(in) File "<ipython-input-4-7f51abe8dfc1>", line 1 print(in) ^ SyntaxError: invalid syntax In [5]: print(In) ['', 'import math', 'math.sin(2)', 'math.cos(2)', 'print(in)', 'print(In)'] In [6]: print(Out) {2: 0.9092974268256817, 3: -0.4161468365471424} In [7]:
Ipython里面有一对In与Out的输入与输出。,在Ipython中会自动添加In和Out的Python变量,其中In为list实例,Out为Dict实例
任何返回值是None的命令都不会加到Out变量中(所以print输出不会添加到Out中)。
In [7]: print() In [8]: print Out[8]: <function print> In [9]: print(In) ['', 'import math', 'math.sin(2)', 'math.cos(2)', 'print(in)', 'print(In)', 'print(Out)', 'print()', 'print', 'print(In)'] In [10]: print(Out) {2: 0.9092974268256817, 3: -0.4161468365471424, 8: <built-in function print>} In [11]:
这样,如果利用之前的结果可以方便的拿出来使用。
1.5.2 下划线快捷和以前的输出
如果想获取上一次的输出可以用_来表示,倒数第二个用__,以此类推
通过_x,其中的x为Out输出中的key,开获取第几次的值
In [27]: Out[2] Out[27]: 0.9092974268256817 In [28]: _2 Out[28]: 0.9092974268256817
1.5.3 禁止输出
就是在输入命令行中,在末尾添加;分号
In [32]: math.sin(2) + math.cos(2); In [33]: 32 in Out Out[33]: False In [34]:
在Out的输出字典中,根本没有key32的存在,说明没有输出。
1.5.4 相关的魔法命令
%history 来获取所有的历史命令输入。
通过-n可以显示命令行的输入索引编号,通过n-m开业获取n到m号的输入命令情况。
In [35]: %history 1-4 import math math.sin(2) math.cos(2) print(in) In [36]: %history -n 1-4 1: import math 2: math.sin(2) 3: math.cos(2) 4: print(in) In [37]:
1.6 IPython和shell命令
1.6.2 IPython中的shell命令
只需要在命令之前+!感叹号
In [45]: !pwd /Users/shijianzhong In [46]: !echo "printing from the shell" printing from the shell In [47]:
1.6.3 在shell中传入或传出值
通过变量接收shell命令的输出值
contents = !ls In [48]: print(contents) ['Applications', 'Compute-Question', 'Desktop', 'Documents', 'Downloads', 'Java_Home', 'Library', 'Movies', 'Music', 'Pictures', 'Postman', 'Public', 'PycharmProjects', 'Untitled Folder', '__pycache__', 'books', 'c_study', 'codeintel', 'django_venv', 'dump.rdb', 'eclipse-workspace', 'exec_new3.sh', 'flasky', 'group_git_tips', 'image', 'java_error_in_pycharm.hprof', 'java_error_in_pycharm_958.log', 'learn_crystal', 'learn_django', 'learn_git', 'learn_ruby', 'learn_shell', 'learngit', 'mbox', 'mprun_demo.py', 'my_home', 'my_software', 'mycentos.tar', 'myscript.py', 'nohup.out', 'pyalgo_two', 'pyalgotrade', 'ruby_study', 'scrapy_venv', 'show_p_path.py', 'ssh_test', 'study', 'tmp', 'work_space'] In [49]: contents Out[49]: ['Applications', 'Compute-Question', 'Desktop', 'Documents', 'Downloads', 'Java_Home', 'Library', 'Movies', 'Music', 'Pictures', 'Postman', 'Public', 'PycharmProjects', 'Untitled Folder', '__pycache__', 'books', 'c_study', 'codeintel', 'django_venv', 'dump.rdb', 'eclipse-workspace', 'exec_new3.sh', 'flasky', 'group_git_tips', 'image', 'java_error_in_pycharm.hprof', 'java_error_in_pycharm_958.log', 'learn_crystal', 'learn_django', 'learn_git', 'learn_ruby', 'learn_shell', 'learngit', 'mbox', 'mprun_demo.py', 'my_home', 'my_software', 'mycentos.tar', 'myscript.py', 'nohup.out', 'pyalgo_two', 'pyalgotrade', 'ruby_study', 'scrapy_venv', 'show_p_path.py', 'ssh_test', 'study', 'tmp', 'work_space'] In [50]: type(contents) Out[50]: IPython.utils.text.SList In [51]: message = "hello from the Python" In [52]: !echo {message} hello from the Python
变量接收的shell命令输出的对象是一个类似与列表的对象,Python中的变量名,也可以通过{}的形式传递给shell
1.7与shell相关的魔法方法
Notebook中的shell命令是在一个临时的分支shell中执行的。如果你希望以一种更持久的方式运行,可以使用%魔法命令
通过%后面执行的命令,不能与Python进行交互
一般的shell命令,会通过%automagic模范函数进行翻转。当automagic被打开,一些常用的命令直接省略%就可以跟shell中使用一样
cd cat cp ls man mkdir more mv pwd rm rmdir
In [76]: mkdir tmpe In [77]: cp myscript.py tmp In [78]: cp myscript.py tmpe/ In [79]: ls tmpe myscript.py In [80]: In [80]: rm -r tmpe In [81]:
1.8错误和调试
1.8.1控制异常:%xmode
当一个Python脚本未执行通过时,会抛出一个异常。当解释器捕获到这些异常中的一个时,可以在轨迹追溯(traceback)中找到引起这个错误的原因。
利用%xmode魔法函数,IPython允许你在异常发生时控制打印信息的数量。
In [1]: def func1(a, b): ...: return a / b ...: ...: def func2(x): ...: a = x ...: b = x - 1 ...: return func1(a, b) ...: In [2]: func2(1) --------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-2-7cb498ea7ed1> in <module> ----> 1 func2(1) <ipython-input-1-84841e495819> in func2(x) 5 a = x 6 b = x - 1 ----> 7 return func1(a, b) 8 <ipython-input-1-84841e495819> in func1(a, b) 1 def func1(a, b): ----> 2 return a / b 3 4 def func2(x): 5 a = x ZeroDivisionError: division by zero
In [3]: %xmode? Docstring: Switch modes for the exception handlers. Valid modes: Plain, Context, Verbose, and Minimal. If called without arguments, acts as a toggle. File: /Library/Python/3.7/site-packages/IPython/core/magics/basic.py
xmode有4中选项,默认就时Context
接下来显示Plain
In [4]: %xmode Plain Exception reporting mode: Plain In [5]: func2(1) Traceback (most recent call last): File "<ipython-input-5-7cb498ea7ed1>", line 1, in <module> func2(1) File "<ipython-input-1-84841e495819>", line 7, in func2 return func1(a, b) File "<ipython-input-1-84841e495819>", line 2, in func1 return a / b ZeroDivisionError: division by zero
接下来显示Verbose
In [7]: func2(1) --------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-7-7cb498ea7ed1> in <module> ----> 1 func2(1) global func2 = <function func2 at 0x101fb9e18> <ipython-input-1-84841e495819> in func2(x=1) 5 a = x 6 b = x - 1 ----> 7 return func1(a, b) global func1 = <function func1 at 0x101fb9c80> a = 1 b = 0 8 <ipython-input-1-84841e495819> in func1(a=1, b=0) 1 def func1(a, b): ----> 2 return a / b a = 1 b = 0 3 4 def func2(x): 5 a = x ZeroDivisionError: division by zero In [8]:
最后时Minimal模式
In [8]: %xmode Minimal Exception reporting mode: Minimal In [9]: func2(1) ZeroDivisionError: division by zero
显然Verbose时显示最丰富的。
1.8.2 调试:当阅读轨迹追溯不足以解决问题时
IPython的调试器时Ipdb,Python的时pdb,我现在还不是很熟悉pdb。。。
IPython中最方便的调试界面可能就时%debug魔法命令了。如果你在捕获异常后调用该调试器,它会在异常点自动打开一个交互式调试提示符。
ipdb提示符让你可以探索栈空间当前状态,探索可用变量,设置运行Python命令
In [10]: %debug > <ipython-input-1-84841e495819>(2)func1() 1 def func1(a, b): ----> 2 return a / b 3 4 def func2(x): 5 a = x ipdb> print(a) 1 ipdb> print(b) 0 ipdb>
这个交互式调试器的功能不止如此,我们甚至可以设置单步入栈和出栈来查看各变量的值
In [11]: %debug > <ipython-input-1-84841e495819>(2)func1() 1 def func1(a, b): ----> 2 return a / b 3 4 def func2(x): 5 a = x ipdb> up > <ipython-input-1-84841e495819>(7)func2() 4 def func2(x): 5 a = x 6 b = x - 1 ----> 7 return func1(a, b) 8 ipdb> print(x) 1 ipdb> up > <ipython-input-9-7cb498ea7ed1>(1)<module>() ----> 1 func2(1) ipdb> down > <ipython-input-1-84841e495819>(7)func2() 4 def func2(x): 5 a = x 6 b = x - 1 ----> 7 return func1(a, b) 8 ipdb> down > <ipython-input-1-84841e495819>(2)func1() 1 def func1(a, b): ----> 2 return a / b 3 4 def func2(x): 5 a = x ipdb> print(x) *** NameError: name 'x' is not defined ipdb> quit
ipdb 中的提示框后面的<ipython-input-1-84841e495819>(2)func1()
这种, 表示运行的栈的区域,上面的就是表示运行在func1区域。
如果你希望在任何异常时都自动启动调试器,可以用%pdb魔法函数来启动这个自启动过程
In [15]: %pdb on Automatic pdb calling has been turned ON In [16]: func2(1) ZeroDivisionError: division by zero > <ipython-input-1-84841e495819>(2)func1() 1 def func1(a, b): ----> 2 return a / b 3 4 def func2(x): 5 a = x ipdb> print(b) 0 ipdb> q
部分调试命令
list 显示文件的当前路径
h(elp) 显示命令列表,或查找特定命令的帮助信息
q(uit) 退出调试器和程序
c(ontinue) 退出调试器,继续运行程序
n(ext) 跳到程序的下一步
<enter> 重复前一个命令
p(rint) 打印变量
s(tep) 步进子进程
r(eturn) 从子进程跳出
1.9 代码的分析和计时
%time
对单个语句的执行时间进行计时
%timeit
对单个语句的重复执行进行计时,以获取更高的精准度
%prun
利用分析器运行代码
%lprun
利用逐行分析器运行代码
%memit
测量单个语句的内存使用
%mprun
通过逐行的内存分析器运行代码
%time
: Time the execution of a single statement%timeit
: Time repeated execution of a single statement for more accuracy%prun
: Run code with the profiler%lprun
: Run code with the line-by-line profiler%memit
: Measure the memory use of a single statement%mprun
: Run code with the line-by-line memory profiler
1.9.1 代码段计时:%timeit和%time
首先使用%timeit 使用很简单
In [1]: %timeit sum(range(100)) 1.1 µs ± 27.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
因为这个操作很快,所以%timeit自动让代码重复运行了很多次。对于慢的,会自动调整并减少重复执行的次数。
In [2]: %%timeit ...: total = 0 ...: for i in range(1000): ...: for j in range(1000): ...: total += i * (-1) ** j ...: 303 ms ± 2.79 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
有时候重复操作并不是一个最佳选择。例如,如果有一个列表需要排序,我们可能会被重复操作误导。对一个预先排好序的列表进行排序,比对一个无序的列表进行排序要快,所以重复运作将使结果出现偏差。
In [16]: import random ...: L = [random.random() for i in range(100000)] ...: %timeit L.sort() 410 µs ± 4.27 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1000次循环,第一次排序了以后,后面的L就使一个有序的列表了。
这样的情况下,可以使用%time来尝试
In [16]: import random ...: L = [random.random() for i in range(100000)] ...: %timeit L.sort() 410 µs ± 4.27 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) In [17]: %time L.sort() CPU times: user 896 µs, sys: 24 µs, total: 920 µs Wall time: 970 µs In [18]:
上面是对已经排序的列表再次进行排序,下面是全新的随机列表进行排序的时间
In [18]: L = [random.random() for i in range(100000)] In [19]: %time L.sort() CPU times: user 14.7 ms, sys: 49 µs, total: 14.7 ms Wall time: 14.8 ms
%time就执行一次,由于%timeit会进行一些优化,比如阻止清理未利用的Python对象(即垃圾回收),所有通常情况下%timeit会比%time得到的结果要快
In [20]: %%time ...: total = 0 ...: for i in range(1000): ...: for j in range(1000): ...: total += i * (-1) ** j ...: CPU times: user 354 ms, sys: 1.57 ms, total: 356 ms Wall time: 356 ms
1.9.2分析整个脚本
一个程序右很多的单个语句组成,有时候对整个脚本计时比单个语句计时更重要。IPython可以通过%prun这个魔法函数实现
In [24]: def sum_of_lists(N): ...: total = 0 ...: for i in range(5): ...: L = [j ^ (j >> i) for j in range(N)] ...: total += sum(L) ...: return total ...: In [25]: %prun sum_of_lists(1000000) 14 function calls in 0.530 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 5 0.452 0.090 0.452 0.090 <ipython-input-24-c8a2c5fe2cd3>:4(<listcomp>) 5 0.041 0.008 0.041 0.008 {built-in method builtins.sum} 1 0.028 0.028 0.521 0.521 <ipython-input-24-c8a2c5fe2cd3>:1(sum_of_lists) 1 0.009 0.009 0.530 0.530 <string>:1(<module>) 1 0.000 0.000 0.530 0.530 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1.9.3 用%lprun进行逐行分析
用%prun对代码中的每个函数分析非常有用,但有时候逐行代码分析报告更加方便。该功能需要通过安装第三方的包来实现。首相安装line_profier包
pip install line_profiler
接着用IPython导入line_profiler包提供的IPython扩展
本来mac电脑装了老半天一直装不上去这个包,后面查寻后通过pip3 install Cython git+https://github.com/rkern/line_profiler.git --user进行了安装
In [3]: %load_ext line_profiler In [5]: %lprun -f sum_of_lists sum_of_lists(5000) Timer unit: 1e-06 s Total time: 0.004989 s File: <ipython-input-2-c8a2c5fe2cd3> Function: sum_of_lists at line 1 Line # Hits Time Per Hit % Time Line Contents ============================================================== 1 def sum_of_lists(N): 2 1 3.0 3.0 0.1 total = 0 3 6 6.0 1.0 0.1 for i in range(5): 4 5 4820.0 964.0 96.6 L = [j ^ (j >> i) for j in range(N)] 5 5 159.0 31.8 3.2 total += sum(L) 6 1 1.0 1.0 0.0 return total
1.9.4 用%memit和%mprun进行内存分析
与前面line_profiler差不多,首先进行导包,后面进行魔法函数进行运行
内存分析扩展包有两个有用的魔法函数:%memit魔法函数(它提供的内存消耗计算功能类似于%timeit)和%mprun魔法函数(它提供的内存消耗计算功能类似于%lprun)。
%menit的使用很简单
In [7]: %load_ext memory_profiler In [8]: def sum_of_lists(N): ...: total = 0 ...: for i in range(5): ...: L = [j ^ (j >> i) for j in range(N)] ...: total += sum(L) ...: return total ...: In [9]: %memit sum_of_lists(1000000) peak memory: 152.33 MiB, increment: 79.62 MiB
这个函数消耗了150m内存
对于逐行代码内存消耗,可以用%mprun魔法函数。但很可惜,这个魔法函数仅仅对独立模块内部的函数有效,而对于Notebook本身不起作用。
所以后面先生成一个文件
In [14]: %%file mprun_demo.py ...: def sum_of_lists(N): ...: total = 0 ...: for i in range(5): ...: L = [j ^ (j >> i) for j in range(N)] ...: total += sum(L) ...: del L # remove reference to L ...: return total ...: Overwriting mprun_demo.py
In [15]: from mprun_demo import sum_of_lists In [17]: %mprun -f sum_of_lists sum_of_lists(10000) Filename: /Users/shijianzhong/mprun_demo.py Line # Mem usage Increment Line Contents ================================================ 1 91.4 MiB 91.4 MiB def sum_of_lists(N): 2 91.4 MiB 0.0 MiB total = 0 3 91.4 MiB 0.0 MiB for i in range(5): 4 91.4 MiB 0.0 MiB L = [j ^ (j >> i) for j in range(N)] 5 91.4 MiB 0.0 MiB total += sum(L) 6 91.4 MiB 0.0 MiB del L # remove reference to L 7 91.4 MiB 0.0 MiB return total