本文为个人学习总结,如有错误欢迎指正。推荐隔壁
SeanCheney的专题。
1~3章
第一章 准备工作
什么类型的数据
这里的数据指的是结构化数据(structured data)
- 表格型数据
- 多维数组
- 关系型数据库(通过关键列相互联系的多个表)
- 间隔平均或不平均的时间序列
大部分数据集可以转化为更合适分析和建模的结构化形式,比如新闻文章转化为词频表。
重要的Python库
- numpy(numerical python)
- 多维数组对象ndarray
- 线性代数运算、傅里叶变换、随机数生成
- pandas(panel data)
- 兼具numpy高性能的数组计算功能,电子表格和关系型数据库的处理
- 提供复杂精细的索引
- 本书重点
- matplotlib
- scipy-科学计算
- 与numpy结合使用,可以处理多种传统的科学计算问题。
- scikit-learn(本书不介绍)
- 分类:SVM、近邻、随机森林、逻辑回归等等。
- 回归:Lasso、岭回归等等。
- 聚类:k-均值、谱聚类等等。
- 降维:PCA、特征选择、矩阵分解等等。
- 选型:网格搜索、交叉验证、度量。
- 预处理:特征提取、标准化。
- statsmodels(统计分析包)
- 回归模型:线性回归,广义线性模型,健壮线性模型,线性混合效应模型等等。
- 方差分析(ANOVA)。
- 时间序列分析:AR,ARMA,ARIMA,VAR和其它模型。
- 非参数方法: 核密度估计,核回归。
- 统计模型结果可视化。
引入惯例
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import statsmodels as sm
不要一次性引入所有内容from numpy import *
一些常用术语
数据规整(Munge/Munging/Wrangling)
指的是将非结构化和(或)散乱数据处理为结构化或整洁形式的整个过程。这几个词已经悄悄成为当今数据黑客们的行话了。Munge这个词跟Lunge押韵。伪代码(Pseudocode)
算法或过程的“代码式”描述,而这些代码本身并不是实际有效的源代码。语法糖(Syntactic sugar)
这是一种编程语法,它并不会带来新的特性,但却能使代码更易读、更易写。
第二章 Python语法基础,IPython和Jupyter Notebooks
2.1 Ipython和Jupyter Notebooks
Tab补全
在Jupyter notebook和新版的IPython(5.0及以上),自动补全功能是下拉框的形式。
默认情况下,IPython会隐藏下划线开头的方法和属性,比如魔术方法和内部的“私有”方法和属性,以避免混乱的显示。我们可以先输入一个下划线再按Tab键。
自省
一切对象后面使用?
,都可以显示对象信息。包括自定义的函数(自定义的函数使用??
可以显示源码)
In [8]: b = [1, 2, 3]
In [9]: b?
Type: list
String Form:[1, 2, 3]
Length: 3
Docstring:
list() -> new empty list
list(iterable) -> new list initialized from iterable's items
In [12]: add_numbers??
Signature: add_numbers(a, b)
Source:
def add_numbers(a, b):
"""
Add two numbers together
Returns
-------
the_sum : type of arguments
"""
return a + b
File:
Type: function
?的另一个用途,搜索Ipython的命名空间
In [13]: np.*load*?
np.__loader__
np.load
np.loads
np.loadtxt
np.pkgload
%run命令
假如有一个文件ipython_script_test.py
,我们可以使用%run来运行。
In [14]: %run ipython_script_test.py
在Jupyter notebook中,你也可以使用%load
,它将脚本导入到一个代码格中:
%load ipython_script_test.py
中断运行的代码
代码运行时按Ctrl-C,无论是%run或长时间运行命令,都会导致KeyboardInterrupt
。
从剪贴板执行程序
使用%paste
和%cpaste
函数,
键盘快捷键
Ipython有许多快捷键,而Jupyter notebook的快捷键变化快,建议看官方文档。
魔术命令
IPython中特殊的命令(Python中没有)被称作“魔术”命令。
matplotlib集成
IPython在分析计算领域能够流行的原因之一是它非常好的集成了数据可视化和其它用户界面库。
2.2 Python语言基础
语言语义
- 代码块缩进
Python的设计强调可读性。因此在冒号后面的代码块中使用缩进(最好四个空格代替)而非括号。
for x in array:
if x < pivot:
less.append(x)
else:
greater.append(x)
- 多句同行,使用分号分隔
a = 5; b = 6; c = 7
万物皆对象
注释
使用#,井号后面的内容将会被python解释器忽略。函数和对象的调用
变量的参数传递
赋值也被称为绑定,所以当a=b时,a和b是同一个对象,如果是复制一样的列应该用a=b[:]
另外将对象作为参数传递给函数时,那么新的局域变量创建了对原始对象的引用,而非复制,如下面的代码。
def append_element(some_list, element):
some_list.append(element)
- 动态引用,强类型
'5'+5
在VB中是10,JavaScript中是字符串'55',而在Python中会报错。但是如果是浮点数和整数进行运算是可以的。
我们可以使用isinstance(object,classinfo)来判断对象是否是某个类型。
>>>isinstance (a,str)
False
>>>isinstance (a,(str,int,list)) # 是元组中的一个返回 True
True
type()和isinstance()的区别:
class A:
pass
class B(A):
pass
isinstance(A(), A) # returns True
type(A()) == A # returns True
isinstance(B(), A) # returns True
type(B()) == A # returns False
- 属性和方法
Python的对象通常都有属性(其它存储在对象内部的Python对象)和方法(对象的附属函数可以访问对象的内部数据)。可以用obj.attribute_name
访问属性和方法,并且在句点后按Tab进行补全选择合适的方法。
可以使用getattr
,hasattr
,setattr
函数来访问对象的属性和方法。
- 鸭子类型
不关心对象的具体类型,而关注是否拥有某个特殊的方法或行为。如下面的代码,我们不关注对象类型,只要支持+
和*
的操作即可。
def calculate(a,b,c):
return (a+b)*c
example1 = calculate (1, 2, 3)
example2 = calculate ([1, 2, 3], [4, 5, 6], 2)
example3 = calculate ('apples ', 'and oranges, ', 3)
- 导入
import module
a = module.function1()
from module import function1, function2, function3
b = function2()
import module as mokuai
c = mokuai.function1()
-
二元运算符和比较运算符
可变对象和不可变对象
一般都可变,元组不可变。
标量类型
Python的标准库中有一些内建的类型,用于处理数值数据、字符串、布尔值,和日期时间。其中日期和时间的处理需要另外讨论,因为是标准库datetime模块提供的。
数值类型
分为int和float字符串
- 使用单引号和双引号都可以,对于有换行的字符串使用'''或"""都行。
c = """
This is a longer string that
spans multiple lines
"""
In [55]: c.count('\n')
Out[55]: 3
此时的变量c有4行,3个换行符。
- 字符串可以被转换为列
a=list('python')
- 反斜杠
a=r'1\n2'
b='1\\n2'
此时a和b是一致的,r代表raw。如果是单独的\n
则会转义为换行如果前面
- 字符串的模板化和格式化
template='{0:.2f} {1:s} are worth US${2:d}'
template.format(4.5560, 'Argentine Pesos', 1)
'4.56 Argentine Pesos are worth US$1'
{0:.2f}
表示第一个参数为有两位小数的浮点数
{1:s}
表示第二个参数为字符串
{2:d}
表示第三个参数为一个整数
- 字节和Unicode
用encode将这个Unicode字符串编码为UTF-8:
val = "español"
val_utf8 = val.encode('utf-8')
val_utf8
b'espa\xc3\xb1ol'
type(val_utf8)
bytes
val_utf8.decode('utf-8')
'español'
UTF-8是字节类型的
布尔值
可以用and和or组合使用。类型转换
In [91]: s = '3.14159'
In [92]: fval = float(s)
In [93]: type(fval)
Out[93]: float
In [94]: int(fval)
Out[94]: 3
In [95]: bool(fval)
Out[95]: True
In [96]: bool(0)
Out[96]: False
- None
None是Python的空值类型。如果一个函数没有明确的返回值,就会默认返回None:
In [97]: a = None
In [98]: a is None
Out[98]: True
In [99]: b = 5
In [100]: b is not None
Out[100]: True
- 日期和时间
Python内建的datetime模块提供了datetime、date和time类型。datetime类型结合了date和time,是最常使用的:
In [102]: from datetime import datetime, date, time
In [103]: dt = datetime(2011, 10, 29, 20, 30, 21)
In [104]: dt.day
Out[104]: 29
In [105]: dt.minute
Out[105]: 30
datetime转化为str:
In [108]: dt.strftime('%m/%d/%Y %H:%M')
Out[108]: '10/29/2011 20:30'
当我们使用聚类时对时间序列进行分组,替换掉datetimes的time字段很有用。
In [110]: dt.replace(minute=0, second=0)
Out[110]: datetime.datetime(2011, 10, 29, 20, 0)
delta = dt2 - dt
In [113]: delta
Out[113]: datetime.timedelta(17, 7179)
In [114]: type(delta)
Out[114]: datetime.timedelta
结果timedelta(17, 7179)指明了timedelta将17天、7179秒的编码方式。
并且加回去之后,会产生一个新的偏移datetime
In [115]: dt
Out[115]: datetime.datetime(2011, 10, 29, 20, 30, 21)
In [116]: dt + delta
Out[116]: datetime.datetime(2011, 11, 15, 22, 30)
自我理解:第一个datetime是模块,第二个是模块中的一个类。
控制流
-
if、elif和else
基础的就不记录了,判断顺序:- 但凡其中有一个条件满足了,则后面的elif和else都不再执行。
- and和or复合在一起时,从左到右执行。
- 比较式可以串在一起4 > 3 > 2 > 1返回True
-
for循环
for循环是在一个可迭代对象中迭代,是一个迭代器。- continue跳过剩下部分进行下一个迭代
- break跳出for循环,只能跳出当前的for循环。
while循环
当条件判断为False或者显式地以break结尾时才结束,break一般嵌套在if里面。pass
什么都不做,可以用于占位。range
返回一个迭代器,并且是等差数列。可以用list转化为列。下面的%是模运算。
sum = 0
for i in range(100000):
# % is the modulo operator
if i % 3 == 0 or i % 5 == 0:
sum += i
- 三元表达式
value = true-expr if condition else false-expr
相当于下面的代码,其中true-expr或false-expr可以是任何Python代码。
if condition:
value = true-expr
else:
value = false-expr
第三章 Python的数据结构、函数和文件
3.1 数据结构和序列
3.1.1 元组
- 元组
In [1]: tup = 4, 5, 6
In [2]: tup
Out[2]: (4, 5, 6)
tuple可以将任意序列或迭代器转换成元组:(可以是列表,字符串,range函数)
In [5]: tuple([4, 0, 2])
Out[5]: (4, 0, 2)
In [6]: tup = tuple('string')
In [7]: tup
Out[7]: ('s', 't', 'r', 'i', 'n', 'g')
元组的切片和列表的切片一样。
元组不能修改,但是里面的对象是可以更高。
In [9]: tup = tuple(['foo', [1, 2], True])
In [11]: tup[1].append(3)
In [12]: tup
Out[12]: ('foo', [1, 2, 3], True)
元组可以用加号串联起来,或用乘号复制。
In [13]: (4, None, 'foo') + (6, 0) + ('bar',)
Out[13]: (4, None, 'foo', 6, 0, 'bar')
In [14]: ('foo', 'bar') * 4
Out[14]: ('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')
- 拆分元组
如果你想将元组赋值给类似元组的变量,Python会试图拆分等号右边的值:
In [15]: tup = (4, 5, 6)
In [16]: a, b, c = tup
In [17]: b
Out[17]: 5
即使含有元组的元组也会被拆分:
In [18]: tup = 4, 5, (6, 7)
In [19]: a, b, (c, d) = tup
In [20]: d
Out[20]: 7
In [21]: a, b = 1, 2
In [22]: a
Out[22]: 1
In [23]: b
Out[23]: 2
In [24]: b, a = a, b
In [25]: a
Out[25]: 2
In [26]: b
Out[26]: 1
变量拆分常用来迭代元组或列表序列:
这里的format是字符串的一个方法,在使用format传入参数之前需要先规定好字符串的格式。
且下述有a、b、c三个变量需要在seq中迭代,因此seq列表中有3个对象,每个对象又有3个值,刚好可以迭代三次,如果seq列表中的对象值不是3个则会报错,如果seq=(1, 2, 3, 4, 5, 6, 7, 8, 9)同样会报错。
In [27]: seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
In [28]: for a, b, c in seq:
....: print('a={0}, b={1}, c={2}'.format(a, b, c))
a=1, b=2, c=3
a=4, b=5, c=6
a=7, b=8, c=9
python增加了高级的元组拆分功能,可以摘取前几个元素,这个方法为*rest
。当让也可以用其他名字,不一定非要rest。并且rest会从元组转变为列表。
In [29]: values = 1, 2, 3, 4, 5
In [30]: a, b, *rest = values
In [31]: a, b
Out[31]: (1, 2)
In [32]: rest
Out[32]: [3, 4, 5]
- tuple方法
使用count计算元组或列表某个值出现的频率。
In [34]: a = (1, 2, 2, 2, 3, 4, 2)
In [35]: a.count(2)
Out[35]: 4
3.1.2 列表
用[]来表示,大多情况可以和元组交叉使用。可以使用list()实体化迭代器。list(range(1,11,1))
-
添加和删除元素
- 添加
- 末尾添加
list_name.append('new_element')
- 中间插入
list_name.insert(2,'new_element')
在第三个 位置插入新的元素。 - 相比append,insert耗费的计算量大。双端加入可以使用collections库的deque。
a=deque(maxlen=10)
maxlen称为对象a的属性,此时对象a可以使用方法a.appendleft('new_element')
或a.append('new_element')
- 末尾添加
- 添加
串联和组合列表
使用加号串联
In [57]: [4, None, 'foo'] + [7, 8, (2, 3)]
Out[57]: [4, None, 'foo', 7, 8, (2, 3)]
使用extend方法追加
In [58]: x = [4, None, 'foo']
In [59]: x.extend([7, 8, (2, 3)])
In [60]: x
Out[60]: [4, None, 'foo', 7, 8, (2, 3)]
前两种方法,前者计算量更大,需要新创建一个列表。使用extend追加元素到较大的列表中更为可取。
- 排序
a是一个列表,我们可以用a.sort()
排序。
key参数给定一个方法,这个方法会对列表中的每一个元素使用,并按照方法得到的值进行排序。如len方法对每个元素使用后,返回字符串的长度,并依据长度进行排序。
In [64]: b = ['saw', 'small', 'He', 'foxes', 'six']
In [65]: b.sort(key=len)
In [66]: b
Out[66]: ['He', 'saw', 'six', 'small', 'foxes']
- 二分搜索和维护已排序的列表
bisect模块对有序列表使用,如果非有序列表会出现错误。
-
bisect
和bisect_right
返回要插入的元素在列表中的下标,如果元素存在则在最右侧插入(原列表有序,并且插入后仍然有序),bisect_left
相反。 -
insort
方法会把元素插入到有序列表中的正确位置。若列表无序(这里的有序判定reverse=False,即只有正序才算有序),返回空。默认插入到右边。insort
-
insort
和insort_right
一致,insort_left
插入到左侧。 - 字符串的的排序会按照第一个字母,第二个字母依次排序。
In [67]: import bisect
In [68]: c = [1, 2, 2, 2, 3, 4, 7]
In [69]: bisect.bisect(c, 2)
Out[69]: 4
In [70]: bisect.bisect(c, 5)
Out[70]: 6
In [71]: bisect.insort(c, 6)
In [72]: c
Out[72]: [1, 2, 2, 2, 3, 4, 6, 7]
- 切片
切片部分可以用于被赋值,也可赋值给其他变量:
In [75]: seq[3:4] = [6, 3]
也可以使用负数
seq = [7, 2, 3, 7, 5, 6, 0, 1]
In [80]: seq[-6:-2]
Out[80]: [3, 7, 5, 6]
列表也有步长即list[start:stop:step]
,step默认为1,step为负数则从末尾倒着切。
In [81]: seq[::2]
Out[81]: [7, 3, 5, 0]
In [81]: seq[::-1]
Out[81]: [1, 0, 6, 5, 7, 3, 2, 7]
In [81]: seq[-2:-6:-1]
Out[81]: [0, 6, 5, 7]
不过当step为负数时,前面的start和stop也要跟着倒着数,并且都遵从包含start不包含stop。
3.1.3 序列函数
- enumerate函数
当我们迭代一个序列时(如list),我们使用enumerate函数,可以返回(i,value)元组序列(i为序号)。
In [83]: some_list = ['foo', 'bar', 'baz']
In [84]: mapping = {}
In [85]: for i, v in enumerate(some_list):
....: mapping[v] = i
In [86]: mapping
Out[86]: {'foo': 0, 'bar': 1, 'baz': 2}
enemerate(list)本身并不是字典,不过可以通过迭代的方式填补给空字典,序号作为值(且序号从0开始)。
- sorted函数
sorted(object,reverse=True)也可倒着。
In [88]: sorted('horse race')
Out[88]: [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']
- zip函数
zip函数可以组合多个列表或元组等序列,组成一个元组列表。
In [89]: seq1 = ['foo', 'bar', 'baz']
In [90]: seq2 = ['one', 'two', 'three']
In [91]: zipped = zip(seq1, seq2)
In [92]: list(zipped)
Out[92]: [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]
zip可以处理任意数量的序列,不过元素个数取决于最短的序列。
In [93]: seq3 = [False, True]
In [94]: list(zip(seq1, seq2, seq3))
Out[94]: [('foo', 'one', False), ('bar', 'two', True)]
zip的常见用法之一是同时迭代多个序列,结合enumerate使用:
In [95]: for i, (a, b) in enumerate(zip(seq1, seq2)):
....: print('{0}: {1}, {2}'.format(i, a, b))
....:
0: foo, one
1: bar, two
2: baz, three
zip函数也可以用来解压序列,把行的列表转化为列的列表。
column1,column2,column3 = zip(*list(zip(seq1, seq2, seq3)))
>>> column1
('foo', 'bar')
>>> column2
('one', 'two')
>>> column3
(False, True)
这里的list(zip(seq1, seq2, seq3))
返回值为列表[('foo', 'one', False), ('bar', 'two', True)]
用zip(*object)
可以反向转化为列。
- reversed函数
返回一个反转的迭代器。对象可以是列表、元组、range(),字符串。
3.1.4 字典
字典更为常见的名字是哈希映射或关联数组,是键值对的大小可变集合。
dict={key1:value1,key2:value2}
,访问dict[key1]
检查字典是否包含某个键:
key2 in dict
-
删除键值对:
del dict[key]
-
dict.pop(key)
,使用pop可以把弹出的值赋值给别的变量。
生成迭代器:
keys
,values
虽然字典没有顺序,但是两个方法可以用相同的顺序输出键和值。update方法将一个字典融合到另一个字典。
dict1.update(dict2)
如果有相同的键则会用dict2覆盖到dict1,并且dict2不会改变。用序列创建字典
mapping = {}
for key, value in zip(key_list, value_list):
mapping[key] = value
后面会谈到dict comprehensions,另一种构建字典的优雅方式。
- 默认值
有时我们想取一个键的值,如果这个键不在字典里那么我们想得到一个预先设置的默认值。如下图
if key in some_dict:
value = some_dict[key]
else:
value = default_value
我们可以使用字典的pop和get方法,两者的语法都一样如:dict.get(key[,default=None])
。区别在于get不会删除掉,但pop会删除掉这个key的键值对,并且如果不给默认值会报错。
另外字典的setdefault()方法:dict.setdefault(key, default=None)
返回值:当字典中包含给定的键,则返回键对应的值,否则返回默认值。不过相比get方法,setdefault方法在键不在字典中的时候会添加该键和用默认值当作值。
In [123]: words = ['apple', 'bat', 'bar', 'atom', 'book']
In [124]: by_letter = {}
In [125]: for word in words:
.....: letter = word[0]
.....: if letter not in by_letter:
.....: by_letter[letter] = [word]
.....: else:
.....: by_letter[letter].append(word)
.....:
In [126]: by_letter
Out[126]: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
上述代码想给每个words列表中每个元素按照首字母分类,使用setdefault后的进阶方法:
for word in words:
letter = word[0]
by_letter.setdefault(letter, []).append(word)
append方法只针对于列表,by_letter.setdefault(letter, [])
总是会返回一个列表,再在这个列表的基础上append。
collections模块有一个很有用的类,defaultdict,它可以进一步简化上面。传递类型或函数以生成每个位置的默认值:
from collections import defaultdict
by_letter = defaultdict(list)
for word in words:
by_letter[word[0]].append(word)
- 有效的键类型
字典的值可以是任何Python对象,而键通常是不可变的标量类型(整数、浮点型、字符串)或元组(元组中的对象必须是不可变的)。这被称为“可哈希性”。可以用hash函数检测一个对象是否是可哈希的(可被用作字典的键):
In [128]: hash((1, 2, (2, 3)))
Out[128]: 1097636502276347782
In [129]: hash((1, 2, [2, 3])) # fails because lists are mutable
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 hash((1, 2, [2, 3])) # fails because lists are mutable
TypeError: unhashable type: 'list'
元组套元组具有可哈希性,但是元组里面套列表就不可以。
3.1.5 集合
集合是无序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。可以用两种方式创建集合:通过set函数或使用尖括号set语句:
In [133]: set([2, 2, 2, 1, 3, 3])
Out[133]: {1, 2, 3}
In [134]: {2, 2, 2, 1, 3, 3}
Out[134]: {1, 2, 3}
并且还可以计算并集,交集等数学计算。
- 合并使用union方法或
|
运算符
a.union(b)
a | b
union并不会影响a和b
- 交集,使用intersection或
&
运算符:
a.intersection(b)
{3, 4, 5}
集合的常见操作如下:
pop()是从左往右弹出的。
集合可以转化为元组获得类似列表的效果。(集合不具备可哈希性)
集合的相等只考虑内容,不考虑顺序
In [153]: {1, 2, 3} == {3, 2, 1}
Out[153]: True
3.1.6 列表、集合和字典的推导式
- 列表推导式的一般形式如下:(允许用户方便地从一个集合过滤元素,形成列表,并且在传递参数地过程中还可以修改元素)
[expr for val in collection if condition]
等同于下面的循环:
result = []
for val in collection:
if condition:
result.append(expr)
例如:给定一个字符串列表,我们可以过滤出长度在2及以下的字符串,并将其转换成大写:
In [154]: strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
In [155]: [x.upper() for x in strings if len(x) > 2]
Out[155]: ['BAT', 'CAR', 'DOVE', 'PYTHON']
- 集合和字典的推导式
set_comp = {expr for value in collection if condition}
dict_comp = {key-expr : value-expr for value in collection if condition}
集合的例子:返回strings里面字符串的长度有哪些
In [156]: unique_lengths = {len(x) for x in strings}
In [157]: unique_lengths
Out[157]: {1, 2, 3, 4, 6}
当然也可以用map函数:,map(function,iterable,...)
In [158]: set(map(len, strings))
Out[158]: {1, 2, 3, 4, 6}
字典的例子:
In [159]: loc_mapping = {val : index for index, val in enumerate(strings)}
In [160]: loc_mapping
Out[160]: {'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}
- 嵌套列表推导式
我们将all_data中的两个列表中有两个e字母的全部添加到感兴趣的名字列表中。
all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'],
['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']]
names_of_interest = []
for names in all_data:
enough_es = [name for name in names if name.count('e') >= 2]
names_of_interest.extend(enough_es)
下面的嵌套看起来复杂些,前后不变,中间的for ... in ...的嵌套顺序,前者是总的for循环(和正常写嵌套循环一样先写总的)。
In [162]: result = [name for names in all_data for name in names
.....: if name.count('e') >= 2]
In [163]: result
Out[163]: ['Steven']
some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
[[x for x in tup] for tup in some_tuples]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
3.2 函数
使用def
声明,return
返回值,当没有return没有的话返回None。想要让形参可选,直接让形参为''
,def function1(parameter='')
。但形参的数据类型没有限制为字符串。
参数的传递分为位置参数和关键字参数,且在使用时关键字参数在位置参数之后,如下的z就是关键字参数:
my_function(5, 6, z=0.7)
3.2.1 命名空间、作用域和本地函数
函数可以访问两种不同作用域的变量:全局和局部。函数内部的变量为局部,调用时创建,执行完毕后销毁。
def func():
a = []
for i in range(5):
a.append(i)
a = []
def func():
for i in range(5):
a.append(i)
前者的a是局部变量,后者是全局变量。
3.2.2 返回多个值
return多个值时返回的是一个元组。
def f():
a = 5
b = 6
c = 7
return a, b, c
a, b, c = f()
当然有时返回字典更合适:
def f():
a = 5
b = 6
c = 7
return {'a' : a, 'b' : b, 'c' : c}
3.2.3 函数是对象
re
模块不太懂
我们想对调查数据进行清理,得到格式统一的字符串(去除空格,删除标点符号,统一大小写),我们使用内建的字符串方法和正则表达式re
模块:
states = [' Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda','south carolina##', 'West virginia?']
import re
def clean_strings(strings):
result = []
for value in strings:
value = value.strip()
value = re.sub('[!#?]', '', value)
value = value.title()
result.append(value)
return result
In [173]: clean_strings(states)
Out[173]:
['Alabama',
'Georgia',
'Georgia',
'Georgia',
'Florida',
'South Carolina',
'West Virginia']
当然我们也可以把这一段连续的处理弄成列表通过嵌套对每个元素进行所有的数据清理工作。
def remove_punctuation(value): #定义一个去除标点符号的函数
return re.sub('[!#?]', '', value)
clean_ops = [str.strip, remove_punctuation, str.title] 定义一个包含3个数据清理的函数列表
def clean_strings(strings, ops):
result = []
for value in strings:
for function in ops:
value = function(value)
result.append(value)
return result
当然也可以使用map(function,iterable...)函数返回迭代器。
result = []
for x in map(remove_punctuation, states):
result.append(x)
3.2.4 匿名(Lambda)函数
python支持一种被称为匿名(lambda)函数,lambda关键字仅仅告诉python“我正在声明一个匿名函数”,与def不同的是匿名含函数对象本身是没有提供名称name属性,例如下面的函数和lambda函数是等价的。
def short_function(x):
return x * 2
equiv_anon = lambda x: x * 2
下面是一个lambda函数的使用案例,apply_to_list
函数的第二个参数为一个函数,我们使用lambda x: x * 2
代替。
def apply_to_list(some_list, f):
return [f(x) for x in some_list]
ints = [4, 0, 1, 5, 6]
apply_to_list(ints, lambda x: x * 2)
第二个案例:
In [178]: strings.sort(key=lambda x: len(set(list(x)))) #按照使用的不重复的字母数升序。
In [179]: strings
Out[179]: ['aaaa', 'foo', 'abab', 'bar', 'card']
3.2.5 柯里化:部分参数应用
柯里化(currying)是从现有函数派生出新函数的技术,例如:
def add_numbers(x, y):
return x + y
add_five = lambda y: add_numbers(5, y) #这里的第二个参数称为“柯里化的”。
此时add_five
也是一个函数。
3.2.6 生成器
以一种一致的方式对序列进行迭代(列表,文件中的行)是Python的一个重要特点,通过一种叫迭代器协议(iterator protocol)实现。
- 字典迭代后得到所有的键
- 一般能接受list对象的方法,也可以接受其他可迭代对象。如max、min、sum
- iter()函数,创建一个迭代器。
- 生成器(generator)用于创造可迭代对象。
def squares(n=10):
print('Generating squares from 1 to {0}'.format(n ** 2))
for i in range(1, n + 1):
yield i ** 2
返回单个值用return,以延迟方式返回一个值的序列使用yeild。
In [188]: for x in gen:
.....: print(x, end=' ')
Generating squares from 1 to 100
1 4 9 16 25 36 49 64 81 100
- 生成器表达式
生成器表达式(generator expression)也可以构造生成器,下面两种方式完全一样。类似于列表、字典、集合推导式生成器,把列表推导式两端的方括号改为圆括号即可。
gen = (x ** 2 for x in range(100))
def _make_gen():
for x in range(100):
yield x ** 2
gen = _make_gen()
也可以当作函数的参数:
In [191]: sum(x ** 2 for x in range(100))
Out[191]: 328350
In [192]: dict((i, i **2) for i in range(5))
Out[192]: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
- itertools模块
-
itertools.chain()
中间的对象为lists/tuples/iterables,合并并返回可迭代对象。 -
itertools.count(start=0, step=1)
返回无限的等差可迭代对象 -
itertools.filterfalse(condition,data)
如:itertools.filterfalse(lambda x:x<10,range(1,20,2))返回1-19中奇数且大于等于10的可迭代对象。 -
itertools.groupby(iterable[,keyfuc])
这里的key放函数,返回一个个(key,sub-iterator)。首先用keyfuc对每一个元素使用,然后按照返回的值当作key,返回(key,sub-iterator)。 -
itertools.combinations(iterable,k)
返回iterable中所有可能的k元元组组成的序列,不考虑元组内的顺序。——组合 -
itertools.permutations(iterable,k)
返回iterable中所有看呢鞥的k元元组组成的序列,考虑元组内的顺序。——排列 -
itertools.product(*iterable,repeat=1)
这里的iterable为多个,并为多个iterable创建笛卡尔积。例如两个迭代器相当于两列数据,如果repeat=2就相当于重复这两列数据,一共四列数据进行创建笛卡尔积。 - 略
-
In [193]: import itertools
In [194]: first_letter = lambda x: x[0]
In [195]: names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
In [196]: for letter, names in itertools.groupby(names, first_letter):
.....: print(letter, list(names)) # names is a generator
A ['Alan', 'Adam']
W ['Wes', 'Will']
A ['Albert']
S ['Steven']
3.2.7 错误和异常处理
- try-except
In [198]: float('something')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
in ()
----> 1 float('something')
ValueError: could not convert string to float: 'something'
def attempt_float(x):
try:
return float(x)
except:
return x
In [200]: attempt_float('1.2345')
Out[200]: 1.2345
In [201]: attempt_float('something')
Out[201]: 'something'
我们也可以在except后面加上特定的错误,多个错误可以用元组表示,例如:
def attempt_float(x):
try:
return float(x)
except (TypeError, ValueError):
return x
也可以加入else和finally(总是被执行)模块:
f = open(path, 'w')
try:
write_to_file(f)
except:
print('Failed')
else:
print('Succeeded')
finally:
f.close()
- IPython的异常(看不太懂)
如果是在%run一个脚本或一条语句时抛出异常,IPython默认会打印完整的调用栈(traceback),在栈的每个点都会有几行上下文:
In [10]: %run examples/ipython_bug.py
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
/home/wesm/code/pydata-book/examples/ipython_bug.py in ()
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:
自身就带有文本是相对于Python标准解释器的极大优点。你可以用魔术命令%xmode,从Plain(与Python标准解释器相同)到Verbose(带有函数的参数值)控制文本显示的数量。后面可以看到,发生错误之后,(用%debug或%pdb magics)可以进入stack进行事后调试。
3.3 文件与操作系统
使用open函数以及相对/绝对路径打开文件。
In [207]: path = 'examples/segismundo.txt'
In [208]: f = open(path)
文件中提取的行都带有完整的行结束符(EOL),
In [209]: lines = [x.rstrip() for x in open(path)]
In [210]: lines
Out[210]:
['Sueña el rico en su riqueza,',
'que más cuidados le ofrece;',
'',
'sueña el pobre que padece',
'su miseria y su pobreza;',
'',
'sueña el que a medrar empieza,',
'sueña el que afana y pretende,',
'sueña el que agravia y ofende,',
'',
'y en el mundo, en conclusión,',
'todos sueñan lo que son,',
'aunque ninguno lo entiende.',
'']
使用open创建文件对象,一定要使用close方法关闭。
In [211]: f.close()
当然使用with可以在退出代码块时自动关闭文件。
In [212]: with open(path) as f:
.....: lines = [x.rstrip() for x in f]
其中open函数第二个参数决定模式,默认为open(path,'r')
对于可读文件,常用方法时read、seek和tell。
In [213]: f = open(path)
In [214]: f.read(10) #因为以'r'模式打开,所以这里按照10个字符读取。
Out[214]: 'Sueña el r'
In [215]: f2 = open(path, 'rb') # Binary mode
In [216]: f2.read(10) #因为以'rb'模式打开,所以这里按照10个字节读取。
Out[216]: b'Sue\xc3\xb1a el '
我们看到虽然前面读取了10个字符,但是位置却是11,这是因为默认的编码用了这么多字节才解码了这10个字符。
In [217]: f.tell() #返回文件的当前位置,即文件指针当前位置。
Out[217]: 11
In [218]: f2.tell()
Out[218]: 10
我们可以使用seek方法来更改读取的位置
f.seek(0)
下面为一些常用方法
3.3.1 字节与Unicode文件
Unicode又称统一码或万国码,为每种语言中的每个字符设定了统一并且唯一的二进制编码。
UTF-8是长度可变的Unicode编码,所以当我们请求一定数量字符,python会取足够多的字节进行解码,如果是‘rb’
模式,则以准确的请求字节数进行读取。
注意:不要在二进制模式中使用seek,若文件位置位于定义Unicode的字节中间,在读取时会产生错误。
In [240]: f = open(path)
In [241]: f.read(5)
Out[241]: 'Sueña'
In [242]: f.seek(4)
Out[242]: 4
In [243]: f.read(1)
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
in ()
----> 1 f.read(1)
/miniconda/envs/book-env/lib/python3.6/codecs.py in decode(self, input, final)
319 # decode input (taking the buffer into account)
320 data = self.buffer + input
--> 321 (result, consumed) = self._buffer_decode(data, self.errors, final
)
322 # keep undecoded input until the next call
323 self.buffer = data[consumed:]
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb1 in position 0: invalid s
tart byte
In [244]: f.close()
3.4 本章小结
前三章Python的基础、环境和语法,接下来学习NumPy和Python的面向数组计算。