第三篇:Python的数据结构、函数和⽂件

一、数据结构和序列:Python的数据结构简单⽽强⼤。通晓它们才能成为熟练的Python程序员。


1、元组:元组是⼀个固定⻓度,不可改变的Python序列对象。
创建元组的最简单⽅式,是⽤逗号分隔⼀列值:
tup = 4, 5, 6
tup # 输出:(4, 5, 6)
当⽤复杂的表达式定义元组,最好将值放到圆括号内,如下所示:
nested_tup = (4, 5, 6), (7, 8)
nested_tup # 输出:((4, 5, 6), (7, 8))
⽤tuple可以将任意序列或迭代器转换成元组:
tuple([4, 0, 2]) # 输出:(4, 0, 2)
tup = tuple('string')
tup # 输出:('s', 't', 'r', 'i', 'n', 'g')

可以⽤⽅括号访问元组中的元素。和C、C++、JAVA等语⾔⼀样,序列是从0开始的:
tup[0]   # 输出:'s'
元组中存储的对象可能是可变对象。⼀旦创建了元组,元组中的对象就不能修改了:
tup = tuple(['foo', [1, 2], True])
如果元组中的某个对象是可变的,⽐如列表,可以在原位进⾏修改:
tup[1].append(3)
tup   # 输出:('foo', [1, 2, 3], True)
可以⽤加号运算符将元组串联起来:
(4, None, 'foo') + (6, 0) + ('bar',)   # 结果如下:(4, None, 'foo', 6, 0, 'bar')
元组乘以⼀个整数,像列表⼀样,会将⼏个元组的复制串联起来:
('foo', 'bar') * 4   # 结果如下:('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')
对象本身并没有被复制,只是引⽤了它。

2、拆分元组

将元组赋值给类似元组的变量,Python会试图拆分等号右边的值:
tup = (4, 5, 6)
a, b, c = tup
即使含有元组的元组也会被拆分:
tup = 4, 5, (6, 7)
a, b, (c, d) = tup
使⽤这个功能,你可以很容易地替换变量的名字。替换可以这样做:
a, b = 1, 2
b, a = a, b    # a=2, b=1

变量拆分常⽤来迭代元组或列表序列:
seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
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,这也⽤在函数签名中以抓取任意⻓度列表的位置参数:例如:
values = 1, 2, 3, 4, 5
a, b, *rest = values   # rest前面的*号才是最重要的,名字“rest”可任意
rest的部分是想要舍弃的部分,rest的名字不重要。作为惯⽤写法,许多Python程序员会将不需要的变量使⽤下划线:
a, b, *_ = values

3、tuple(元组)⽅法
⼀个很有⽤的方法是count(也适⽤于列表),统计某个值的出现频率:
a = (1, 2, 2, 2, 3, 4, 2)
a.count(2)    # 输出:4

4、列表

与元组对⽐,列表的⻓度可变、内容可以被修改。可以⽤⽅括号定义,或⽤list函数:
a_list = [2, 3, 7, None]
tup = ('foo', 'bar', 'baz')
b_list = list(tup)   # 元组转列表,使用list函数
b_list        # 输出:['foo', 'bar', 'baz']

列表和元组的语义接近,在许多函数中可以交叉使⽤。
list函数常⽤来在数据处理中实体化迭代器或⽣成器:
gen = range(10) # range(0, 10)
list(gen) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

5、添加和删除元素:⽤append在列表末尾添加元素

b_list.append('dwarf')    # ['foo', 'bar', 'baz', 'dwarf']
insert可以在特定的位置插⼊元素:
b_list.insert(1, 'red')
插⼊的序号必须在0和列表⻓度之间。
注意:与append相⽐,insert耗费的计算量⼤,因为对后续元素的引⽤必须在内部迁移,以便为新元素提供空间。如果要在序列的头部和尾部插⼊元素,你可能需要使⽤collections.deque,⼀个双尾部队列。
insert的逆运算是pop,它移除并返回指定位置的元素:

b_list.pop(2)

可以⽤remove去除某个值,remove会先寻找第⼀个值并除去:
b_list.remove('foo')
如果不考虑性能,使⽤append和remove,可以把Python的列表当做完美的“多重集”数据结构。

⽤in可以检查列表是否包含某个值:
'dwarf' in b_list   # 输出:True or False
否定in可以再加⼀个not:
'dwarf' not in b_list
在列表中检查是否存在某个值远⽐字典和集合速度慢,因为Python是线性搜索列表中的值,但在字典和集合中,在同样的时间内还可以检查其它项(基于哈希表)。

6、串联和组合列表

与元组类似,可以⽤加号将两个列表串联起来:
[4, None, 'foo'] + [7, 8, (2, 3)]    # [4, None, 'foo', 7, 8, (2, 3)]
如果已经定义了⼀个列表,⽤extend⽅法可以追加多个元素:例如:
x = [4, None, 'foo']
x.extend([7, 8, (2, 3)]) # [4, None, 'foo', 7, 8, (2, 3)]
通过加法将列表串联的计算量较⼤,因为要新建⼀个列表,并且要复制对象。⽤extend追加元素,尤其是到⼀个⼤列表中,更为可取。

7、排序
⽤sort函数将⼀个列表原地排序(不创建新的对象):
a = [7, 2, 5, 1, 3]
a.sort()   # [1, 2, 3, 5, 7]
sort有⼀些选项,有时会很好⽤。其中之⼀是⼆级排序key,可以⽤这个key进⾏排序。例如,可以按⻓度对字符串进⾏排序:
b = ['saw', 'small', 'He', 'foxes', 'six']
b.sort(key=len)   # ['He', 'saw', 'six', 'small', 'foxes']
注意:len是函数len()的名称

8、⼆分搜索和维护已排序的列表
bisect模块⽀持⼆分查找,和向已排序的列表插⼊值。bisect.bisect可以找到插⼊值后仍保证排序的位置,bisect.insort是向这个位置插⼊值:
import bisect
c = [1, 2, 2, 2, 3, 4, 7]
bisect.bisect(c, 2)   # 输出:4,表示要插入数字2的话,应该在索引为4的位置插入
bisect.bisect(c, 5)   # 输出:6
bisect.insort(c, 6)   # 插入元素:6
c            # [1, 2, 2, 2, 3, 4, 6, 7]
注意:bisect模块不会检查列表是否已排好序,进⾏检查的话会耗费⼤量计算。因此,对未排序的列表使⽤bisect不会产⽣错误,但结果不⼀定正确。

9、切⽚:⽤切片可以选取⼤多数序列类型的⼀部分
切⽚的基本形式是在⽅括号中使⽤start:stop:
seq = [7, 2, 3, 7, 5, 6, 0, 1]
seq[1:5]   # [2, 3, 7, 5]
切⽚也可以被序列赋值:
seq[3:4] = [6, 3]
seq   # [7, 2, 3, 6, 3, 5, 6, 0, 1]
切⽚的起始元素是包括的,不包含结束元素。因此,结果中包含的元素个数是stop - start。
start或stop都可以被省略,省略之后,分别默认序列的开头和结尾:
seq[:5]   # [7, 2, 3, 6, 3]
seq[3:]   # [6, 3, 5, 6, 0, 1]

负数表明从后向前切⽚:
seq[-4:]    # [5, 6, 0, 1]
seq[-6:-2]   # [6, 3, 5, 6]
在第⼆个冒号后⾯使⽤step,可以隔几个取⼀个元素:
seq[::2]     # [7, 3, 3, 6, 1],这里隔一个取一个元素
⼀个聪明的⽅法是使⽤-1,它可以将列表或元组颠倒过来:
seq[::-1]     # [1, 0, 6, 5, 3, 6, 3, 2, 7]

二、序列函数:Python有⼀些有⽤的序列函数。

1、enumerate函数
Python内建了⼀个enumerate函数,可以返回(i, value)元组序列:
for i, value in enumerate(collection):
  # do something with value
当你索引数据时,使⽤enumerate的⼀个好⽅法是计算序列(唯⼀的)dict映射到位置的值:
some_list = ['foo', 'bar', 'baz']
mapping = {}
for i, v in enumerate(some_list):
  mapping[v] = i
mapping # 输出:{'bar': 1, 'baz': 2, 'foo': 0}

2、sorted函数

sorted函数可以从任意序列的元素返回⼀个新的排好序的列表:
sorted([7, 1, 2, 6, 0, 3, 2])   # [0, 1, 2, 2, 3, 6, 7]
sorted('horse race')      # [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']
sorted函数可以接受和sort相同的参数。

3、zip函数
zip可以将多个列表、元组或其它序列成对组合成⼀个元组列表:
seq1 = ['foo', 'bar', 'baz']
seq2 = ['one', 'two', 'three']
zipped = zip(seq1, seq2)
list(zipped)   # [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]

zip的常⻅⽤法之⼀是同时迭代多个序列,可结合enumerate使⽤:
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可以被⽤来解压序列。也可以当
作把⾏的列表转换为列的列表。这个⽅法看起来有点神奇:
pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'), ('Schilling', 'Curt')]
first_names, last_names = zip(*pitchers)
first_names   # ('Nolan', 'Roger', 'Schilling')
last_names   # ('Ryan', 'Clemens', 'Curt')

4、reversed函数
reversed可以从后向前迭代⼀个序列:
list(reversed(range(10)))   # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

5、字典:Python最为重要的数据结构。
它更为常⻅的名字是哈希映射或关联数组。它是键值对的⼤⼩可变集合,键和值都是
Python对象。创建字典的⽅法之⼀是使⽤大括号,⽤冒号分隔键和值:
empty_dict = {}
d1 = {'a': 'some value', 'b': [1, 2, 3, 4]}
你可以像访问列表或元组中的元素⼀样,访问、插⼊或设定字典中的元素:
d1[7] = 'an integer'   # 在字典中添加键值对
可以⽤检查列表和元组是否包含某个值得⽅法,检查字典中是否包含某个键:
'b' in d1     # True

可以⽤del关键字或pop⽅法(返回值得同时删除键)删除值:
ret = d1.pop('dummy')
keys和values是字典的键和值的迭代器⽅法。虽然键值对没有顺序,这两个⽅法可以⽤相同的顺序输出键和值:
d1 = {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}
list(d1.keys())   # ['a', 'b', 7]
list(d1.values())   # ['some value', [1, 2, 3, 4], 'an integer']
⽤update⽅法可以将⼀个字典与另⼀个融合:
d1.update({'b' : 'foo', 'c' : 12})
d1     # {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}
update⽅法是原地改变字典,因此任何传递给update的键的旧的值都会被舍弃。

6、⽤序列创建字典

将两个序列配对组合成字典。下⾯是⼀种写法:
mapping = {}
for key, value in zip(key_list, value_list):
  mapping[key] = value
因为字典本质上是2元元组的集合,dict可以接受2元元组的列表:
mapping = dict(zip(range(5), reversed(range(5))))
mapping    # {0: 4, 1: 3, 2: 2, 3: 1, 4: 0}

7、默认值

下⾯的逻辑很常⻅:
if key in some_dict:
  value = some_dict[key]
else:
  value = default_value
因此,dict的⽅法get和pop可以取默认值进⾏返回,上⾯的if-else语句可以简写成下⾯:
value = some_dict.get(key, default_value)
get默认会返回None,如果不存在键,pop会抛出⼀个异常。关于设定值,常⻅的情况是在字典的值是属于其它集合,如列表。
例如,你可以通过⾸字⺟,将⼀个列表中的单词分类:
words = ['apple', 'bat', 'bar', 'atom', 'book']
by_letter = {}
for word in words:
  letter = word[0]
  if letter not in by_letter:
    by_letter[letter] = [word]
  else:
    by_letter[letter].append(word)
by_letter   # {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}

setdefault⽅法就正是⼲这个的。前⾯的for循环可以改写为:

for word in words:
  letter = word[0]
  by_letter.setdefault(letter, []).append(word)
by_letter    # {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}

collections模块有⼀个很有⽤的类,defaultdict,它可以进
⼀步简化上⾯。传递类型或函数以⽣成每个位置的默认值:
from collections import defaultdict
by_letter = defaultdict(list)
for word in words:
  by_letter[word[0]].append(word)

8、有效的键类型

字典的值可以是任意Python对象,⽽键通常是不可变的标量类型(整数、浮点型、字符串)或元组(元组中的对象必须是不可变的)。这被称为“可哈希性”。可以⽤hash函数检测⼀个对象是否是可哈希的(可被⽤作字典的键):例如:
hash('string')     # 输出:-2698396839112811693,可哈希
hash((1, 2, (2, 3)))   # 输出:1097636502276347782
hash((1, 2, [2, 3]))   # 报错,列表不能被哈希

要⽤列表当做键,⼀种⽅法是将列表转化为元组,只要内部元素可以被哈希,它也就可以被哈希:
d = {}
d[tuple([1, 2, 3])] = 5    # 列表转换为元组,可以被哈希,就可以当做字典的键
d   # 输出:{(1, 2, 3): 5}

9、集合

集合是⽆序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。可以⽤两种⽅式创建集合:通过set函数或使⽤大括号set语句:
set([2, 2, 2, 1, 3, 3])   # 输出:{1, 2, 3},set函数创建集合
{2, 2, 2, 1, 3, 3}     # 输出:{1, 2, 3},大括号创建集合

集合⽀持合并、交集、差分和对称差等数学集合运算。考虑两个示例集合:
a = {1, 2, 3, 4, 5}
b = {3, 4, 5, 6, 7, 8}
合并是取两个集合中不重复的元素。可以⽤(union)⽅法,或者(|)运算符:
a.union(b)      # 输出:{1, 2, 3, 4, 5, 6, 7, 8}
a | b          # 输出:{1, 2, 3, 4, 5, 6, 7, 8}
交集的元素包含在两个集合中。可以⽤intersection或&运算符:
a.intersection(b)   # 输出:{3, 4, 5}
a & b    # 输出:{3, 4, 5}

所有逻辑集合操作都有另外原地实现⽅法,它可以直接⽤结果替代集合的内容。对于⼤的集合,这么做效率更⾼:
c = a.copy()
c |= b
c      # {1, 2, 3, 4, 5, 6, 7, 8}
d = a.copy()
d &= b
d     # {3, 4, 5}

与字典类似,集合元素通常都是不可变的。要获得类似列表的元素,必须转换成元组:
my_data = [1, 2, 3, 4]
my_set = {tuple(my_data)}   # 先把列表转换成元组才可以
my_set               # {(1, 2, 3, 4)}

你还可以检测⼀个集合是否是另⼀个集合的⼦集或⽗集:
a_set = {1, 2, 3, 4, 5}
{1, 2, 3}.issubset(a_set)     # True,判断是否是子集
a_set.issuperset({1, 2, 3})    # True,判断是否是父集
集合的内容相同时,集合才对等:
{1, 2, 3} == {3, 2, 1} # True

Python的集合操作常用方法如下:

10、列表、集合和字典推导式

列表推导式是Python最受喜爱的特性之⼀。它允许⽤户⽅便的从⼀个集合过滤元素,形成列表,在传递参数的过程中还可以修改元素。形式如下:
[expr for val in collection if condition]
它等同于下⾯的for循环:
result = []
for val in collection:
  if condition:
    result.append(expr)

filter条件可以被忽略,只留下表达式就⾏。例如,给定⼀个字符串列表,我们可以过滤出⻓度在2及以下的字符串,并将其转换成⼤写:

strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
[x.upper() for x in strings if len(x) > 2]  # 输出:['BAT', 'CAR', 'DOVE', 'PYTHON']

⽤相似的⽅法,还可以推导集合和字典。字典的推导式如下所示:
dict_comp = {key-expr : value-expr for value in collection if condition}

集合的推导式与列表很像,只不过⽤的是大括号:
set_comp = {expr for value in collection if condition}

与列表推导式类似,集合与字典的推导也很⽅便,⽽且使代码的读写都很容易。
来看前⾯的字符串列表。假如我们只想要字符串的⻓度,⽤集合推导式的⽅法⾮常⽅便:
unique_lengths = {len(x) for x in strings}
unique_lengths     # 输出:{1, 2, 3, 4, 6}

map函数可以进⼀步简化:
set(map(len, strings))    # 输出:{1, 2, 3, 4, 6}

作为⼀个字典推导式的例⼦,可以创建⼀个字符串的查找映射表以确定它在列表中的位置:
loc_mapping = {val : index for index, val in enumerate(strings)}
loc_mapping     # 输出:{'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}

11、嵌套列表推导式
假设我们有⼀个包含列表的列表,列表如下所示:

all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'], ['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']]
现在假设想⽤⼀个列表包含所有的名字,这些名字中包含两个或更多的e。可以⽤for循环来做:
names_of_interest = []
for names in all_data:
  enough_es = [ nam for name in names if name.count('e') >= 2 ]
  names_of_interest.extend(enough_es)
可以⽤嵌套列表推导式的⽅法,将这些写在⼀起,如下所示:
result = [ name for names in all_data for name in names if name.count('e') >= 2 ]
result   # 输出:['Steven']

嵌套列表推导式看起来有些复杂。列表推导式的for部分是根据嵌套的顺序,过滤条件还是放在最后。
下⾯是另⼀个例⼦,将⼀个整数元组的列表扁平化成了⼀个整数列表:
some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
flattened = [x for tup in some_tuples for x in tup]
flattened # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]

记住,for表达式的顺序是与嵌套for循环的顺序⼀样(⽽不是列表推导式的顺序):

flattened = []
for tup in some_tuples:
  for x in tup:
    flattened.append(x)
你可以有任意多级别的嵌套,但是如果你有两三个以上的嵌套,你就应该考虑下代码可读性的问题了。
分辨列表推导式的列表推导式中的语法也是很重要的:
[[x for x in tup] for tup in some_tuples]  # 输出:[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
这段代码产⽣了⼀个列表的列表,⽽不是扁平化的只包含元素的列表。

三、函数

1、函数
函数是Python中最主要也是最重要的代码组织和复⽤⼿段。作为最重要的原则,如果你要重复使⽤相同或⾮常类似的代码,就需要写⼀个函数。通过给函数起⼀个名字,还可以提⾼代码的可读性。函数使⽤def关键字声明,⽤return关键字返回值:
def my_function(x, y, z=1.5):
  if z > 1:
    return z * (x + y)
  else:
    return z / (x + y)
同时拥有多条return语句也是可以的。如果到达函数末尾时没有遇到任何⼀条return语句,则返回None。
函数可以有⼀些位置参数(positional)和⼀些关键字参数(keyword)。关键字参数通常⽤于指定默认值或可选参数。在上⾯的函数中,x和y是位置参数,⽽z则是关键字参数。

也就是说,该函数可以下⾯这三种⽅式进⾏调⽤:

my_function(5, 6, z=0.7)
my_function(3.14, 7, 3.5)
my_function(10, 20)

函数参数的主要限制在于:关键字参数必须位于位置参数(如果有的话)之后。也可以以任何顺序指定关键字参数。也就是说,你不⽤记住函数参数的顺序,只要记得它们的名字就可以了。
注意:也可以⽤关键字传递位置参数。前⾯的例⼦,也可以写为:这种写法可以提⾼可读性。
my_function(x=5, y=6, z=7)
my_function(y=6, x=5, z=7)

2、命名空间、作⽤域,和局部函数

函数可以访问两种不同作⽤域中的变量:全局(global)和局部(local)。Python有⼀种更科学的⽤于描述变量作⽤域的名称,即命名空间(namespace)。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。局部命名空间是在函数被调⽤时创建的,函数参数会⽴即填⼊该命名空间。在函数执⾏完毕之后,局部命名空间就会被销毁(会有⼀些例外的情况,具体闭包的那⼀节介绍)。看看下⾯这个函数:

def func():
  a = []
  for i in range(5):
    a.append(i)

调⽤func()之后,⾸先会创建出空列表a,然后添加5个元素,最后a会在该函数退出的时候被销毁。假如我们像下⾯这样定义a:
a = []
def func():
  for i in range(5):
    a.append(i)
虽然可以在函数中对全局变量进⾏赋值操作,但是那些变量必须⽤global关键字声明成全局的才⾏。不要频繁使⽤global关键字。因为全局变量⼀般是⽤于存放系统的某些状态的。

3、返回多个值

下面函数可以返回多个值
def f():
  a = 5
  b = 6
  c = 7
  return a, b, c
a, b, c = f()
该函数其实只返回了⼀个对象,也就是⼀个元组,最后该元组会被拆包到各个结果变量中。在上⾯的例⼦中,我们还可以这样写:
return_value = f()
这⾥的return_value将会是⼀个含有3个返回值的三元元组。此外,还有⼀种⾮常具有吸引⼒的多值返回⽅式——返回字典:

def f():
  a = 5
  b = 6
  c = 7
  return {'a': a, 'b': b, 'c': c}
取决于⼯作内容,第⼆种⽅法可能很有⽤。

4、函数也是对象

假设我们有下⾯这样⼀个字符串数组,希望对其进⾏⼀些数据清理⼯作并执⾏⼀堆转换:
states = [' Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda', 'south carolina##', 'West virginia?']
为了得到⼀组能⽤于分析⼯作的格式统⼀的字符串,需要做很多事情:去除空⽩符、删除各种标点符号、正确的⼤写格式等。做法之⼀是使⽤内建的字符串⽅法和正则表达式re模块:

 1 import re
 2 def clean_strings(strings):
 3      result = []
 4      for value in strings:
 5           value = value.strip()
 6           value = re.sub('[!#?]', '', value)
 7           value = value.title()
 8           result.append(value)
 9      return result
10 # 输出如下:
11 ['Alabama',
12  'Georgia',
13  'Georgia',
14  'Georgia',
15  'Florida',
16  'South Carolina',
17  'West Virginia']

其实还有另外⼀种不错的办法:将需要在⼀组给定字符串上执⾏的所有运算做成⼀个列表:

def remove_punctuation(value):
     return re.sub('[!#?]', '', value)
clean_ops = [str.strip, remove_punctuation, str.title]    # 函数列表
def clean_strings(strings, ops):
     result = []
     for value in strings:
          for function in ops:
               value = function(value)
          result.append(value)
     return result
clean_strings(states, clean_ops)        # 调用函数,输出如下:
['Alabama',
 'Georgia',
 'Georgia',
 'Georgia',
 'Florida',
 'South Carolina',
 'West Virginia']

这种多函数模式使你能在很⾼的层次上轻松修改字符串的转换⽅式。此时的clean_strings也更具可复⽤性!
还可以将函数⽤作其他函数的参数,⽐如内置的map函数,它⽤于在⼀组数据上应⽤⼀个函数:
for x in map(remove_punctuation, states):
  print(x.strip())
# 输出如下:
Alabama
Georgia
Georgia
georgia
FlOrIda
south carolina
West virginia

5、匿名(lambda)函数
Python⽀持⼀种被称为匿名的、或lambda函数。由单条语句组成,该语句的结果就是返回值。
它是通过lambda关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是⼀个匿名函数”。
def short_function(x):
  return x * 2
equiv_anon = lambda x: x * 2   # 这条命令和上面的两个命令是一样的
lambda函数在数据分析⼯作中⾮常⽅便,很多数据转换函数都以函数作为参数的。直接传⼊lambda函数⽐编写完整函数声明要少输⼊很多字(也更清晰),
甚⾄⽐将lambda函数赋值给⼀个变量还要少输⼊很多字。看看下⾯这个简单的例⼦:
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)
虽然你可以直接编写[x *2 for x in ints],但是这⾥我们可以⾮常轻松地传⼊⼀个⾃定义运算给apply_to_list函数。
再来看另外⼀个例⼦。假设有⼀组字符串,你想要根据各字符串不同字⺟的数量对其进⾏排序:
strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
strings.sort(key=lambda x: len(set(list(x))))
strings # 输出:['aaaa', 'foo', 'abab', 'bar', 'card']

注意:lambda函数之所以会被称为匿名函数,与def声明的函数不同,原因之⼀就是这种函数对象本身是没有提供名称name属性。

6、柯⾥化:部分参数应⽤

柯⾥化(currying)是⼀个有趣的计算机科学术语,它指的是通过“部分参数应⽤”(partial argument application)从现有函数派⽣出新函数的技术。例如,有⼀个执⾏两数相加的简单函数:
def add_numbers(x, y):
  return x + y
通过这个函数,可以派⽣出⼀个新的只有⼀个参数的函数 -- add_five,它⽤于对其参数加5:
add_five = lambda y: add_numbers(5, y)
add_numbers的第⼆个参数称为“柯⾥化的”(curried)。
内置的functools模块可以⽤partial函数将此过程简化:
from functools import partial
add_five = partial(add_numbers, 5)

7、⽣成器

能以⼀种⼀致的⽅式对序列进⾏迭代(⽐如列表中的对象或⽂件中的⾏)是Python的⼀个重要特点。这是通过⼀种叫做迭代器协议(iterator protocol,它是⼀种使对象可迭代的通⽤⽅式)的⽅式实现的,⼀个原⽣的使对象可迭代的⽅法。⽐如说,对字典进⾏迭代可以得到其所有的键:
some_dict = {'a': 1, 'b': 2, 'c': 3}
for key in some_dict:
  print(key)
当你编写for key in some_dict时,Python解释器⾸先会尝试从some_dict创建⼀个迭代器:
dict_iterator = iter(some_dict)
dict_iterator   # <dict_keyiterator at 0x7fbbd5a9f908>,键迭代器
迭代器是⼀种特殊对象,它可以在诸如for循环之类的上下⽂中向Python解释器输送对象。
⼤部分能接受列表之类的对象的⽅法也都可以接受任何可迭代对象。⽐如min、max、sum等内置⽅法以及list、tuple等类型构造器:
list(dict_iterator) # 输出:['a', 'b', 'c']

⽣成器(generator)是构造新的可迭代对象的⼀种简单⽅式。⼀般的函数执⾏之后只会返回单个值,⽽⽣成器则是以延迟的⽅式返回⼀个值序列,即每返回⼀个值之后暂停,直到下⼀个值被请求时再继续。要创建⼀个⽣成器,只需将函数中的return替换为yeild即可:

 1 def squares(n=10):
 2      print('Generating squares from 1 to {0}'.format(n ** 2))
 3      for i in range(1, n + 1):
 4           yield i ** 2
 5 调⽤该⽣成器时,没有任何代码会被⽴即执⾏:
 6 gen = squares()
 7 gen    # 输出:<generator object squares at 0x000001DAC0EF35C8>
 8 直到你从该⽣成器中请求元素时,它才会开始执⾏其代码:
 9 for x in gen:
10      print(x, end=' ')
11 Generating squares from 1 to 100
12 1 4 9 16 25 36 49 64 81 100
13 
14 它跟下⾯这个冗⻓得多的⽣成器是完全等价的:
15 def _make_gen():
16      for x in range(100):
17           yield x ** 2
18 gens = _make_gen()
19 
20 ⽣成器表达式也可以取代列表推导式,作为函数参数:
21 sum(x ** 2 for x in range(100))        # 输出:328350
22 dict((i, i **2) for i in range(5))        # 输出:{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

8、itertools模块
标准库itertools模块中有⼀组⽤于许多常⻅数据算法的⽣成器。例如,groupby可以接受任何序列和⼀个函数。
它根据函数的返回值对序列中的连续元素进⾏分组。下⾯是⼀个例⼦:
import itertools
first_letter = lambda x: x[0]
names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
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']


⼀些有⽤的itertools函数如下所示:

9、错误和异常处理
在数据分析中,许多函数只⽤于部分输⼊。例如,Python的float函数可以将字符串转换成浮点数,但输⼊有误时,有ValueError错误。
假如想优雅地处理float的错误,让它返回输⼊值。我们可以写⼀个函数,在try/except中调⽤float:

 1 def attempt_float(x):
 2      try:
 3           return float(x)
 4      except:
 5           return x
 6 当float(x)抛出异常时,才会执⾏except的部分
 7 可以⽤元组包含多个异常:
 8 def attempt_float(x):
 9      try:
10           return float(x)
11      except (TypeError, ValueError):
12           return x
13 某些情况下,可能不想抑制异常,你想⽆论try部分的代码是否成功,都执⾏⼀段代码。可以使⽤finally14 f = open(path, 'w')
15 try:
16      write_to_file(f)
17 finally:
18      f.close()
19 这⾥,⽂件处理f总会被关闭。相似的,你可以⽤else让只在try部分成功的情况下,才执⾏代码:
20 f = open(path, 'w')
21 try:
22      write_to_file(f)
23 except:
24      print('Failed')
25 else:
26      print('Succeeded')
27 finally:
28      f.close()

10、IPython的异常
如果是在%run⼀个脚本或⼀条语句时抛出异常,IPython默认会打印完整的调⽤栈(traceback),在栈的每个点都会有⼏⾏上下⽂。

⾃身就带有⽂本是相对于Python标准解释器的极⼤优点。你可以⽤魔术命令%xmode,从Plain(与Python标准解释器相同)到Verbose(带有函数的参数值)控制⽂本显示的数量。后⾯可以看到,发⽣错误之后,(⽤%debug或%pdb magics)可以进⼊stack进⾏事后调试。

四、⽂件和操作系统


1、文件操作
打开⼀个⽂件以便读写,使⽤内置的open函数以及⼀个相对或绝对的⽂件路径:例如:
path = 'examples/segismundo.txt'
f = open(path)
默认情况下,⽂件是以只读模式('r')打开的。然后,就可以像处理列表那样来处理
这个⽂件句柄 f 了,⽐如对⾏进⾏迭代:
for line in f:
  pass
从⽂件中取出的⾏都带有完整的⾏结束符(EOL),因此你常常会看到下⾯这样的代码(得到⼀组没有EOL的⾏):

lines = [ x.rstrip() fo x in open(path) ]   # 注意:在实际打开的方法是下面这样
# lines = [ x.rstrip().decode("utf-8") fo x in open(path, 'rb') ]
lines   # 输出lines内容
如果使⽤open创建⽂件对象,⼀定要⽤close关闭它。关闭⽂件可以返回操作系统资源:
⽤with语句可以可以更容易地清理打开的⽂件:在退出代码块时,⾃动关闭⽂件。
with open(path, 'rb') as f:
  lines = [x.rstrip().decode("utf-8") for x in f]

如果输⼊f =open(path,'w'),就会有⼀个新⽂件被创建在当前工作目录,并覆盖掉该位置原来的任何数据。
另外有⼀个x⽂件模式,它可以创建可写的⽂件,但是如果⽂件路径存在,就⽆法创建。


Python的打开⽂件模式如下:

对于可读⽂件,⼀些常⽤的⽅法是read、seek和tell。read会从⽂件返回字符。字符的内容是由⽂件的编码决定的(如UTF-8),如果是⼆进制模式打开的就是原始字节:
f = open(path)
f.read(10)         # 输出:'Sueña el r'
f2 = open(path, 'rb')   # Binary mode
f2.read(10)        # 输出:b'Suexc3xb1a el '
read模式会将⽂件句柄的位置提前,提前的数量是读取的字节数。tell可以给出当前的位置:
f.tell()          # 输出:11,也有可能是10
f2.tell()          # 输出:10
尽管我们从⽂件读取了10个字符,位置却是11,这是因为⽤默认的编码⽤了这么多字节才解码了这10个字符。可以⽤sys模块检查默认的编码:
import sys
sys.getdefaultencoding()   # 输出:'utf-8'
seek将⽂件位置更改为⽂件中的指定字节:
f.seek(3)          # 输出:3
f.read(1)          # 输出:读取的字符
关闭⽂件:
f.close()
f2.close()

向⽂件写⼊,可以使⽤⽂件的write或writelines⽅法。例如,我们可以创建⼀个⽆空⾏版的tmp.txt:
with open('tmp.txt', 'w') as handle:
  handle.writelines(x for x in open(path) if len(x) > 1)    # 打开另一个文件去掉空行并写入tmp.txt文件
with open('tmp.txt') as f:
  lines = f.readlines()

Python重要的文件方法或属性

2、⽂件的字节和Unicode
Python⽂件的默认操作是“⽂本模式”,也就是说,你需要处理Python的字符串(即Unicode)。它与“⼆进制模式”相对,⽂件模式加⼀个b。
UTF-8是⻓度可变的Unicode编码,所以当我从⽂件请求⼀定数量的字符时,Python会从⽂件读取⾜够多的字节进⾏解码。
如果以“rb”模式打开⽂件,则读取确切的请求字节数。取决于⽂本的编码,你可以将字节解码为str对象,但只有当每个编码的Unicode字符都完全成形时才能这么做。

⽂本模式结合了open的编码选项,提供了⼀种更⽅便的⽅法将Unicode转换为另⼀种编码:
sink_path = 'sink.txt'
with open(path) as source:
  with open(sink_path, 'xt', encoding='iso-8859-1') as sink:
    sink.write(source.read())
with open(sink_path, encoding='iso-8859-1') as f:
  print(f.read(10))

注意,不要在⼆进制模式中使⽤seek。如果⽂件位置位于定义Unicode字符的字节的中间位置,读取后⾯会产⽣错误。
如果你经常要对⾮ASCII字符⽂本进⾏数据分析,通晓Python的Unicode功能是⾮常重要的。

原文地址:https://www.cnblogs.com/Micro0623/p/10058557.html