这里主要是对《利用python进行数据分析》的学习,原书的电子版地址为:
https://github.com/iamseancheney/python_for_data_analysis_2nd_chinese_version
不知道这个项目是不是译者或者是什么好心人整理的。
这里不准备对数据分析或者是使用python的背景这些东西做过多的解释,先来看下Ipython和jupyter。
Ipython和jupyter
在安装完python之后(不管是直接安装的某个版本的python,或者是通过anaconda进行安装),通过配置环境变量之后,都可以通过在终端或者cmd中输入python进入python环境:
C:\Users\xxx>python
Python 3.7.1 (default, Dec 10 2018, 22:54:23) [MSC v.1915 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
在上面的python环境中,可以进行交互式操作。而Ipython可以说是这个python的加强版,Ipython可以通过在终端或cmd中输入Ipython进入:
C:\Users\xxx>Ipython
Python 3.7.1 (default, Dec 10 2018, 22:54:23) [MSC v.1915 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
可以看出两者的界面是有所区别的。而jupyter则是一个更宽泛的多语言交互计算工具,现在Ipython可以作为jupyter使用的内核。
因为这里使用jupyter作为python代码运行的基础(即不是通过运行.py文件运行代码的),因此可以将jupyter看作是Ipython的进阶版。
目前jupyter可以用作编写、测试、调式python代码的增强shell,还可以使用jupyter编写markdown和HTML内容。
jupyter操作
运行jupyter notebook
在终端或cmd中执行jupyter notebook:
C:\Users\xxx>jupyter notebook
[I 15:15:17.002 NotebookApp] JupyterLab extension loaded from D:\Anaconda3\lib\site-packages\jupyterlab
[I 15:15:17.002 NotebookApp] JupyterLab application directory is D:\Anaconda3\share\jupyter\lab
[I 15:15:17.007 NotebookApp] Serving notebooks from local directory: C:\Users\wood
[I 15:15:17.007 NotebookApp] The Jupyter Notebook is running at:
[I 15:15:17.008 NotebookApp] http://localhost:8888/?token=7b8bd3e1540e6fdcc8afd7846a0ef12835889a04b01f69ff
[I 15:15:17.009 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 15:15:17.460 NotebookApp]
To access the notebook, open this file in a browser:
file:///C:/Users/xxx/AppData/Roaming/jupyter/runtime/nbserver-2252-open.html
Or copy and paste one of these URLs:
http://localhost:8888/?token=7b8bd3e1540e6fdcc8afd7846a0ef12835889a04b01f69ff
上面的命令会打开本地浏览器,然后可以选择对应的.ipynb文件打开。
或者按照上面的提示在启动notebook之后,手动输入本地网址打开文件。
自省
在变量前或者变量后使用问号?,可以显示对象的信息:
a = [1,2,3]
# ?a
a?
结果为:
Type: list
String form: [1, 2, 3]
Length: 3
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.
而对于函数或者实例方法,则会显示定义过的文档字符串:
def func(a,b,c):
"""a:example
b:example
c:example"""
print("hello world")
func?
结果为:
Signature: func(a, b, c)
Docstring:
a:example
b:example
c:example
File: c:\users\xxx\desktop\python_demo\<ipython-input-48-07ebd1c164cf>
Type: function
而两个问号??则会显示函数的内容:
def func(a,b,c):
"""a:example
b:example
c:example"""
print("hello world")
func??
结果为:
Signature: func(a, b, c)
Source:
def func(a,b,c):
"""a:example
b:example
c:example"""
print("hello world")
File: c:\users\xxx\desktop\python_demo\<ipython-input-49-2b7dcc8932bb>
Type: function
同时问号?还可以匹配命名空间中的变量:
fun*?
结果为:
func
魔术命令
Ipython中的特殊命令(python中不存在)被称为魔术命令,格式为在命令前添加%前缀。
%run
该命令可以在Ipython或jupyter中运行某个.py文件:
# test.py
print("hello world")
运行如下代码:
%run test.py
结果为:
hello world
%timeit
能够测量python语句的执行时间:
%timeit %run test.py
结果为:
hello world
...
hello world
3.07 ms ± 518 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
即能够通过多次运行python语句,来测量其运行时间。
%pwd
表示当前目录
%matplotlib inline
在jupyter中表示可以在notebook中进行可视化显示。
python语法
这里直接跳过简单的内容,看一下可能会引起混淆的部分。
引用、赋值、浅拷贝、深拷贝
这里直接先给出具体的区别:
- 引用:指向实际的内存单元,可以说变量名就是对其指向的实际的内存单元的引用
- 赋值:变量间的赋值,指向的是同一块内存单元
- 浅拷贝:只复制最外层的结构,对于内层的嵌套结构直接引用其原始的内存地址
- 深拷贝:不可变类型直接引用原始的内存地址,可变类型重新创建
首先看不可变类型:
import copy
a = (1,2,3)
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(a,b,c,d,id(a),id(b),id(c),id(d))
结果为:
(1, 2, 3) (1, 2, 3) (1, 2, 3) (1, 2, 3) 1783431521840 1783431521840 1783431521840 1783431521840
可以看出指向的地址都一样,即引用的是同一块内存。
再看下可变类型:
a = [1,2,3]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(a,b,c,d,id(a),id(b),id(c),id(d))
a[1] = 5
print(a,b,c,d,id(a),id(b),id(c),id(d))
结果为:
[1, 2, 3] [1, 2, 3] [1, 2, 3] [1, 2, 3] 1783402930888 1783402930888 1783402930440 1783402930696
[1, 4, 3] [1, 4, 3] [1, 2, 3] [1, 2, 3] 1783402930888 1783402930888 1783402930440 1783402930696
即赋值会直接引用原始的内存地址,因此发生内容修改时,会一同发生变化。而对于非嵌套的数据结构,深拷贝和浅拷贝都会重新复制一份数据,因此不会发生更改。
对于可变类型的嵌套结构:
a = [1,2,[3,4]]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(a,b,c,d,id(a),id(b),id(c),id(d))
a[2][0] = 5
print(a,b,c,d,id(a),id(b),id(c),id(d))
结果为:
[1, 2, [3, 4]] [1, 2, [3, 4]] [1, 2, [3, 4]] [1, 2, [3, 4]] 1783402932232 1783402932232 1783402930824 1783402930888
[1, 2, [5, 4]] [1, 2, [5, 4]] [1, 2, [5, 4]] [1, 2, [3, 4]] 1783402932232 1783402932232 1783402930824 1783402930888
和之前有区别的在于,嵌套结构的内层数据在浅拷贝时不会发生创建,而是会引用原有的内存地址。
对于不可变类型的嵌套结构来说,因为内外都是不可变类型的,因此地址都是相同的。
对于可变地址和不可变地址的嵌套结构来说:
a = [1,2,(3,4)]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(a,b,c,d,id(a),id(b),id(c),id(d))
print(a,b,c,d,id(a[2]),id(b[2]),id(c[2]),id(d[2]))
a[0] = 5
print(a,b,c,d,id(a),id(b),id(c),id(d))
print(a,b,c,d,id(a[2]),id(b[2]),id(c[2]),id(d[2]))
结果为:
[1, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] 1783402914248 1783402914248 1783402914184 1783402914376
[1, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] 1783431576392 1783431576392 1783431576392 1783431576392
[5, 2, (3, 4)] [5, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] 1783402914248 1783402914248 1783402914184 1783402914376
[5, 2, (3, 4)] [5, 2, (3, 4)] [1, 2, (3, 4)] [1, 2, (3, 4)] 1783431576392 1783431576392 1783431576392 1783431576392
这里我们一行一行看:
- 第一行:因为外层是可变类型,因此在浅拷贝时(只拷贝外层)由于重新创建,地址发生了变化,深拷贝(拷贝外层和内层)也会发生变化
- 第二行:但是内层由于是不可变类型,因此地址不会发生改变
- 第三行:同第一行
- 第四行:同第二行
还有一种可能:
a = (1,2,[3,4])
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(a,b,c,d,id(a),id(b),id(c),id(d))
print(a,b,c,d,id(a[2]),id(b[2]),id(c[2]),id(d[2]))
a[2][0] = 5
print(a,b,c,d,id(a),id(b),id(c),id(d))
print(a,b,c,d,id(a[2]),id(b[2]),id(c[2]),id(d[2]))
结果为:
(1, 2, [3, 4]) (1, 2, [3, 4]) (1, 2, [3, 4]) (1, 2, [3, 4]) 1783431521840 1783431521840 1783431521840 1783431577816
(1, 2, [3, 4]) (1, 2, [3, 4]) (1, 2, [3, 4]) (1, 2, [3, 4]) 1783402931464 1783402931464 1783402931464 1783402931848
(1, 2, [5, 4]) (1, 2, [5, 4]) (1, 2, [5, 4]) (1, 2, [3, 4]) 1783431521840 1783431521840 1783431521840 1783431577816
(1, 2, [5, 4]) (1, 2, [5, 4]) (1, 2, [5, 4]) (1, 2, [3, 4]) 1783402931464 1783402931464 1783402931464 1783402931848
这里我们一行一行看:
- 第一行:因为外层是不可变类型,因此在浅拷贝时(只拷贝外层)会直接指向原有的内存地址,地址不会发生变化,而由于内层是可变类型,深拷贝(拷贝外层和内层)会发生变化
- 第二行:但是内层由于是可变类型,而浅拷贝不拷贝内层,因此地址不变,深拷贝(拷贝外层和内层)会发生变化
- 第三行:同第一行
- 第四行:同第二行
值传递
在函数传参时,新的局部变量也支持创建了对原始对象的引用,而不是重新赋值。
def func(a):
b = a
print(id(a), id(b))
a = [1,2,3]
print(id(a))
func(a)
结果为:
1783402930824
1783402930824 1783402930824
而考虑到可变类型的性质,完全可以在函数中实现对传入的可变类型参数的修改和更新,该修改在函数外部是可见的。
类型检查
可以使用isinstance函数来检查某个对象是否是某个类型的实例,或者是某个类型列表中的实例:
Signature: isinstance(obj, class_or_tuple, /)
Docstring:
Return whether an object is an instance of a class or of a subclass thereof.
A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to
check against. This is equivalent to ``isinstance(x, A) or isinstance(x, B)
or ...`` etc.
Type: builtin_function_or_method
即下面的代码都是可以的:
isinstance([1,2,3], (list))
isinstance([1,2,3], (list, tuple))
属性访问
以下的内容是等效的:
getattr("hello","split")("l")
"hello".split("l")
即可以通过getattr函数来获取某个对象的属性和方法。
可迭代对象、迭代器、生成器
首先看一下《流畅的python》一书中对可迭代对象和迭代器的描述,以及本书中对生成器的描述:
可迭代对象:
- 使用iter内置函数可以获取迭代器的对象
- 如果对象实现了能够返回迭代器的__iter__方法,那么对象就是可迭代的
- 序列都可以迭代
- 实现了__getitem__方法,而且其参数是从0开始的索引,这种对象也可以迭代
迭代器:
- 实现了无参数的__next__方法,返回序列中的下一个元素
- 如果没有元素了,就抛出StopIteration异常
- python中的迭代器还实现了__iter__方法,因此迭代器也可以迭代
生成器:
- 生成器是构造新的可迭代对象的一种简单方式
- 一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。
- 要创建一个生成器,只需将函数中的return替换为yeild即可
根据上边的内容,如果要判断对象是否可迭代,可以通过iter函数来实现,如果可迭代,返回的就是迭代器,如果不可迭代就会报错:
def isiterable(obj):
try:
iter(obj)
return True
except TypeError: # not iterable
return False
创建生成器则可以借助yield关键字:
def func(n = 10):
for i in range(1, n + 1):
yield i ** 2
print(func())
for it in func():
print(it, end = " ")
结果为:
<generator object func at 0x0000019F3CD0D7C8>
1 4 9 16 25 36 49 64 81 100
is和==
简单说来is是判断两者是否指向同一内存,而==则是判断两者值是否相等:
a = [1,2,3]
b = [1,2,3]
print(a == b, a is b)
结果为:
True False
None
需要注意的是,None在python中是用单例实现的,即只存在一个None对象的实例。
a = None
b = None
print(a == b, a is b)
结果为:
True True
序列函数
常用的会有以下几个:
- enumerate:能够返回索引和值的元组序列
- sorted:可以返回新的有序列表
- zip:可以将多个列表、元组或其它序列成对组合成一个元组列表
- reversed:可以从后往前迭代一个序列
比如enumerate:
for index, value in enumerate(range(3,0,-1)):
print(index, value)
结果为:
0 3
1 2
2 1
sorted则会返回新的有序列表:
sorted([2,4,6,8,3])
结果为:
[2, 3, 4, 6, 8]
还有zip:
for item in zip(range(3), range(3)):
print(item)
结果为:
(0, 0)
(1, 1)
(2, 2)
但reversed返回的则是生成器:
print(reversed(range(3)), list(reversed(range(3))))
结果为:
<range_iterator object at 0x0000019F3CEAC250> [2, 1, 0]
推导式
列表推导式使用中括号实现:
tmp = [x ** 2 for x in range(10) if x % 2 == 0]
print(tmp, end = " ")
结果为:
[0, 4, 16, 36, 64]
集合推导式用大括号实现:
tmp = {x ** 2 for x in range(10) if x % 2 == 0}
print(tmp, end = " ")
结果为:
{0, 64, 4, 36, 16}
字典推导式也用大括号实现,不过和集合推导式格式有区别:
tmp = {x:x ** 2 for x in range(10) if x % 2 == 0}
print(tmp, end = " ")
结果为:
{0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
生成器推导式用小括号实现:
tmp = (x ** 2 for x in range(10) if x % 2 == 0)
print(tmp, list(tmp))
结果为:
<generator object <genexpr> at 0x0000019F3CD0DED0> [0, 4, 16, 36, 64]