注:本篇博客是學習廖雪峰老師網站的摘抄,是為了方便以后的學習。
如有侵權,請聯系刪除!
聯系郵箱:
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
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
