列表、集合以及字典的推导式
列表推导式是最受欢迎的 Python 的语言特性之一,只需一条简洁的表达式,即可对一组元素进行过滤,并对得到的元素进行转换变形。基本形式:
1 | [expr for value in collection if condition] |
这相当于下面这段 for 循环:
1 | result = [] |
过滤条件可以省略。滤除长度小于等于 2 的字符串,并将剩下的字符串转换成大写字母形式:
1 | strings = ['a', 'as', 'bat', 'car', 'dove', 'python'] |
字典推导式的基本形式,产生的是字典:
1 | dict_comp = {key_expr:value_expr for value in collection if condition} |
集合推导式的基本形式,跟列表推导式的唯一区别就是花括号:
1 | set_comp = {expr for value in collection if condition} |
推导式都只是语法糖而已,使代码变得更容易读写。
构造一个集合,其内容为列表字符串的各种长度:
1 | unique_lengths = {len(x) for x in strings} |
为这些字符串创建一个指向其列表位置的映射关系:
1 | loc_mapping = {val:index for index, val in enumerate(strings)} |
嵌套列表推导式
嵌套 for 循环中各个 for 的顺序是怎样的,嵌套推导式中各个 for 表达式的顺序就是怎样的。将一个由整数元组构成的列表扁平化为一个简单的整数列表:
1 | some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)] |
如果嵌套超过两三层,就需要思考一下数据结构的设计问题了。注意与“列表推导式中的列表推导式”之间的区别:
1 | [[x for x in tup] for tup in some_tuples] |
函数
函数是用 def 关键字声明的,并使用 return 关键字返回。可以有一些位置参数(positional)和一些关键字参数(keyword):
1 | def my_function(x, y, z=1.5): |
关键字参数必须位于位置参数之后,你可以任何顺序指定关键字参数。
命名空间、作用域,以及局部函数
函数可以访问两种不同作用域中的变量:全局(global)和局部(local)。局部命名空间(namespace)是在函数被调用时创建的,执行完毕之后就会被销毁。
在函数中对全局变量进行赋值操作,必须用 global 关键字声明成全局的:
1 | a = None |
不要频繁使用 global 关键字,因为全局变量一般是用于存放系统的某些状态的。如果用了很多,说明需要面向对象编程(使用类)。
严格意义上来说,所有函数都是某个作用域的局部函数。
返回多个值
许多函数都可能会有多个输出(在该函数内部计算出的数据结构或其他辅助数据),其实只返回了一个对象,也就是一个元组。
返回字典:
1 | def f(): |
函数亦为对象
假设我们有下面这样一个字符串数组,希望对其进行一些数据清理工作并执行一堆转换:
1 | states = ['Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda', 'south carolina##', 'West virginia?'] |
将需要在一组给定字符串上执行的所有运算做成一个列表:
1 | import re |
然后我们就有了:
1 | clean_strings(states, clean_ops) |
这种多函数模式,能在很高的层次上,轻松修改字符串的转换方式。此时的 clean_strings 也更具可复用性。
还可以将函数用作其他函数的参数。map 函数用于在一组数据上应用一个函数:
1 | map(remove_punctuation, states) |
匿名(lambda)函数
仅由单条语句组成,该语句的结果就是返回值。
通过 lambda 关键字定义的,没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。
很多数据转换函数都以函数作为参数,直接传入 lambda 函数比编写完整的函数声明要少输入很多字,也更清晰:
1 | def apply_to_list(some_list, f): |
根据各字符串不同字母的数量对其进行排序:
1 | strings = ['foo', 'card', 'bar', 'aaaa', 'abab'] |
lambda 函数之所以会被称为匿名函数,原因之一就是这种函数对象,本身是没有提供名称属性的。
闭包:返回函数的函数
闭包就是由其他函数动态生成,并返回的函数。
关键性质:被返回的函数可以访问,其创建者局部命名空间中的变量。
虽然可以修改任何内部状态对象(比如向字典添加键值对),但不能绑定外层函数作用域中的变量。解决办法是,修改字典或列表,而不是绑定变量:
1 | def make_counter(): |
可以编写带有大量选项的非常一般化的函数,然后再组装出更简单、更专门化的函数:
1 | def format_and_pad(template, space): |
通过面向对象编程,这种模式也能用类来实现。
扩展调用语法和 args、*kwargs
在 Python 中,函数参数的工作方式其实很简单,位置和关键字参数分别被打包成元组和字典。
这一切都是在幕后悄悄发生的,函数实际接收到的是一个元组 args 和一个字典 kwargs。
柯里化:部分参数应用
柯里化(currying)指的是:通过部分参数应用(partial argument application)从现有函数派生出新函数的技术。
其实只是定义了一个可以调用现有函数的新函数而已:
1 | def add_numbers(x, y): |
add_numbers 的第二个参数称为“柯里化的”。内置的 functools 模块可以用 partial 函数将此过程简化:
1 | from functools import partial |
生成器
能以一种一致的方式对序列进行迭代,是 Python 的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol)的方式实现的:
1 | some_dict = {'a':1, 'b':2, 'c':3} |
迭代器是一种特殊对象,当你编写 for key in some_dict 时,Python 解释器首先会尝试从 some_dict 创建一个迭代器:
1 | dict_iterator = iter(some_dict) |
生成器(generator)是构造可迭代对象的一种简单方式。生成器以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。
要创建一个生成器,只需要将函数中的 return 替换为 yeild 即可:
1 | def squares(n=10): |
调用该生成器时,没有任何代码会被立即执行:
1 | gen = squares() |
直到你从该生成器中请求元素时,它才会开始执行其代码:
1 | for x in gen: |
生成器表达式
生成器表达式(generator expression)是构造生成器的最简单方式。类似于列表、字典、集合推导式,创建方式为,把列表推导式两端的方括号改成圆括号:
1 | gen = (x ** 2 for x in xrange(100)) |
生成器表达式可用于任何接受生成器的 Python 函数:
1 | sum(x ** 2 for x in xrange(100)) |
itertools 模块
标准库 itertools 模块中有一组用于许多常见数据算法的生成器。例如,groupby 可以接受任何序列和一个函数,根据函数的返回值,对序列中的连续元素进行分组:
1 | import itertools |
文件和操作系统
Python 在文本和文件处理方面很流行。
为了打开一个文件以便读写,可以使用内置的 open 函数以及一个相对或绝对的文件路径:
1 | path = 'ch13/segismundo.txt' |
默认情况下,文件是以只读模式(’r’)打开的。然后,我们就可以处理这个文件句柄 f 了:
1 | for line in f: |
Python 的文件模式:
重要的 Python 文件方法或属性: