原文地址:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
Python 简介
Python解释器
- CPython: 官方版本的解释器。这个解释器是用 C 语言开发的,所以叫 CPython。在命令行下运行 Python 就是启动 CPython 解释器。
- IPython: CPython 之上的一个交互式解释器,也就是说,IPython 只是在交互方式上有所增强,但是执行 Python 代码的功能和 CPython 是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了 IE。
- CPython 用
>>>
作为提示符,而 IPython 用In [序号]:
作为提示符。 - Jython: 运行在 Java 平台上的 Python 解释器,可以直接把 Python 代码编译成 Java 字节码执行。
- IronPython: 和 Jython 类似,只不过 IronPython 是运行在微软 .Net 平台上的 Python 解释器,可以直接把 Python 代码编译成 .Net 的字节码。
- Jupyter Notebook: 此前被称为IPython notebook,是一个交互式笔记本,支持运行 40 多种编程语言。本质是一个 Web 应用程序,便于创建和共享文学化程序文档,支持实时代码,数学方程,可视化和 markdown。 用途包括数据清理和转换、数值模拟、统计建模、机器学习等等。
第一个 Python 程序
exit()
退出 Python。
python hello.py
执行程序。
提醒:能不能像.exe
文件那样直接运行.py
文件呢?在 Windows 上是不行的,但是,在 Mac 和 Linux 上是可以的,方法是在.py
文件的第一行加上一个特殊的注释:
#!/usr/bin/env python3
print('hello, world')
然后,通过命令给hello.py
以执行权限:
$ chmod a+x hello.py
Python代码运行助手
print()
会依次打印每个字符串,遇到逗号,
会输出一个空格。
name = input()
name = input('please enter your name: ')
print('hello,', name)
input()
返回的数据类型是str
,str
不能直接和整数比较,必须先把str
转换成整数。Python 提供了int()
函数来完成这件事情。
Python 基础语法
预备知识
- 注释:用
#
开头,行内最后的注释也有用//
的; - Python 大小写敏感;
- 代码段由缩进控制,因此复制可能导致意外发生,为了尽量减少意外,可能需要少用多重缩进,并且习惯上用将 1 个
tab
对应为 4 个空格; - 注意
if
和else
后面的:
,这表明接下来是一个block
; - 转义:
\
用于字符的转义,而%%
则用于占位符情形下%
的转义;r''
用于简化转义;如果字符串内部有很多换行,用\n
写在一行里不好阅读,因此可以用'''...'''
定义多行文本,类似的,用r'''...'''
定义不必转义的多行文本,关于字符串中的'
和"
,如果只有一种,则用另一种引括即可,如果两种都有,就需要用\'
和\"
进行转义; - 占位符:
%d
对应整数,%f
对应浮点数,%s
对应字符串,%x
对应十六进制数,其中%s
是万金油,而%f
还可以指定显示格式,如%.1f
对应只显示 1 位小数的浮点数;
基础语法
- 常见运算符与常量:
+, -, *, **, /, //, True, False, and, or, not, &, |, !=, >=, <=, None, PI(常量经常用大写), /, //(地板除), %(余数)
; - Python 的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如 Java 对 32 位整数的范围限制在
-2147483648
-2147483647
。 - Python 的浮点数也没有大小限制,但是超出一定范围就直接表示为
inf
(无限大)。
数据类型
- 基础的类型有
字符串
、整数
、浮点数
、布尔值
; - 复杂的类型有
list, tuple
,其中的list
类似于 Matlab 中的cell
类型,用[]
进行定义,tuple
中的内容一旦定义,不能再修改,用()
定义1; - 其它还有
dict, set
,其中dict
对应key-value
对,如d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
所示,用{:,}
的方式定义,包含get()
与pop()
方法,如'Thomas' in d
所示,还可以用in
这一操作;set
和dict
类似,也是一组key
的集合,但不存储value
。由于key
不能重复,所以,在set
中,没有重复的key
,用set([,])
方式定义,可以使用add()
和remove()
方法,参考下面的例子:
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
字符串
- 对于字符串而言,在内存中统一为 Unicode,但是具体到终端时,可能是各种类型的编码;
- 对于单个字符的编码,Python 提供了
ord()
函数获取字符的整数表示,chr()
函数把编码转换为对应的字符; - 如果知道字符的整数编码,还可以用十六进制这么写 str:
'\u4e2d\u6587'
,这其实对应于字符串中文
; - 由于 Python 的字符串类型是
str
,在内存中以 Unicode 表示,一个字符对应若干个字节(英文字母是 1 个字节,汉字是 3 个字节)。如果要在网络上传输,或者保存到磁盘上,就需要把str
变为以字节为单位的bytes
。Python 对bytes
类型的数据用带b
前缀的单引号或双引号表示:x = b'ABC'
,要注意区分'ABC'
和b'ABC'
,前者是str
,后者虽然内容显示得和前者一样,但bytes
的每个字符都只占用一个字节2。
以 Unicode 表示的str
通过encode()
方法可以编码为指定的bytes
,例如:
>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
纯英文的str
可以用 ASCII 编码为bytes
,内容是一样的,含有中文的str
可以用 UTF-8 编码为bytes
。含有中文的str
无法用 ASCII 编码,因为中文编码的范围超过了 ASCII 编码的范围,Python 会报错。
在bytes
中,无法显示为 ASCII 字符的字节,用\x##
显示。
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes
。要把bytes
变为str
,就需要用decode()
方法:
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
要计算 str 包含多少个字符,可以用len()
函数:
>>> len('ABC')
3
>>> len('中文')
2
len()
函数计算的是str
的字符数,如果换成bytes
,len()
函数就计算字节数:
>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6
可见,1 个中文字符经过 UTF-8 编码后通常会占用 3 个字节,而 1 个英文字符只占用 1 个字节。
在操作字符串时,我们经常遇到str
和bytes
的互相转换。为了避免乱码问题,应当始终坚持使用 UTF-8 编码对str
和bytes
进行转换。
List 和 tuple
- list:是一种
有序
的集合,可以随时添加[append, insert(i, value)]
和删除[pop, pop(i)]
其中的元素,list
中的元素从0
号开始,其中-1
表示最后一个元素,索引越界时,会抛出IndexError
错误;list
可以作为另一个list
的元素,此时的访问方法是myList[i][j]
,初始的赋值用classmates = ['Michael', 'Bob', 'Tracy']
完成,注意其中用的是[]
,之后操纵元素时用的是函数的()
; - tuple:和
list
非常类似,但是tuple
一旦初始化就不能,初始的赋值用修改
classmates = ('Michael', 'Bob', 'Tracy')
完成,注意其中用的是()
,并且之后元素的不可3。改变
tuple
所谓的不变
是说,tuple
的每个元素,指向永远不变。即指向'a'
,就不能改成指向'b'
,指向一个list
,就不能改成指向其他对象,但指向的这个list
本身是可变的!
dict 和 list
注意dict
内部存放的顺序和key
放入的顺序没有关系。和list
比较,dict
有以下几个特点:
- 查找和插入的速度极快,不会随着
key
的增加而变慢; - 需要占用大量的内存,内存浪费多。
而list
相反,所以,dict
是用空间来换取时间的一种方法。
dict
可以用在需要高速查找的很多地方,在 Python 代码中几乎无处不在,正确使用dict
非常重要,需要牢记的第一条就是dict
的key
必须是不可变对象。
循环、判断
if 语句
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>
循环语句:for 和 while
for...in
循环,依次把list
或tuple
中的每个元素迭代出来
sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
sum = sum + x
print(sum)
如果要计算 1-100 的整数之和,从 1 写到 100 有点困难,幸好 Python 提供一个range()
函数,可以生成一个整数序列,再通过list()
函数可以转换为list
。比如range(5)
生成的序列是从 0 开始小于 5 的整数:
>>> list(range(5))
[0, 1, 2, 3, 4]
range(101)
就可以生成 0-100 的整数序列,计算如下:
sum = 0
for x in range(101):
sum = sum + x
print(sum)
从上面的例子看业,range()
生成的序列,即使不转换成list
,也可以循环操作。
while
循环,只要条件满足,就不断循环,条件不满足时退出循环。比如我们要计算 100 以内所有奇数之和,可以用 while 循环实现:
sum = 0
n = 99
while n > 0:
sum = sum + n
n = n - 2
print(sum)
可以用break
以及continue
跳出循环,有些时候,如果代码写得有问题,会让程序陷入死循环,也就是永远循环下去。这时可以用Ctrl+C退出程序,或者强制结束 Python 进程。
函数
内置函数列表:https://docs.python.org/3/library/functions.html
- 类型转换:
int()
,float()
,str()
,bool()
,hex()
,enumerate()
4,iter()
5,list()
6; - 类型检查:
isinstance(x, str)
; - 是否包含关键字:
in
;
定义函数
在 Python 中,定义一个函数要使用def
语句,依次写出函数名、括号、括号中的参数和冒号:
,然后,在缩进块中编写函数体,函数的返回值用return
语句返回。如果没有return
语句,函数执行完毕后也会返回结果,只是结果为None
。return None
可以简写为return
。
def my_abs(x):
if x >= 0:
return x
else:
return -x
如果已经把my_abs()
的函数定义保存为abstest.py
文件了,那么,可以在该文件的当前目录下启动 Python 解释器,用from abstest import my_abs
来导入my_abs()
函数,注意abstest
是文件名(不含.py
扩展名)。
更稳健的函数
- 空函数:如果想定义一个什么事也不做的空函数,可以用
pass
语句。
def nop():
pass
- 参数检查:调用函数时,如果参数个数不对,Python 解释器会自动检查出来,并抛出
TypeError
,但是如果参数类型不对,Python 解释器就无法帮我们检查。数据类型检查可以用内置函数isinstance()
实现。
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
- 返回多个值:返回值是一个
tuple
!但是,在语法上,返回一个tuple
可以省略括号,而多个变量可以同时接收一个tuple
,按位置赋给对应的值,所以,Python 的函数返回多值其实就是返回一个tuple
,但写起来更方便。
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0
>>> r = move(100, 100, 60, math.pi / 6)
>>> print(r)
(151.96152422706632, 70.0)
函数的参数
Python 的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数、关键字参数和命名关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。下面是关于函数参数的一些重要规则的总结:
- 默认参数必须指向不变对象;
- 可变参数内部是
()-tuple
,关键字参数内部是{}-dict
; - 可变参数在语法上是
*
,关键字参数是**
,命名关键字参数是单独的 *
,如果有可变参数,则命名关键字参数不再需要单独的使用特殊分隔符*
; - 可变参数本身调用时,参数可以是
list-()
或tuple-[]
; - 对于任意函数,都可以通过类似
func(*args, **kw)
的形式调用它,无论它的参数是如何定义的,但是这种做法容易引起混淆,不推荐; - 参数定义的顺序必须是:
必选参数
、默认参数
、可变参数
、命名关键字参数
和关键字参数
。
默认参数(必须指向不变对象)
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
必选参数在前,默认参数在后,否则 Python 的解释器会报错;当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。有多个默认参数时,调用的时候,既可以按顺序提供默认参数,比如调用enroll('Bob', 'M', 7)
,意思是,除了name
,gender
这两个参数外,最后 1 个参数应用在参数age
上,city
参数由于没有提供,仍然使用默认值。也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll('Adam', 'M', city='Tianjin')
,意思是,city
参数用传进去的值,其他默认参数继续使用默认值。
警告:默认参数很有用,但使用不当,会有很大的麻烦。
下面的例子先定义一个函数,传入一个list
,添加一个'END'
再返回:
def add_end(L=[]):
L.append('END')
return L
正常调用时,结果似乎不错:
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
使用默认参数调用时,一开始结果也是对的:
>>> add_end()
['END']
但是,再次调用add_end()
时,结果就不对了:
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
默认参数是[]
,但是函数似乎每次都记住了上次添加了'END'
后的list
。
原因是 Python 函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
提醒:默认参数必须指向不变对象!
要修改上面的例子,我们可以用None
这个不变对象来实现:
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
现在,无论调用多少次,都不会有问题:
>>> add_end()
['END']
>>> add_end()
['END']
为什么要设计str
、None
这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
可变参数(函数内部为 tuple,定义和引用都可以用 *)
要定义出这种函数,必须确定输入的参数。由于参数个数不确定,首先想到可以把a
,b
,c
……作为一个list
或tuple
传进来,但是调用的时候,需要先组装出一个list
或tuple
,如果利用可变参数,调用函数的方式可以简化。
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
定义可变参数和定义一个list
或tuple
参数相比,仅仅在参数前面加了一个*
号。在函数内部,参数numbers
接收到的是一个tuple
,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括 0 个参数。如果已经有一个list
或者tuple
,可以将其中的元素一个一个传递给可变参数函数,但是太繁琐,所以 Python 允许在list
或tuple
前面加一个*
号,把list
或tuple
的元素变成可变参数传进去,这种写法相当有用,而且很常见。
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
关键字参数(函数内部为 dict,定义和引用都可以用 **)
可变参数允许你传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple
。而关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
。请看示例:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
函数person
除了必选参数name
和age
外,还接受关键字参数kw
。在调用该函数时,可以只传入必选参数:
>>> person('Michael', 30)
name: Michael age: 30 other: {}
也可以传入任意个数的关键字参数:
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
关键字参数有什么用?它可以扩展函数的功能
。比如,在person
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,也可以先组装出一个dict
,然后,把该dict
转换为关键字参数传进去:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
当然,上面复杂的调用可以用简化的写法7:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra
表示把extra
这个dict
的所有key-value
用关键字参数传入到函数的**kw
参数,kw
将获得一个dict
,注意kw
获得的dict
是extra
的一份拷贝,对kw
的改动不会影响到函数外的extra
。
命名关键字参数(限制参数名称,单独的 *)
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw
检查。
仍以person()
函数为例,我们希望检查是否有city
和job
参数:
// 这种做法更灵活
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)
但是调用者仍可以传入不受限制
的关键字参数:
>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数。这种方式定义的函数如下:
// 这种做法更严谨
def person(name, age, *, city, job):
print(name, age, city, job)
定义:和关键字参数
**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。
调用方式如下:
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
提示:如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符
*
了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
警告:命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
由于调用时缺少参数名city
和job
,Python 解释器把这 4 个参数均视为位置参数,但person()
函数仅接受 2 个位置参数。
命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
由于命名关键字参数city
具有默认值,调用时,可不传入city
参数:
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*
作为特殊分隔符。如果缺少*
,Python 解释器将无法识别位置参数和命名关键字参数:
def person(name, age, city, job):
# 缺少 *,city和job被视为位置参数
pass
````
### 不同类型参数的优先级
> **提示**:在 Python 中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这 5 种参数都可以组合使用。但是请注意,参数定义的顺序必须是:`必选参数`、`默认参数`、`可变参数`、`命名关键字参数`和`关键字参数`。
比如定义一个函数,包含上述若干种参数:
```python
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函数调用的时候,Python 解释器自动按照参数位置和参数名把对应的参数传进去。
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
// 这里是直接一个一个参数的传递
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
// c 用默认值,d 对应可变参数, ext 对应关键字参数
最神奇的是通过一个tuple
和dict
,你也可以调用上述函数:
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
// 直接用 list 或 tuple 作为参数
// 注意 args = (4,) 后面有一个逗号,(4) 对应一个数 4,而 (4, ) 才对应只有一个元素 4 的 tuple
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
// 这里的 args 和 kw 不是直接对应 * 和 **
// 而是要先满足必选参数的 1,2,3,之后再用 kw 的第一项满足可变参数
// 最后用 kw 的第二项满足关键字参数
所以,对于任意函数,都可以通过类似func(*args, **kw)
的形式调用它,无论它的参数是如何定义的8。
递归
教程上说用尾递归的方法可以避免栈溢出,但 Python 也没有提供针对尾递归的优化,考虑到平时用的很少,并且尾递归代码的写法比较麻烦9,所以干脆不要使用。
高级特性
切片
>>> L[0:3]
>>> L[:3] // 取前 3 个元素
>>> L[1:3] // 取 1, 2 号元素,0 号不取
>>> L[:10:2] // 前 10 个元素,每两个取 1 个
>>> L[::5] // 所有数,每5个取一个
>>> [:] // 复制
>>> L[-1] // 取倒数第一个元素
>>> L[-2:-1] // 取最后两个元素
>>> L[-10:] // 取最后 10 个元素
>>> 'ABCDEFG'[:3] // 字符串也可以视作一个 list
'ABC'
>>> 'ABCDEFG'[::2]
'ACEG'
迭代
- 通常用
for ... in
完成迭代; - 默认情况下,
dict
迭代的是key
。如果要迭代value
,可以用for value in d.values()
,如果要同时迭代key
和value
,可以用for k, v in d.items()
; - 字符串也是可迭代对象,因此,也可以作用于
for
循环; - 通过
collections
模块的Iterable
类型判断一个对象是可迭代对象;
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
- 可以同时引用了两个变量;
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
... print(x, y)
...
1 1
2 4
3 9
要对list
实现类似 Java 那样的下标循环,Python 内置的enumerate()
函数可以把一个list
变成索引-元素对
,这样就可以在for
循环中同时迭代索引和元素本身;
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
list 生成器
静态 list
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> [x * x for x in range(1, 11) if x % 2 == 0] // 用 if 后,只输出偶数的平方
[4, 16, 36, 64, 100]
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ'] // 使用两层循环,可以生成全排列
>>> import os // 导入 os 模块,模块的概念后面讲到
>>> [d for d in os.listdir('.')] // os.listdir 可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Desktop', 'Documents', 'Downloads', 'Library', 'VirtualBox VMs']
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' } // 使用两个变量来生成 list
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
>>> L = ['Hello', 'World', 'IBM', 'Apple'] // 把一个 list 中所有的字符串变成小写
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']
>>> L = ['Hello', 'World', 18, 'Apple', None] // 非字符串类型没有 lower() 方法,列表生成式会报错
>>> [s.lower() for s in L]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
AttributeError: 'int' object has no attribute 'lower'
>>> x = 'abc' // 用内建 isinstance 函数可判断变量是否为字符串
>>> y = 123
>>> isinstance(x, str)
True
>>> isinstance(y, str)
False
动态 list:generator
通过静态 list 生成式,我们可以直接创建一个list
。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含 100 万个元素的列表,会占用很大的存储空间,如果仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果list
中的元素可以按照某种算法推算出来,就不必创建完整的list
,从而节省大量的空间。在 Python 中,这种一边循环一边计算的机制,称为生成器:generator。
方法1(()
):要创建一个generator
,把一个列表生成式的[]
改成()
,就创建了一个generator
。
>>> L = [x * x for x in range(10)] // list
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10)) // generator
>>> g // 元素无法直接输出
<generator object <genexpr> at 0x1022ef630>
>>> next(g) // 使用 next() 才能依次输出元素
0
>>> next(g)
1
不断调用next(g)
并不可行,正确的方法是使用for
循环,因为generator
也是可迭代对象。
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
方法2(yield
):如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,可以用函数来实现。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
注意其中的a, b = b, a + b
相当于:
t = (b, a + b) # t 是一个 tuple
a = t[0]
b = t[1]
但不必显式写出临时变量t
就可以赋值,另外这里t
只是临时的,所以应该不涉及 tuple 不能修改的问题。可以看出,fib()
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator
。也就是说,上面的函数和generator
仅一步之遥。把fib()
函数变成generator
,只需要把print(b)
改为yield b
就可以了:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
简单来讲,如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator
。
警告:用
for
循环调用generator
时,得不到generator
的return
语句的返回值。如果想要得到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中。
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
迭代器
- Iterable:可以直接用
for
循环的对象统称为可迭代对象,list
、tuple
、dict
、set
、str
等都可以用for
遍历; - Iterator:可以被
next()
函数调用并不断返回下一个值的对象称为迭代器,方法 1 和方法 2 中用generator
生成的都可以用next()
操作; - 可以使用
isinstance()
判断一个对象是否是Iterable
对象或者Iterator
对象。 list
、dict
、str
虽然Iterable
,却不是Iterator
,把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
Python 的 Iterator 对象表示的是一个数据流,Iterator 对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列
,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以 Iterator 的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator 甚至可以表示一个无限大
的数据流,例如全体自然数
。而使用list
是永远不可能存储全体自然数的。
- 空
tuple
的定义在语法上要用myTuple(1,)
这样的方式,否则用myTuple(1)
则认为是 一个元素为1
的一维tuple
,具体可参考教程说明。 ↩ - 这地方稍有歧义,汉字是 3 个字节,但对应于一个字符,因此这种解释只适用于 ASCII 范围内的符号。 ↩
- 虽然
tuple
指向的元素不可改变,但如果其中一个元素是可以改变的list
,那么可以通过改变list
中元素的内容来达到修改tuple
内容的目的,具体参考使用list
和tuple
。 ↩ - 把一个
list
变成索引-元素对
,这样就可以在for
循环中同时迭代索引和元素本身。 ↩ - 将
iterable
转换成iterator
。 ↩ - 将一个惰性的
iterator
转换成一个确定的iterable
型的list
。 ↩ - 虽然将一个
dict
作为参数传递给函数也是可行的,但这样做在函数内部就需要再将dict
一个一个单独进行额外处理,而**
的方法可以省略这个步骤。 ↩ - 这种做法容易引起混淆,个人
。 ↩不太推荐
- 尾递归是指,在函数返回的时候,调用自身本身,并且,
return
语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。 ↩