注:本篇博客是学习廖雪峰老师网站的摘抄,是为了方便以后的学习。
如有侵权,请联系删除!
联系邮箱:
1103540209@qq.com
文章目录
- 1.切片
- 2.迭代
- 3.列表生成式
- 4.生成器
- 5.迭代器
- 小结
- 参考
掌握了Python的数据类型、语句和函数,基本上就可以编写出很多有用的程序了。比如构造一个
1, 3, 5, 7, ..., 99
的列表,可以通过循环实现:
L
=
[
]
n
=
1
while
n
<=
99
:
L
.
append
(
n
)
n
=
n
+
2
取list的前一半的元素,也可以通过循环实现。
但是在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。
基于这一思想,我们来介绍Python中非常有用的高级特性,1行代码能实现的功能,决不写5行代码。请始终牢记,代码越少,开发效率越高。
1.切片
取一个list或tuple的部分元素是非常常见的操作。比如,一个list如下:
L
=
[
'Michael'
,
'Sarah'
,
'Tracy'
,
'Bob'
,
'Jack'
]
取前3个元素,应该怎么做?
笨办法:
[
L
[
0
]
,
L
[
1
]
,
L
[
2
]
]
[
'Michael'
,
'Sarah'
,
'Tracy'
]
之所以是笨办法是因为扩展一下,取前N个元素就没辙了。
取前N个元素,也就是索引为
0-(N-1)
的元素,可以用循环:
r
=
[
]
n
=
3
for
i
in
range
(
n
)
:
r
.
append
(
L
[
i
]
)
r
[
'Michael'
,
'Sarah'
,
'Tracy'
]
对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。
对应上面的问题,取前3个元素,用一行代码就可以完成切片:
L
[
0
:
3
]
[
'Michael'
,
'Sarah'
,
'Tracy'
]
L[0:3]
表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
如果第一个索引是0,还可以省略:
L
[
:
3
]
[
'Michael'
,
'Sarah'
,
'Tracy'
]
也可以从索引1开始,取出2个元素出来:
L
[
1
:
3
]
[
'Sarah'
,
'Tracy'
]
类似的,既然Python支持
L[-1]
取倒数第一个元素,那么它同样支持倒数切片,试试:
L
[
-
2
:
]
[
'Bob'
,
'Jack'
]
L
[
-
2
:
-
1
]
[
'Bob'
]
记住倒数第一个元素的索引是-1。
切片操作十分有用。我们先创建一个0-99的数列:
L
=
list
(
range
(
100
)
)
可以通过切片轻松取出某一段数列。比如前10个数:
L
[
:
10
]
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
]
后10个数:
L
[
-
10
:
]
[
90
,
91
,
92
,
93
,
94
,
95
,
96
,
97
,
98
,
99
]
前11-20个数:
L
[
10
:
20
]
[
10
,
11
,
12
,
13
,
14
,
15
,
16
,
17
,
18
,
19
]
前10个数,每两个取一个:
L
[
:
10
:
2
]
[
0
,
2
,
4
,
6
,
8
]
所有数,每5个取一个:
L
[
:
:
5
]
[
0
,
5
,
10
,
15
,
20
,
25
,
30
,
35
,
40
,
45
,
50
,
55
,
60
,
65
,
70
,
75
,
80
,
85
,
90
,
95
]
甚至什么都不写,只写
[:]
就可以原样复制一个
list
:
L
[
:
]
[0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99]
tuple
也是一种
list
,唯一区别是
tuple
不可变。因此,
tuple
也可以用切片操作,只是操作的结果仍是
tuple
:
(
0
,
1
,
2
,
3
,
4
,
5
)
[
:
3
]
(
0
,
1
,
2
)
字符串
'xxx'
也可以看成是一种
list
,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
'ABCDEFG'
[
:
3
]
'ABC'
'ABCDEFG'
[
:
:
2
]
'ACEG'
在很多编程语言中,针对字符串提供了很多各种截取函数(例如,substring),其实目的就是对字符串切片。Python没有针对字符串的截取函数,只需要切片一个操作就可以完成,非常简单。
2.迭代
如果给定一个
lis
t或
tuple
,我们可以通过
for循环
来遍历这个
list
或
tuple
,这种遍历我们称为迭代
(Iteration)
。
在Python中,迭代是通过
for ... in
来完成的,而很多语言比如C语言,迭代
list
是通过下标完成的,比如Java代码:
for
(
i
=
0
;
i
<
list
.
length
;
i
++
)
{
n
=
list
[
i
]
;
}
可以看出,Python的
for循环
抽象程度要高于C的
for循环
,因为Python的
for循环
不仅可以用在
list
或
tuple
上,还可以作用在其他可迭代对象上。
list
这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是
可迭代对象
,无论有无下标,都可以迭代,比如
dict
就可以迭代:
d
=
{
'a'
:
1
,
'b'
:
2
,
'c'
:
3
}
for
key
in
d
:
print
(
key
)
a
b
c
因为
dict
的存储不是按照
list
的方式顺序排列,所以,迭代出的结果顺序很可能不一样。
默认情况下,
dict
迭代的是
key
。如果要迭代
value
,可以用
for value in d.values()
,如果要同时迭代
key
和
value
,可以用
for k, v in d.items()
。
由于字符串也是可迭代对象,因此,也可以作用于
for循环
:
for
ch
in
'ABC'
:
print
(
ch
)
A
B
C
所以,当我们使用
for循环
时,只要作用于一个可迭代对象,
for循环
就可以正常运行,而我们不太关心该对象究竟是
list
还是其他数据类型。
那么,如何判断一个对象是可迭代对象呢?方法是通过collections模块的
Iterable
类型判断:
from
collections
import
Iterable
isinstance
(
'abc'
,
Iterable
)
# str是否可迭代
True
isinstance
(
[
1
,
2
,
3
]
,
Iterable
)
# list是否可迭代
True
isinstance
(
123
,
Iterable
)
# 整数是否可迭代
False
最后一个小问题,如果要对
list
实现类似Java那样的下标循环怎么办?Python内置的
enumerate函数
可以把一个
list
变成
索引-元素对
,这样就可以在
for循环
中同时迭代索引和元素本身:
for
i
,
value
in
enumerate
(
[
'A'
,
'B'
,
'C'
]
)
:
print
(
i
,
value
)
0
A
1
B
2
C
上面的for循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:
for
x
,
y
in
[
(
1
,
1
)
,
(
2
,
4
)
,
(
3
,
9
)
]
:
print
(
x
,
y
)
1
1
2
4
3
9
3.列表生成式
列表生成式即
List Comprehensions
,是Python内置的非常简单却强大的可以用来创建
list
的生成式。
举个例子,要生成list
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
可以用
list(range(1, 11))
:
list
(
range
(
1
,
11
)
)
[
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
]
但如果要生成
[1x1, 2x2, 3x3, ..., 10x10]
怎么做?方法一是循环:
L
=
[
]
for
x
in
range
(
1
,
11
)
:
L
.
append
(
x
*
x
)
L
[
1
,
4
,
9
,
16
,
25
,
36
,
49
,
64
,
81
,
100
]
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的
list
:
[
x
*
x
for
x
in
range
(
1
,
11
)
]
[
1
,
4
,
9
,
16
,
25
,
36
,
49
,
64
,
81
,
100
]
写列表生成式时,把要生成的元素
x * x
放到前面,后面跟
for循环
,就可以把
list
创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
for循环
后面还可以加上
if判断
,这样我们就可以筛选出仅偶数的平方:
[
x
*
x
for
x
in
range
(
1
,
11
)
if
x
%
2
==
0
]
[
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可以列出文件和目录
[
'.ipynb_checkpoints'
,
'args 和 kwargs的用法.ipynb'
,
'data'
,
'pandas 表的合并 merge、join、contact.ipynb'
,
'Pandas合并连接merge.ipynb'
,
'pandas数据合并与重塑(pd.concat篇).ipynb'
,
'picture'
,
'Python3中的编码问题(Unicode, UTF-8, GBK, ASCII).ipynb'
,
'Python创建类、构造函数和析构函数、创建实例对象.ipynb'
,
'Python异常及处理方法总结.ipynb'
,
'python的定时器.ipynb'
,
'python的类.ipynb'
,
'tqdm介绍及常用方法.ipynb'
,
'Untitled.ipynb'
,
'urlencode与unquote.ipynb'
,
'玩转Python类的(私有)属性与方法的使用.ipynb'
,
'面向对象编程.ipynb'
]
for循环
其实可以同时使用两个甚至多个变量,比如
dict
的
items()
可以同时迭代
key
和
value
:
d
=
{
'x'
:
'A'
,
'y'
:
'B'
,
'z'
:
'C'
}
for
k
,
v
in
d
.
items
(
)
:
print
(
k
,
'='
,
v
)
x
=
A
y
=
B
z
=
C
因此,列表生成式也可以使用两个变量来生成
list
:
d
=
{
'x'
:
'A'
,
'y'
:
'B'
,
'z'
:
'C'
}
[
k
+
'='
+
v
for
k
,
v
in
d
.
items
(
)
]
[
'x=A'
,
'y=B'
,
'z=C'
]
最后把一个
list
中所有的字符串变成小写:
L
=
[
'Hello'
,
'World'
,
'IBM'
,
'Apple'
]
[
s
.
lower
(
)
for
s
in
L
]
[
'hello'
,
'world'
,
'ibm'
,
'apple'
]
4.生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,
如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的
list
,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:
generator
。
要创建一个
generator
,有很多种方法。第一种方法很简单,只要把一个列表生成式的
[]
改成
()
,就创建了一个
generator
:
L
=
[
x
*
x
for
x
in
range
(
10
)
]
L
[
0
,
1
,
4
,
9
,
16
,
25
,
36
,
49
,
64
,
81
]
g
=
(
x
*
x
for
x
in
range
(
10
)
)
g
<
generator
object
<
genexpr
>
at
0x00000187C250CF10
>
创建
L
和
g
的区别仅在于最外层的
[]
和
()
,L是一个
list
,而g是一个
generator
。
我们可以直接打印出
list
的每一个元素,但我们怎么打印出
generator
的每一个元素呢?
如果要一个一个打印出来,可以通过
next()
函数获得
generator
的下一个返回值:
next
(
g
)
0
next
(
g
)
1
next
(
g
)
9
next
(
g
)
16
next
(
g
)
25
next
(
g
)
36
next
(
g
)
49
next
(
g
)
64
next
(
g
)
81
next
(
g
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
StopIteration Traceback
(
most recent call last
)
<
ipython
-
input
-
61
-
e734f8aca5ac
>
in
<
module
>
-
-
-
-
>
1
next
(
g
)
StopIteration
:
我们讲过,
generator
保存的是算法,每次调用
next(g)
,就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出
StopIteration
的错误。
当然,上面这种不断调用
next(g)
实在是太变态了,正确的方法是使用
for循环
,因为
generator
也是可迭代对象:
g
=
(
x
*
x
for
x
in
range
(
10
)
)
for
n
in
g
:
print
(
n
)
0
1
4
9
16
25
36
49
64
81
所以,我们创建了一个
generator
后,基本上永远不会调用
next()
,而是通过
for循环
来迭代它,并且不需要关心
StopIteration
的错误。
generator
非常强大。如果推算的算法比较复杂,用类似列表生成式的
for循环
无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
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就可以赋值。
上面的函数可以输出斐波那契数列的前N个数:
fib
(
6
)
1
1
2
3
5
8
'done'
仔细观察,可以看出,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'
这就是定义
generator
的另一种方法。如果一个函数定义中包含
yield
关键字,那么这个函数就不再是一个普通函数,而是一个
generator
:
f
=
fib
(
6
)
f
<
generator
object
fib at
0x00000187C25B8830
>
这里,最难理解的就是
generator
和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成
generator
的函数,在每次调用
next()
的时候执行,遇到
yield
语句返回,再次执行时从上次返回的
yield
语句处继续执行。
举个简单的例子,定义一个
generator
,依次返回数字1,3,5:
def
odd
(
)
:
print
(
'step 1'
)
yield
1
print
(
'step 2'
)
yield
(
3
)
print
(
'step 3'
)
yield
(
5
)
调用该
generator
时,首先要生成一个
generator
对象,然后用
next()
函数不断获得下一个返回值:
o
=
odd
(
)
next
(
o
)
step
1
1
next
(
o
)
step
2
3
next
(
o
)
step
3
5
next
(
o
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
StopIteration Traceback
(
most recent call last
)
<
ipython
-
input
-
75
-
ac94be31f4f2
>
in
<
module
>
-
-
-
-
>
1
next
(
o
)
StopIteration
:
可以看到,
odd
不是普通函数,而是
generator
,在执行过程中,遇到
yield
就中断,下次又继续执行。执行3次
yield
后,已经没有
yield
可以执行了,所以,第4次调用
next(o)
就报错。
回到fib的例子,我们在循环过程中不断调用
yield
,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成
generator
后,我们基本上从来不会用
next()
来获取下一个返回值,而是直接使用
for循环
来迭代:
for
n
in
fib
(
6
)
:
print
(
n
)
1
1
2
3
5
8
但是用
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
5.迭代器
我们已经知道,可以直接作用于
for循环
的数据类型有以下几种:
一类是集合数据类型,如
list、tuple、dict、set、str
等;
一类是
generator
,包括
生成器
和
带yield的generator function
。
这些可以直接作用于
for循环
的对象统称为可迭代对象:
Iterable
。
可以使用
isinstance()
判断一个对象是否是
Iterable对象
:
from
collections
import
Iterable
# 判断列表是不是可迭代对象
isinstance
(
[
]
,
Iterable
)
True
# 判断字典是不是可迭代对象
isinstance
(
{
}
,
Iterable
)
True
# 判断字符串是不是可迭代对象
isinstance
(
'abc'
,
Iterable
)
True
# 判断一个生成器是不是可迭代对象
isinstance
(
(
x
for
x
in
range
(
10
)
)
,
Iterable
)
True
#判断一个数是不是可迭代对象
isinstance
(
100
,
Iterable
)
False
而生成器不但可以作用于
for循环
,还可以被
next()函数
不断调用并返回下一个值,直到最后抛出
StopIteration
错误表示无法继续返回下一个值了。
可以被
next()函数
调用并不断返回下一个值的对象称为迭代器:
Iterator
。
可以使用
isinstance()
判断一个对象是否是
Iterator
对象:
# 判断一个生成器是不是迭代器
isinstance
(
(
x
for
x
in
range
(
10
)
)
,
Iterator
)
True
# 判断一个列表是不是迭代器
isinstance
(
[
]
,
Iterator
)
False
# 判断一个字典是不是迭代器
isinstance
(
{
}
,
Iterator
)
False
# 判断一个字符串是不是迭代器
isinstance
(
'abc'
,
Iterator
)
False
生成器都是
Iterator
对象,但
list、dict、str
虽然是
Iterable
,却不是
Iterator
。
把
list、dict、str
等
Iterable
变成
Iterator
可以使用
iter()
函数:
isinstance
(
iter
(
[
]
)
,
Iterator
)
True
isinstance
(
iter
(
'abc'
)
,
Iterator
)
True
你可能会问,为什么
list、dict、str
等数据类型不是
Iterator
?
这是因为Python的
Iterator
对象表示的是一个数据流,
Iterator
对象可以被
next()
函数调用并不断返回下一个数据,直到没有数据时抛出
StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过
next()
函数实现按需计算下一个数据,所以
Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用
list
是永远不可能存储全体自然数的。
小结
凡是可作用于
for循环
的对象都是
Iterable
类型;
凡是可作用于
next()
函数的对象都是
Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如
list、dict、str
等是
Iterable
但不是
Iterator
,不过可以通过
iter()
函数获得一个
Iterator
对象。
Python的
for循环
本质上就是通过不断调用
next()
函数实现的,例如:
for
x
in
[
1
,
2
,
3
,
4
,
5
]
:
pass
实际上完全等价于:
# 首先获得Iterator对象:
it
=
iter
(
[
1
,
2
,
3
,
4
,
5
]
)
# 循环:
while
True
:
try
:
# 获得下一个值:
x
=
next
(
it
)
except
StopIteration
:
# 遇到StopIteration就退出循环
break
参考
https://www.liaoxuefeng.com/wiki/1016959663602400/1017323698112640