5、數據結構
本章更詳細地描述了一些你已經學過的東西,并添加了一些新的東西。
5.1. 更多關于Lists
列表(list
)數據類型有更多的方法。下面是列表對象的所有方法:
list.append(x)
在列表末尾添加一項。相當于a[len(a):] = [x]
。
list.extend(iterable)
通過添加可迭代對象中的所有項來擴展列表。相當于a[len(a):] = iterable
。
list.insert(i, x)
在給定位置插入一個項目。第一個參數是要插入的元素的索引,因此a.insert(0, x)插入到列表的前面,a.insert(len(a), x)相當于a.append(x)。
list.remove(x)
從列表中移除值等于x的第一個元素。如果沒有這樣的元素,將引發ValueError。
list.pop([i])
刪除列表中給定位置的項,并返回它。如果沒有指定索引,a.pop()
刪除并返回列表中的最后一項。(方法簽名中i
周圍的方括號表示該參數是可選的,而不是您應該在該位置鍵入方括號。你會在Python庫參考中經常看到這個符號。)
list.clear()
從列表中刪除所有項目。相當于del a[:]
。
list.index(x[, start[, end]])
返回列表中第一個值等于x的項的從零開始的索引。如果沒有這樣的項則引發ValueError。
可選參數start
和end
被解釋為切片表示法,并用于將搜索限制為列表的特定子序列。返回的索引是相對于整個序列的開頭計算的,而不是相對于start參數計算的。
list.count(x)
返回x在列表中出現的次數。
list.sort(*, key=None, reverse=False)
對列表中的項進行排序(參數可用于自定義排序,請參閱sorted()了解其解釋)。
list.reverse()
將列表中的元素反轉。
list.copy()
返回列表的淺拷貝。相當于a[:]
。
一個使用了大多數列表方法的例子:
>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4) # Find next banana starting a position 4
6
>>> fruits.reverse()
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape')
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort()
>>> fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
>>> fruits.pop()
'pear'
你可能已經注意到,像insert,
remove
或sort
這樣只修改列表的方法不會打印返回值-它們返回默認值None
。這是Python中所有可變數據結構的設計原則。
您可能會注意到的另一件事是,并非所有數據都可以排序或比較。例如,[None, ‘hello’, 10]不排序,因為整數不能與字符串比較,None
不能與其他類型比較。此外,還有一些類型沒有定義排序關系。例如,3+4j < 5+7j不是一個有效的比較。
5.1.1 使用列表作為棧
列表方法可以很容易地將列表用作棧,其中添加的最后一個元素是檢索的第一個元素(“后進先出”)。要將一項添加到堆棧頂部,請使用append()
。要從堆棧頂部檢索項,請使用不帶顯式索引的pop()
。例如:
stack = [3, 4, 5]
stack.append(6)
stack.append(7)
stack
#[3, 4, 5, 6, 7]
stack.pop()
#7
stack
#[3, 4, 5, 6]
stack.pop()
#6
stack.pop()
#5
stack
#[3, 4]
5.1.2 使用列表作為隊列
也可以將列表用作隊列,其中添加的第一個元素是檢索到的第一個元素(“先入先出”);然而,對于這個目的,列表不是有效的。雖然從列表
末尾進行追加和彈出操作速度很快,但從列表
開頭進行插入或彈出操作速度很慢(因為所有其他元素都必須移動一位)。
要實現隊列,請使用collections.deque,它被設計為具有從兩端快速追加和彈出的功能。例如:
from collections import deque
queue = deque(["Eric", "John", "Michael"])
queue.append("Terry") # Terry arrives
queue.append("Graham") # Graham arrives
queue.popleft() # The first to arrive now leaves
# 'Eric'
queue.popleft() # The second to arrive now leaves
# 'John'
queue # Remaining queue in order of arrival
# deque(['Michael', 'Terry', 'Graham'])
5.1.3 列表推導
列表推導式提供了一種創建列表的簡明方法。常見的應用程序是創建新列表,其中每個元素是應用于另一個序列或可迭代對象的每個成員的某些操作的結果,或者創建滿足特定條件的那些元素的子序列。
例如,假設我們想創建一個正方形列表,如下所示:
squares = []
for x in range(10):squares.append(x**2)squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
注意,這會創建(或覆蓋)一個名為x的變量,該變量在循環完成后仍然存在。我們可以在沒有任何副作用的情況下使用:
squares = list(map(lambda x: x**2, range(10)))
或等價
squares = [x**2 for x in range(10)]
這樣更簡潔易讀。
列表推導式
由方括號組成,括號中包含一個表達式,后跟一個for
子句,然后是零個或多個for
或if
子句。結果將是在其后的for和if子句的上下文中對表達式求值所產生的新列表。例如,下面的listcomp將兩個不相等的列表中的元素組合起來:
[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
# [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
它等價于:
combs = []
for x in [1,2,3]:for y in [3,1,4]:if x != y:combs.append((x, y))combs# [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
注意,在這兩個代碼片段中,for
和if
語句的順序是相同的。
如果表達式是一個元組(例如前面示例中的(x, y)
),則必須將其括起來。
vec = [-4, -2, 0, 2, 4]
# create a new list with the values doubled
[x*2 for x in vec]# filter the list to exclude negative numbers
[x for x in vec if x >= 0]# apply a function to all the elements
[abs(x) for x in vec]# call a method on each element
freshfruit = [' banana', ' loganberry ', 'passion fruit ']
[weapon.strip() for weapon in freshfruit]# create a list of 2-tuples like (number, square)
[(x, x**2) for x in range(6)]# the tuple must be parenthesized, otherwise an error is raised
# 元組必須加括號,否則會引發錯誤
[x, x**2 for x in range(6)]# flatten a list using a listcomp with two 'for'
# 使用帶有兩個“for”的listcomp將列表平鋪
vec = [[1,2,3], [4,5,6], [7,8,9]]
[num for elem in vec for num in elem]
列表推導式可以包含復雜的表達式和嵌套函數:
from math import pi
[str(round(pi, i)) for i in range(1, 6)]# ['3.1', '3.14', '3.142', '3.1416', '3.14159']
5.1.4 嵌套列表推導式
列表推導式中的初始表達式可以是任意表達式,包括另一個列表推導式。
考慮下面的例子,一個3x4矩陣被實現為3個長度為4的列表:
matrix = [[1, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12],
]
下面的列表推導式將行和列進行轉置:
[[row[i] for row in matrix] for i in range(4)]# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
正如我們在上一節看到的,嵌套的listcomp是在它后面的for語句的上下文中求值的,所以這個例子等價于:
transposed = []
for i in range(4):transposed.append([row[i] for row in matrix])transposed
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
這就相當于:
transposed = []
for i in range(4):# the following 3 lines implement the nested listcomptransposed_row = []for row in matrix:transposed_row.append(row[i])transposed.append(transposed_row)transposed
在現實世界中,您應該更喜歡內置函數,而不是復雜的流語句。zip()函數將在這個用例中做得很好:
list(zip(*matrix))# [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
有關本行星號的詳細信息,請參見解包參數列表。
5.2. del 語句
有一種方法可以從給定索引而不是值的列表中刪除項
:del語句。這與返回值的pop()方法不同。del
語句還可以用于從列表中刪除切片或清除整個列表(我們之前通過將空列表賦值給切片來完成)。例如:
a = [-1, 1, 66.25, 333, 333, 1234.5]
del a[0]
a
# [1, 66.25, 333, 333, 1234.5]
del a[2:4]
a
# [1, 66.25, 1234.5]
del a[:]
a
# []
del也可以用來刪除整個變量:
del a
此后引用名稱a
是錯誤的(至少在為其分配另一個值之前)。稍后我們將找到del
的其他用途。
5.3 元組和序列
我們看到列表和字符串有許多共同的屬性,比如索引和切片操作。它們是序列數據類型的兩個例子(參見序列類型-list, tuple, range)。由于Python是一種不斷發展的語言,因此可能會添加其他序列數據類型。還有另一種標準序列數據類型:元組(tuple
)。
元組由若干以逗號分隔的值組成,例如:
t = 12345, 54321, 'hello!'
t[0]t# Tuples may be nested:
u = t, (1, 2, 3, 4, 5)
u# Tuples are immutable: 元組是不可變的
t[0] = 88888
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment# but they can contain mutable objects:
v = ([1, 2, 3], [3, 2, 1])
v
如你所見,在輸出時,元組總是用圓括號括起來
,以便正確解釋嵌套的元組;它們的輸入可以帶圓括號,也可以不帶圓括號,盡管圓括號通常是必需的(如果元組是較大表達式的一部分)。不可能對元組中的單個項進行賦值,但是可以創建包含可變對象的元組,例如列表。
雖然元組看起來類似于列表,但它們通常用于不同的情況和不同的目的。元組是不可變的
,通常包含一個異構的元素序列,這些元素可以通過解包(參見本節后面的部分)或索引(甚至在namedtuples的情況下通過屬性)來訪問。列表是可變的
,它們的元素通常是同構的,可以通過在列表上迭代來訪問。
一個特殊的問題是構造包含0項或1項的元組:語法有一些額外的怪癖來適應這些問題
。空元組由一對空括號構造
;只有一項的元組是通過在值后面加上逗號來構造的(將單個值括在括號中是不夠的)
。丑陋,但有效。例如:
>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)
語句t = 12345, 54321, 'hello!'
是元組打包(tuple packing
)的一個例子:值12345
,54321
和'hello!'
被打包在一個元組中。相反的操作也是可能的:
x, y, z = t
這被適當地稱為序列解包(sequence unpacking
),并適用于右側的任何序列。序列解包要求等號左側的變量個數與序列中的元素個數一樣多。注意,多重賦值實際上只是元組打包和序列解包的組合。
5.4 集合(Sets)
Python還包括set的數據類型。set
是沒有重復元素的無序集合。基本用途包括成員測試和消除重復條目。Set對象還支持數學運算,如并、交、差和對稱差(union, intersection, difference, and symmetric difference
)。
花括號或set()
函數可用于創建集合。注意:要創建一個空集合,必須使用set()
,而不是{}
;后者創建一個空字典,這是一種數據結構,我們將在下一節討論。
下面是一個簡短的演示:
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket) # show that duplicates have been removed
{'orange', 'banana', 'pear', 'apple'}
>>> 'orange' in basket # fast membership testing
True
>>> 'crabgrass' in basket
False>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
>>> a - b # letters in a but not in b
{'r', 'd', 'b'}
>>> a | b # letters in a or b or both
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b # letters in both a and b
{'a', 'c'}
>>> a ^ b # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}
與列表推導式類似,也支持集合推導式:
>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}
5.5 字典
Python內建的另一個有用的數據類型是字典
(dictionary,參見映射類型-字典)。字典在其他語言中有時被稱為“聯想記憶”或“關聯數組”。與序列不同的是,序列是通過一系列數字來索引的,字典是通過鍵來索引的,鍵可以是任何不可變類型;字符串和數字總是可以作為鍵
。如果元組只包含字符串、數字或元組,則可以用作鍵;如果元組直接或間接包含任何可變對象,則不能將其用作鍵。不能將列表用作鍵,因為可以使用索引賦值、切片賦值或append()
和extend()
等方法就地修改列表。
最好將字典視為一組鍵值對(key: value
),并且要求鍵是唯一的(在一個字典中)。一對大括號創建一個空字典:{}
。在大括號內放置逗號分隔的鍵:值對列表,將初始鍵值對添加到字典中;這也是字典在輸出時寫入的方式。
字典的主要操作是存儲帶有某個鍵的值,并提取給定鍵的值。也可以使用del
刪除鍵值對。如果使用已經在使用的鍵進行存儲,則會忘記與該鍵關聯的舊值。使用不存在的鍵提取值是錯誤的。
在字典上執行list(d)
將返回字典中使用的所有鍵的列表,按插入順序(如果您希望對其排序,只需使用sorted(d))
。使用關鍵字in
檢查字典中是否存在單個鍵。
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'jack': 4098, 'sape': 4139, 'guido': 4127}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'jack': 4098, 'guido': 4127, 'irv': 4127}
>>> list(tel)
['jack', 'guido', 'irv']
>>> sorted(tel)
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False
dict()
構造函數直接從鍵值對序列構建字典:
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'guido': 4127, 'jack': 4098}
此外,字典推導式可用于從任意鍵和值表達式創建字典:
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
當鍵是簡單字符串時,有時使用關鍵字參數更容易指定對:
>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'guido': 4127, 'jack': 4098}
5.6 循環技術
在遍歷字典時,可以使用items()
方法同時檢索鍵和相應的值。
>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
... print(k, v)
...
gallahad the pure
robin the brave
當在一個序列中循環時,可以使用 enumerate() 函數同時檢索位置索引和相應的值。
>>> for i, v in enumerate(['tic', 'tac', 'toe']):
... print(i, v)
...
0 tic
1 tac
2 toe
為了同時循環兩個或多個序列,條目可以與zip()
函數配對。
>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
... print('What is your {0}? It is {1}.'.format(q, a))
...
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.
在反向的過程中循環,首先在正向方向上指定序列,然后調用reversed()函數。
>>> for i in reversed(range(1, 10, 2)):
... print(i)
...
9
7
5
3
1
要以排序的方式循環一個序列,使用sorted() 函數返回一個新的排序列表,同時離開源未更改。
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for i in sorted(basket):
... print(i)
...
apple
apple
banana
orange
orange
pear
在序列上使用set()可以消除重復元素。在序列上結合使用sorted()
和set()
是按排序順序循環序列中唯一元素的慣用方法。
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear
當你在列表上循環的時候,有時改變列表是很誘人的;然而,創建一個新列表通常更簡單、更安全。
>>> import math
>>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
>>> filtered_data = []
>>> for value in raw_data:
... if not math.isnan(value):
... filtered_data.append(value)
...
>>> filtered_data
[56.2, 51.7, 55.3, 52.5, 47.8]
5.7 更多關于條件
while
和if
語句中使用的條件可以包含任何操作符,而不僅僅是比較。
比較操作符in
和not in
檢查一個值是否在序列中出現(不出現)。操作符is
和is not
比較兩個對象是否真的是同一個對象。所有比較操作符具有相同的優先級,優先級低于所有數值操作符
。
比較可以鏈式進行。例如,a < b == c
測試a
是否小于b
,并且b
是否等于c
。
可以使用布爾運算符and
和or
組合比較,并且可以用not
來否定比較(或任何其他布爾表達式)的結果。它們的優先級低于比較操作符;在它們之間,not具有最高優先級和or最低優先級
,因此A and not B or C
等價于(A and (not B)) or C
。一如既往,括號可以用來表示期望的組合。
布爾運算符and
和or
是所謂的短路運算符:它們的參數從左到右求值,一旦確定結果,求值就停止。例如,如果A和C為真,但B為假,則A and B and C
不對表達式C求值。當用作通用值而不是布爾值時,短路運算符的返回值是最后求值的參數。
可以將比較結果或其他布爾表達式賦值給變量。例如,
>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'
請注意,在Python中,與C語言不同,表達式內部的賦值必須使用海象操作符:=
顯式地完成。這避免了C程序中遇到的一類常見問題:在打算使用==
時在表達式中鍵入=
。
5.8 比較序列和其他類型
序列對象通常可以與具有相同序列類型的其他對象進行比較。比較使用字典(lexicographical
)順序:首先比較前兩個項,如果它們不同,則決定比較的結果;如果它們相等,則比較接下來的兩個項,依此類推,直到任意一個序列都用完。如果要比較的兩個項本身是相同類型的序列,則遞歸地進行字典比較。如果兩個序列的所有項比較相等,則認為這兩個序列相等。如果一個序列是另一個序列的初始子序列,則較短的序列是較小(較小)的序列。字符串的字典順序使用Unicode代碼點編號對單個字符進行排序。一些相同類型序列之間比較的例子:
(1, 2, 3) < (1, 2, 4)
[1, 2, 3] < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4) < (1, 2, 4)
(1, 2) < (1, 2, -1)
(1, 2, 3) == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)
注意,使用<
或>
比較不同類型的對象是合法的,前提是對象具有適當的比較方法。例如,混合數字類型根據其數值進行比較,因此0
等于0.0
,等等。否則,解釋器將引發TypeError異常,而不是提供任意排序。
6、模塊
如果您退出Python解釋器并再次進入它,您所做的定義(函數和變量)將丟失。因此,如果您想編寫較長的程序,最好使用文本編輯器為解釋器準備輸入,并將該文件作為輸入運行解釋器。這就是所謂的創建腳本(script
)。隨著程序變長,您可能希望將其分成幾個文件,以便于維護。您可能還希望使用在幾個程序中編寫的方便函數,而不將其定義復制到每個程序中。
為了支持這一點**,Python有一種方法可以將定義放在文件中,并在腳本或解釋器的交互式實例中使用它們**。這樣的文件稱為模塊(module
);模塊中的定義可以導入(imported
)到其他模塊或主(main
)模塊中(在頂層以計算器模式執行的腳本中可以訪問的變量集合)。
模塊是包含Python定義和語句的文件。文件名是附加了.py后綴的模塊名
。在模塊中,模塊的名稱(作為字符串)可以作為全局變量__name__
的值使用。例如,使用您最喜歡的文本編輯器在當前目錄中創建一個名為fibo.py
的文件,其內容如下:
# Fibonacci numbers moduledef fib(n): # write Fibonacci series up to na, b = 0, 1while a < n:print(a, end=' ')a, b = b, a+bprint()def fib2(n): # return Fibonacci series up to nresult = []a, b = 0, 1while a < n:result.append(a)a, b = b, a+breturn result
現在進入Python解釋器并用以下命令導入該模塊:
>>> import fibo
這不會將fibo中定義的函數名直接輸入到當前符號表中;它只在那里輸入模塊名fibo
。使用模塊名,你可以訪問以下函數:
>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'
如果你打算經常使用一個函數,你可以給它賦一個本地名稱:
>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1 更多關于模塊的信息
模塊可以包含可執行語句以及函數定義。這些語句用于初始化模塊。它們只在
import語句中第一次遇到模塊名時執行
。(如果文件作為腳本執行,它們也會運行。)
每個模塊都有自己的私有符號表,它被模塊中定義的所有函數用作全局符號表。因此,模塊的作者可以在模塊中使用全局變量,而不必擔心與用戶的全局變量發生意外沖突。另一方面,如果你知道你在做什么,你可以用與引用它的函數相同的符號modname.itemname
來觸摸模塊的全局變量。
模塊可以導入其他模塊。習慣上將所有import語句放在模塊(或腳本)的開頭,但這不是必需的。導入的模塊名稱放在導入模塊的全局符號表中。
import語句有一個變體,它將模塊中的名稱直接導入到導入模塊的符號表中。例如:
>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這并沒有在局部符號表中引入模塊名(因此在本例中,沒有定義fibo
)。
甚至還有一個變體可以導入模塊定義的所有名稱:
>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這將導入所有名稱,但以下劃線(_
)開頭的名稱除外。在大多數情況下,Python程序員不使用此功能
,因為它將一組未知的名稱引入解釋器,可能會隱藏一些您已經定義的內容。
請注意,通常不贊成從模塊或包中導入*
,因為它經常導致代碼可讀性差。但是,在交互式會話中使用它來節省打字是可以的。
如果模塊名后面跟as
,那么as
后面的名稱直接綁定到導入的模塊。
>>> import fibo as fib
>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這實際上是以導入fibo
相同的方式導入模塊,唯一的區別是它可以作為fib
使用。
它也可以在使用from時使用,具有類似的效果:
>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
注意:出于效率考慮,每個模塊在每個解釋器會話中只導入一次。因此,如果你改變了你的模塊,你必須重新啟動解釋器——或者,如果你只想交互測試一個模塊,使用
importlib.reload()
,例如import importlib; importlib.reload(modulename)
。
6.1.1 將模塊作為腳本執行
運行Python模塊時,
python fibo.py <arguments>
模塊中的代碼將被執行,就像您導入它一樣,但__name__
設置為"__main__"
。這意味著通過在模塊末尾添加這段代碼:
if __name__ == "__main__":import sysfib(int(sys.argv[1]))
你可以使該文件既可用作腳本,也可用作可導入的模塊,因為解析命令行的代碼只有在模塊作為“main
”文件執行時才會運行:
$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34
如果導入了模塊,則不會運行代碼:
>>> import fibo
>>>
這通常用于為模塊提供方便的用戶界面,或者用于測試目的(將模塊作為腳本運行以執行測試套件)。
6.1.2 模塊搜索路徑
當導入名為spam
的模塊時,解釋器首先搜索具有該名稱的內置模塊。這些模塊名稱列在sys.builtin_module_names中。如果沒有找到,它就在變量sys.path給出的目錄列表中搜索一個名為spam.py
的文件。sys.path
從這些位置初始化:
- 包含輸入腳本的目錄(如果沒有指定文件,則為當前目錄)。
- PYTHONPATH(一個目錄名列表,語法與shell變量
PATH
相同)。 - 依賴安裝的默認值(按照約定包括
site-packages
目錄,由site模塊處理)。
注意:在支持符號鏈接的文件系統上,包含輸入腳本的目錄將在符號鏈接之后計算。換句話說,
包含符號鏈接的目錄不會被添加到模塊搜索路徑中
。
初始化后,Python程序可以修改sys.path
。包含正在運行的腳本的目錄放在搜索路徑的開頭,在標準庫路徑之前。這意味著將加載該目錄中的腳本,而不是加載庫目錄中同名的模塊
。這是一個錯誤,除非替換是有意的。請參閱標準模塊小節了解更多信息
6.1.3 “編譯”的Python文件
為了加快加載模塊的速度,Python在__pycache__
目錄下以module.version.pyc
的名稱緩存每個模塊的編譯版本。其中version
編碼編譯文件的格式;它通常包含Python版本號。例如,在CPython 3.3版中,spam.py編譯后的版本會被緩存為__pycache__/spam.cpython-33.pyc
。這種命名約定允許來自不同發行版和不同版本的Python的編譯模塊共存。
Python根據編譯后的版本檢查源代碼的修改日期,以查看它是否過期并需要重新編譯。這是一個完全自動的過程。此外,編譯的模塊是平臺獨立的,因此相同的庫可以在具有不同體系結構的系統之間共享
。
Python在兩種情況下不檢查緩存
。首先,它總是重新編譯,并且不存儲直接從命令行加載的模塊的結果。其次,如果沒有源模塊,則不檢查緩存。要支持非源(僅編譯)發行版,編譯模塊必須位于源目錄中,并且不能有源模塊。
給專家的一些建議:
- 您可以使用Python命令上的-O或-OO開關來減小編譯模塊的大小。
-O
開關刪除assert語句,-OO
開關刪除assert語句和__doc__
字符串。由于一些程序可能依賴于這些可用的選項,所以只有在您知道自己在做什么的情況下才應該使用這個選項。“優化”模塊有一個opt-
標簽,通常更小。未來的版本可能會改變優化的效果。 - 從
.pyc
文件中讀取程序并不比從.py
文件中讀取程序運行得更快;.pyc
文件唯一更快的地方是加載它們的速度。 - compileall模塊可以為一個目錄中的所有模塊創建
.pyc
文件。 - PEP 3147中有關于這個過程的更多細節,包括決策流程圖。
6.2 標準模塊
Python附帶了一個標準模塊庫,在單獨的文檔Python庫參考(Python Library Reference,以下簡稱“庫參考”)中進行了描述。有些模塊內置于解釋器中;它們提供了對不屬于語言核心的操作的訪問,但這些操作是內建的,要么是為了提高效率,要么是為了提供對操作系統原語(如系統調用)的訪問。這些模塊的集合是一個配置選項,它也依賴于底層平臺。例如,winreg模塊僅在Windows系統上提供。有一個特別的模塊值得注意:sys,它內置于每個Python解釋器中。變量sys.ps1
和sys.ps2
定義了用于主提示符和輔助提示符的字符串:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>
只有在解釋器處于交互模式時才定義這兩個變量。
變量sys.path
是一個字符串列表,用于確定解釋器對模塊的搜索路徑。它被初始化為從環境變量PYTHONPATH獲取的默認路徑,如果未設置PYTHONPATH
,則從內置默認路徑初始化。你可以使用標準的列表操作來修改它:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
6.3 dir()函數
內置函數dir()用于查找模塊定義了哪些名稱。它返回一個有序的字符串列表:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__','__interactivehook__', '__loader__', '__name__', '__package__', '__spec__','__stderr__', '__stdin__', '__stdout__', '__unraisablehook__','_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework','_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook','api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix','breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing','callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info','excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info','float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth','getallocatedblocks', 'getdefaultencoding', 'getdlopenflags','getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile','getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval','gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info','intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value','maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks','path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix','set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags','setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr','stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info','warnoptions']
如果不帶參數,dir()
會列出你當前定義的名稱:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
請注意,它列出了所有類型的名稱:變量、模塊、函數等
。
dir()
不列出內置函數和變量的名稱。如果你想要它們的列表,它們在標準模塊builtins中定義:
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException','BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning','ChildProcessError', 'ConnectionAbortedError', 'ConnectionError','ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning','EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False','FileExistsError', 'FileNotFoundError', 'FloatingPointError','FutureWarning', 'GeneratorExit', 'IOError', 'ImportError','ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError','IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError','MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented','NotImplementedError', 'OSError', 'OverflowError','PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError','ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning','StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError','SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError','UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError','UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning','ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__','__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs','all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable','chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits','delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit','filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr','hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass','iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview','min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property','quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice','sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars','zip']
6.4 包 (Packages)
包
是通過使用“帶點的模塊名”來構建Python模塊命名空間的一種方式。例如,模塊名A.B
在名為A
的包中指定了一個名為B
的子模塊。就像使用模塊名可以讓不同模塊的作者不必擔心彼此的全局變量名一樣,使用點模塊名可以讓NumPy或Pillow等多模塊包的作者不必擔心彼此的模塊名。
假設您想要設計一個模塊集合(一個“包”)來統一處理聲音文件和聲音數據。有許多不同的聲音文件格式(通常通過它們的擴展名識別,例如:.wav
、.aiff
、.au
),因此您可能需要創建和維護一個不斷增長的模塊集合,以便在各種文件格式之間進行轉換。您可能還希望對聲音數據執行許多不同的操作(例如混音、添加回聲、應用均衡器函數、創建人工立體聲效果),因此您將編寫一個永無止境的模塊流來執行這些操作。這是你的包可能的結構(用分層文件系統表示):
sound/ Top-level package__init__.py Initialize the sound packageformats/ Subpackage for file format conversions__init__.pywavread.pywavwrite.pyaiffread.pyaiffwrite.pyauread.pyauwrite.py...effects/ Subpackage for sound effects__init__.pyecho.pysurround.pyreverse.py...filters/ Subpackage for filters__init__.pyequalizer.pyvocoder.pykaraoke.py...
導入包時,Python會在sys. path
上的目錄中搜索。查找包子目錄的路徑。
需要__init__.py
文件才能使Python將包含該文件的目錄視為包。這可以防止具有公共名稱(如string
)的目錄無意中隱藏稍后在模塊搜索路徑上出現的有效模塊。在最簡單的情況下,__init__.py
可以只是一個空文件,但它也可以執行包的初始化代碼或設置__all__
變量,稍后會介紹。
包的用戶可以從包中導入單個模塊,例如:
import sound.effects.echo
這將加載sound.effects.echo
子模塊。必須使用其全名來引用。
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
另一種導入子模塊的方法是:
from sound.effects import echo
這也會加載子模塊echo
,并使它在沒有包前綴的情況下可用,所以它可以這樣使用:
echo.echofilter(input, output, delay=0.7, atten=4)
另一種變體是直接導入所需的函數或變量:
from sound.effects.echo import echofilter
同樣,這會加載子模塊echo
,但這會使其函數echofilter()
直接可用:
echofilter(input, output, delay=0.7, atten=4)
請注意,當使用from package import item
時,item可以是包的子模塊(或子包),也可以是包中定義的其他名稱,如函數、類或變量。import
語句首先測試項是否在包中定義;如果不是,它就假定它是一個模塊并嘗試加載它。如果找不到,就會引發ImportError異常。
相反,當使用import item.subitem.subsubitem
。除最后一項外,每一項必須是一個包;最后一項可以是模塊或包,但不能是前一項中定義的類、函數或變量。
6.4.1 從包中導入*
現在,當用戶寫入from sound.effects import *
時會發生什么呢?理想情況下,我們希望它以某種方式進入文件系統,找到包中存在的子模塊,并導入它們。這可能會花費很長時間,并且導入子模塊可能會產生不必要的副作用,這種副作用只有在顯式導入子模塊時才會發生。
唯一的解決方案是包作者提供包的顯式索引。import語句使用以下約定:如果一個包的__init__.py
代碼定義了一個名為__all__
的列表,它被認為是當遇到from package import *
時應該導入的模塊名列表。當包的新版本發布時,由包的作者來保持這個列表是最新的。如果包的作者不認為從包中導入*有什么用,他們也可能決定不支持它。例如,文件sound/effects/__init__.pY
可能包含以下代碼:
__all__ = ["echo", "surround", "reverse"]
這意味著from sound.effects import *
將導入sound.effects
的三個命名子模塊。
如果未定義__all__
,則語句rom sound.effects import *
不會從包sound.effects
中導入所有子模塊到導入當前命名空間;它只保證sound.effects
已經被導入(可能在__init__.py
中運行任何初始化代碼),然后導入包中定義的任何名稱。這包括__init__.py
定義的任何名字(以及顯式加載的子模塊)。它還包括由前面的import
語句顯式加載的包的任何子模塊。考慮這段代碼:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
在本例中,echo
和surround
模塊被導入到當前名稱空間中,因為它們是在sound.effects
中定義的。當執行from...import
語句。(這在定義__all__
時也有效。)
盡管在使用import *
時,某些模塊被設計為只導出遵循特定模式的名稱,但在生產代碼中,這仍然被認為是不好的做法。
記住,使用from package import specific_submodule
并沒有錯!事實上,這是推薦的符號,除非導入模塊需要使用來自不同包的同名子模塊。
6.4.2 Intra-package引用
當包被構造成子包時(如示例中的sound
包),您可以使用絕對導入來引用兄弟包的子模塊。例如,如果模塊sound.filters.vocoder
需要在sound.effects
中使用echo
模塊。可以使用from sound.effects import echo
。
您還可以使用import語句的from module import name
形式編寫相對導入。這些導入使用前導點(leading dots
)來指示相對導入中涉及的當前包和父包。例如,在surround
模塊中,你可以使用:
from . import echo
from .. import formats
from ..filters import equalizer
注意,相對導入是基于當前模塊的名稱。由于主模塊的名稱始終為"__main__"
,因此打算用作Python應用程序主模塊的模塊必須始終使用絕對導入。
6.4.3 多個目錄下的包
包支持一個特殊的屬性,path在執行該文件中的代碼之前,它被初始化為一個包含保存包的__init__.py
目錄名稱的列表。這個變量可以修改;這樣做會影響將來對包中包含的模塊和子包的搜索。
雖然不經常需要這個特性,但它可以用于擴展包中的模塊集。