Python learning
編碼
# -*- coding: utf-8 -*-
計算
Python 支持的數字類型有:int、float、Decimal(十進制)、Fraction(分數)、復數
>>> 8 / 5
1.6
>>> 17 // 5
3
>>> 5 * 2
10
>>> 5 ** 2
25
>>> 3.5 // 1.7
2.0
字符串
可以單雙引號,特殊字符使用反斜杠轉義。
特殊的一些:
- 如果不想讓反斜杠當做轉義輸出,可以用原始字符串,方法是在第一個引號前面加上一個
r
,r"C:\some\name"
- 字符串文本能夠分成多行。一種方法是使用三引號:
"""..."""
或者'''...'''
。行尾換行符會被自動包含到字符串中,但是可以在行尾加上\
來避免這個行為(行尾加上則不換行) - 字符串可以由
+
操作符連接(粘到一起),可以由*
表示重復,3 * 'un' + 'ium' == 'unununium'
- 字符串也可以被截取,索引從0記起,如
word[2]
;索引也可以是負數,這將導致從右邊開始計算,word[-1]
表示最后一個字符 - 字符串還支持切片,如
word[2:5]
表示獲取位置[2,5)的三個字符,如果word是Python,則獲取的字符串是tho,word[:5]
表示從最開始到第4個位置的字符串,切片也支持負數 - 索引過大會引發IndexError,但是切片過大不會報錯
- Python字符串不可以被更改 — 它們是 不可變的 (整體賦值是可以的)。因此,賦值給字符串索引的位置會導致錯誤。
word[1] = 'j'
是錯誤的!而word = 'aaa'
可以
相關鏈接:
列表
同樣支持索引和切片、所有的切片操作都會返回一個包含請求的元素的新列表。這意味著下面的切片操作返回列表一個新的(淺)拷貝副本
>>> sq1 = [1, 2, 3]
>>> sq2 = sq1
>>> sq3 = sq1[:]
>>> sq1
[1, 2, 3]
>>> sq2
[1, 2, 3]
>>> sq3
[1, 2, 3]
>>> sq2[1] = 10
>>> sq1
[1, 10, 3]
>>> sq2
[1, 10, 3]
>>> sq3[1] = 100
>>> sq1
[1, 10, 3]
>>> sq3
[1, 100, 3]
所以,我們知道,要想復制一個列表,需要使用[:]來獲取淺拷貝副本,而不能直接賦值。從上面的例子,也可以知道,列表支持對每個元素單獨修改
所以,還可以這樣:
>>> sq1
[1, 10, 3]
>>> sq1 + [1, 2]
[1, 10, 3, 1, 2]
>>> sq1
[1, 10, 3]
>>> sq1.append(10)
>>> sq1
[1, 10, 3, 10]
>>> sq1.extend([7, 6, 5])
>>> sq1
[1, 10, 3, 10, 7, 6, 5]
>>> # insert value
... sq1[1:1] = [40, 50]
>>> sq1
[1, 40, 50, 10, 3, 10, 7, 6, 5]
>>> # replace value
... sq1[1:3] = [60, 70]
>>> sq1
[1, 60, 70, 10, 3, 10, 7, 6, 5]
>>> # remove value
... sq1[1:3] = []
>>> sq1
[1, 10, 3, 10, 7, 6, 5]
一個例子
>>> # fibonacci
... a, b = 0, 1
>>> while b < 10:
... print(b)
... a, b = b, a+b
...
1
1
2
3
5
8
這里要注意的是:
注意a, b = 0, 1
和 a, b = b, a+b
,這是 多重賦值 ,變量在賦值前,右邊的表達式從左到右計算,然后再分別賦值給左邊的每個變量。所以,將第二個多重賦值改成b, a = a+b, b
也沒有問題
條件判斷:0、空序列(長度為0就算)為false,標準比較操作符與 C 相同: <
, >
, ==
, <=
, >=
和 !=
print()
函數可以用,
隔開變量輸出,并自動會在兩個變量之間加一個空格;print('aaa', end=',')
,end的默認值是\n
,改掉這個之后,print不換行,而是以,結尾
流程控制
if語句
if x < 0:
print(x)
elif x == 0:
print("hello")
else:
print("+")
for語句
words = ['cat', 'window', 'defenestrate']
for w in words:
print(w, len(w))
這里需要注意的是,Python 的 for 語句依據任意序列(鏈表或字符串)中的子項,按它們在序列中的順序來進行迭代。所以,在迭代過程中修改迭代序列不安全。如果你想要修改你迭代的序列(例如,復制選擇項),你可以迭代它的復本
for w in words[:]:
if len(w) > 6:
words.insert(0, w)
print(words)
# ['defenestrate', 'cat', 'window', 'defenestrate']
在循環(for, while)中同樣可以使用break
和 continue
,作用和 C 中相同
循環中,支持else
子句,作用是在循環正常結束(break不算)后執行
range()函數
用于生成一個等差級數鏈表(注意:這不是列表List,是迭代器),以下是例子:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
range(5, 10)
5 through 9
range(0, 10, 3)
0, 3, 6, 9
range(-10, -100, -30)
-10, -40, -70
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb
>>> print(range(10))
range(0, 10)
>>> list(range(5))
[0, 1, 2, 3, 4]
定義函數
關鍵字def引入一個函數定義,在其后必須跟有函數名和包括形式參數的圓括號
函數體的第一行語句可以是可選的字符串文本,這個字符串是函數的文檔字符串,或者稱為 docstring。有些工具通過 docstrings 自動生成在線的或可打印的文檔,或者讓用戶通過代碼交互瀏覽;在你的代碼中包含 docstrings 是一個好的實踐,讓它成為習慣吧
def func():
""" 這是一個docstring """
...
沒有return
語句的函數會返回None
在函數中引用全局的變量,可以用global語句聲明,如:global a
函數參數
默認參數值
def func(num, str="hello"):
if num in (1, 2, 3):
print(num)
重要警告:默認值只被賦值一次。這使得當默認值是可變對象時會有所不同,比如列表、字典或者大多數類的實例。例如,下面的函數在后續調用過程中會累積(前面)傳給它的參數!!
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
會輸出:
[1]
[1, 2]
[1, 2, 3]
取而代之的方法:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
關鍵字參數
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
# 接受一個必選參數 (voltage) 以及三個可選參數 (state, action, 和 type)。可以用以下的任一方法調用
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
可變參數列表
一個最不常用的選擇是可以讓函數調用可變個數的參數。這些參數被包裝進一個元組。在這些可變個數的參數之前,可以有零到多個普通的參數
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
通常,這些可變參數是參數列表中的最后一個(只針對于非關鍵字參數),因為它們將把所有的剩余輸入參數傳遞給函數。任何出現在*args
后的參數是關鍵字參數,這意味著,他們只能被用作關鍵字,而不是位置參數
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
可變關鍵字參數列表
相當于接收一個字典,必須在可變參數列表(如果有)的后面
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
keys = sorted(keywords.keys())
for kw in keys:
print(kw, ":", keywords[kw])
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
""" result:
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch
"""
在傳遞可變參數的時候可能遇到一種情況是,當要傳遞的參數已經是一個列表,而函數需要的是可變長參數,可以使用*
來拆開。同理,字典拆成關鍵字參數可以用**
>>> args = [3, 6]
>>> list(range(*args))
[3, 4, 5]
>>> list(range(3, 6))
[3, 4, 5]
Lambda形式
通過lambda
關鍵字,可以創建短小的匿名函數。這里有一個函數返回它的兩個參數的和: lambda a, b: a+b
lambda 形式可以從外部作用域引用變量:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
pass語句
pass 語句什么也不做。它用于那些語法上必須要有什么語句,但程序什么也不做的場合
class MyCls:
pass
def func():
pass
while True:
pass
各種數據結構
列表方法
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> print(a.count(333), a.count(66.25), a.count('x'))
2 1 0
>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.25, 333, -1, 333, 1, 1234.5, 333]
>>> a.index(333)
1
>>> a.remove(333)
>>> a
[66.25, -1, 333, 1, 1234.5, 333]
>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.25]
>>> a.sort()
>>> a
[-1, 1, 66.25, 333, 333, 1234.5]
>>> a.pop()
1234.5
>>> a
[-1, 1, 66.25, 333, 333]
可以看到,列表自帶pop和append方法,所以,可以直接把列表當做堆棧使用
>>> 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]
使用隊列:如果把列表當做隊列來使用,效率不高(頭部插入和彈出很慢)。可以使用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'])
列表推導式為從序列中創建列表提供了一個簡單的方法
常規的方法如下,副作用是x
變量在循環完畢后依然存在,而且長...
>>> squares = []
>>> for x in range(10):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
使用列表推導式:
>>> squares = [x ** 2 for x in range(10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
列表推導式由包含一個表達式的括號組成,表達式后面跟隨一個for
子句,之后可以有零或多個for
或if
子句。結果是一個列表,由表達式依據其后面的for
和if
子句上下文計算而來的結果構成
另一個例子:
>>> [(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)]
del語句
del語句可以刪除列表的對應的索引項 或 變量,與pop()方法的區別在于不返回值,并且可以刪除切片和整個變量
>>> 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 a
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
元組和序列
我們知道列表和字符串有很多通用的屬性,例如索引和切割操作。它們是序列類型中的兩種
元組也是一種標準序列類型,一個元組由數個逗號分隔的值組成:
>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
>>> # 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
([1, 2, 3], [3, 2, 1])
上面的例子說明,元組在輸出的時候總是有括號的,但是輸入的時候可以不用。元組元素就像字符串元素,不可變。但是,如果元組中包含可變元素,里面的元素是可變的(如:列表)
特殊的情況,創建零個或一個元素的元組。零個元素直接用()
,而一個元素的時候,需要額外加一個逗號,丑但有效1,
或 (1,)
>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)
集合
集合(set)是一個無序不重復元素的集,基本功能包括關系測試和消除重復元素。集合對象還支持 union(聯合),intersection(交),difference(差)和 sysmmetric 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 either a or b
{'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'}
字典
字典在其他語言中,可能被稱為“關聯數組”(associative arrays)。字典以關鍵字
為索引,關鍵字可以是任意不可變類型,通常用字符串或數值。如果元組中只包含字符串和數字,它可以做為關鍵字,如果它直接或間接的包含了可變對象,就不能當做關鍵字。不能用列表做關鍵字,因為列表可以用索引、切割或者append()
和extend()
等方法改變
字典的主要操作是依據鍵來存儲和析取值。也可以用del
來刪除鍵:值對(key:value
)。如果你用一個已經存在的關鍵字存儲值,以前為該關鍵字分配的值就會被遺忘。試圖從一個不存在的鍵中取值會導致錯誤
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
>>> list(tel.keys())
['irv', 'guido', 'jack']
>>> sorted(tel.keys())
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False
dict()
構造函數可以直接從key-value
對中創建字典。此外,字典推導式可以從任意的鍵值表達式中創建字典
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}
>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
循環技巧
循環字典,使用item()
方法:
>>> 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))
...
""" result:
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 f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear
若要在循環內部修改正在遍歷的序列(例如復制某些元素),建議您首先制作副本(!)
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]: # Loop over a slice copy of the entire list.
... if len(w) > 6:
... words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']
條件判斷
- 比較操作符
in
和not in
審核值是否在一個區間之內,1 not in [2, 3]
- 比較操作符
is
和is not
比較兩個對象是否相同,[] is ()
- 比較操作可以傳遞,
1 <= 3 > 2
,比較操作符具有相同的優先級 - 短路運算符
and
和or
,參數從左向右解析,一旦結果可以確定即停止,短路操作符的返回值通常是最后一個變量0 and 1
-
not
與and
和or
同屬于邏輯操作符,優先級not
>and
>or
- 總優先級:數值操作 > 比較操作 > 邏輯操作符
- 與
C
不同,Python
在表達式內不能賦值
序列對象可以與相同類型的其他對象作比較,比較操作按字典序進行:首先比較前兩個元素,如果不同,就決定了比較的結果;如果相同,就比較后兩個元素,依此類推,直到所有序列都完成比較。如果兩個元素本身就是同樣類 型的序列,就遞歸字典序比較。如果兩個序列的所有子項都相等,就認為序列相等。如果一個序列是另一個序列的初始子序列,較短的一個序列就小于另一個。字符串的字典序按照單字符的ASCII
順序
模塊
模塊是包括Python
定義和聲明的文件。文件名就是模塊名加上.py
后綴。模塊的模塊名(做為一個字符串)可以由全局變量__name__
得到
# 先在桌面新建一個test.py,定義一個hello()方法
>>> os.getcwd()
'/home/my/Desktop'
>>> import test
>>> test.hello()
hello world
>>> test.__name__
'test'
除了包含函數定義外,模塊也可以包含可執行語句。這些語句一般用來初始化模塊。他們僅在 第一次 被導入的地方執行一次
# 在test.py中加入一個輸出語句
>>> import test
import mod test successfully!
每個模塊都有自己私有的符號表,被模塊內所有的函數定義作為全局符號表使用。因此,模塊的作者可以在模塊內部使用全局變量,而無需擔心它與某個用戶的全局變量意外沖突
模塊可以導入其他的模塊。一個(好的)習慣是將所有的import
語句放在模塊的開始(或者是腳本),這并非強制。被導入的模塊名會放入當前模塊的全局符號表中
import
語句的一個變體直接從被導入的模塊中導入命名到本模塊的語義表中
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
作為腳本來執行模塊
只需要添加一個if判定:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
然后用python fibo.py 50
即可傳入參數并執行
模塊的搜索路徑
導入一個叫spam
的模塊時,解釋器先在當前目錄中搜索名為spam.py
的文件。如果沒有找到的話,接著會到sys.path
變量中給出的目錄列表中查找,變量的初始值如下:
- 輸入腳本的目錄(當前目錄)
- 環境變量PYTHONPATH表示的目錄列表中搜索
- Python默認安裝路徑中的搜索
>>> sys.path
['', '/usr/lib/python35.zip', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/usr/lib/python3.5/lib-dynload', '/usr/local/lib/python3.5/dist-packages', '/usr/lib/python3/dist-packages']
需要注意的是由于這些目錄中包含有搜索路徑中運行的腳本,所以這些腳本不應該和標準模塊重名,否則在導入模塊時Python
會嘗試把這些腳本當作模塊來加載。這通常會引發錯誤
dir()函數
內置函數dir()
用于按模塊名搜索模塊定義,它返回一個字符串類型的存儲列表
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
包
包通常是使用用“圓點模塊名”的結構化模塊命名空間。例如,名為 A.B 的模塊表示了名為A
的包中名為B
的子模塊。可以避免全局變量之間的相互沖突
包的結構可能是這樣的
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
當導入這個包時,Python
通過sys.path
搜索路徑查找包含這個包的子目錄
為了讓Python
將目錄當做內容包,目錄中必須包含__init__.py
文件。這是為了避免一個含有爛俗名字的目錄無意中隱藏了稍后在模塊搜索路徑中出現的有效模塊,比如string。最簡單的情況下,只需要一個空的__init__.py
文件即可。當然它也可以執行包的初始化代碼,或者定義稍后介紹的__all__
變量
導入模塊的方法:
# 1
import sound.effects.echo
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
# 2
from sound.effects import echo
echo.echofilter(input, output, delay=0.7, atten=4)
# 3
from sound.effects.echo import echofilter
echofilter(input, output, delay=0.7, atten=4)
from sound.effects import *
可能會很慢,或者出現一些意外(強烈不推薦的寫法)。但是這種情況難以避免,對于包的作者來說唯一的解決方案就是提供一個明確的包索引:執行from package import *
時,如果包中的__init__.py
代碼定義了一個名為__all__
的列表,就會按照列表中給出的模塊名進行導入(可以避免導入所有模塊)
輸入輸出
print()
函數對于每個參數,自動用空格分開輸出,以下是比較優雅的輸出方式(不是拼接的):
>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"
>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam
>>> print('This {food} is {adjective}.'.format(
... food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.
>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
other='Georg'))
The story of Bill, Manfred, and Georg.
>>> import math
>>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))
The value of PI is approximately 3.142.
可以看到,{}
用于指代格式化的內容,里面可以規定順序、關鍵字等
字段名后允許可選的':'
和格式指令。這允許對值的格式化加以更深入的控制
舊式的字符串格式化,使用%
>>> print("Name:%10s Age:%8d Height:%8.2f" % ("Aviad", 25, 1.83))
Name: Aviad Age: 25 Height: 1.83
文件讀寫
函數open()
返回文件對象,這個函數通常需要兩個字符串參數:文件名、打開模式
這里要特別注意的是,對于非文本文件,要在模式后面加上'b'
,否則會錯誤當做文本文件,修改一些平臺有關的行結束符字符(Python
打開文本,會默認會轉換成平臺相關的行結束符)
>>> f = open('openfile', 'w');
文件的對象方法:
f.read(size)
方法用于讀取文件內容,size是可選的數值,如果沒有指定size或者指定為負數,就會讀取并返回整個文件。當文件大小為當前機器內存兩倍時,就會產生問題。反之,會盡可能按比較大的size
讀取和返回數據。如果到了文件末尾,f.read()
會返回一個空字符串''
>>> f.read()
'This is the entire file.\n'
>>> f.read()
''
>>> f.read()
''
f.readline()
從文件中讀取單獨一行,字符串結尾會自動加上一個換行符\n
,只有當文件最后一行沒有以換行符結尾時,這一操作才會被忽略。這樣返回值就不會有混淆,如果f.readline()
返回一個空字符串,那就表示到達了文件末尾,如果是一個空行,就會描述為'\n'
,一個只包含換行符的字符串
>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''
高效的讀取方法
>>> for line in f:
... print(line, end='')
...
''' result:
This is the first line of the file.
Second line of the file
'''
將所有行讀到一個列表中
>>> f.seek(0)
>>> list(f)
['dfsafdsa\n', 'dfsafds\n', 'fdsagdfhfdh\n', 'hfgng\n', 'nh\n', 'trh\n', 'trh\n', 'tr\n', '\n', '\n', 'h\n', 'tr\n', 'htrhtr\n', 'h\n', 'tr\n', 'htr\n', 'h\n', 'tr\n', '\n', '\n', '\n', 'htrhtr\n']
>>> list(f)
[]
>>> f.seek(0)
>>> f.readlines()
['dfsafdsa\n', 'dfsafds\n', 'fdsagdfhfdh\n', 'hfgng\n', 'nh\n', 'trh\n', 'trh\n', 'tr\n', '\n', '\n', 'h\n', 'tr\n', 'htrhtr\n', 'h\n', 'tr\n', 'htr\n', 'h\n', 'tr\n', '\n', '\n', '\n', 'htrhtr\n']
f.write(string)
方法將string
的內容寫入文件(是覆蓋還是追加,視打開文件的模式而定),Python3.5.2
經測試沒有返回值。如果想寫入非字符串的內容,首先要轉換為字符串
>>> f.write('This is a test\n')
>>> value = ('the answer', 42)
>>> s = str(value)
>>> f.write(s)
文件對象的指針
f.tell()
返回一個整數,代表文件對象在文件中的指針位置,該數值計量了自文件開頭到指針處的比特數。需要改變文件對象指針話話,使用f.seek(offset,from_what)
。指針在該操作中從指定的引用位置移動offset
比特,引用位置由from_what
參數指定。from_what
值為0
表示自文件起始處開始,1
表示自當前文件指針位置開始,2
表示自文件末尾開始(配合的offset
是負數)。from_what
可以忽略,其默認值為0
>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5) # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'
當你使用完一個文件時,調用f.close()
方法就可以關閉它并釋放其占用的所有系統資源。在調用f.close()
方法后,試圖再次使用文件對象將會自動失敗
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file
>>> f
<closed file 'test.txt', mode 'a+' at 0x7f8cd23ca5d0>
使用with處理文件對象的好習慣
使用關鍵字with
的先進之處在于文件用完后會自動關閉,就算發生異常也沒關系。它是try-finally
塊的簡寫
>>> with open('test.txt', 'r') as f:
... read_data = f.read()
...
>>> read_data
'dfsafdsa\n'
>>> f.closed
True
以上的語句相當于,先調用了open()
函數,并將返回值賦給f
,然后里面的語句是try
塊里面的內容,finally
的內容隱藏在f
對象的__exit__
方法中,即f.close()
。這里需要注意的是,這里出現異常的話,仍然需要自己定義try-except
來處理。參考鏈接
使用json存儲格式化數據
需要使用
json
標準模塊,import json
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
['foo', {'bar': ['baz', None, 1.0, 2]}]
>>> x
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> f = open("test.txt", "w+")
>>> f.read()
''
>>> f.tell()
0
>>> json.dump(x, f)
>>> f.seek(0)
>>> f.read()
'"[\\"foo\\", {\\"bar\\": [\\"baz\\", null, 1.0, 2]}]"'
>>> f.seek(0)
>>> y = json.load(f)
>>> y
u'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> z = json.loads(y)
>>> z[1]["bar"][2]
1.0
錯誤和異常
語法錯誤
語法錯誤,也被稱作解析錯誤(最常見)
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
異常
即使一條語句或表達式在語法上是正確的,當試圖執行它時也可能會引發錯誤。運行期檢測到的錯誤稱為異常,并且程序不會無條件的崩潰:很快,你將學到如何在Python
程序中處理它們。然而,大多數異常都不會被程序處理,像這里展示的一樣最終會產生一個錯誤信息
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: int division or modulo by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: Can't convert 'int' object to str implicitly
錯誤信息的最后一行指出發生了什么錯誤。異常也有不同的類型,異常類型做為錯誤信息的一部分顯示出來
異常處理
try
語句按如下方式工作:
- 首先,執行
try
子句 (在try
和except
關鍵字之間的部分) - 如果沒有異常發生,
except
子句在try
語句執行完畢后就被忽略了 - 如果在
try
子句執行過程中發生了異常,那么該子句其余的部分就會被忽略。如果異常匹配于except
關鍵字后面指定的異常類型,就執行對應的except
子句。然后繼續執行try
語句之后的代碼 - 如果發生了一個異常,在
except
子句中沒有與之匹配的分支,它就會傳遞到上一級try
語句中。如果最終仍找不到對應的處理語句,它就成為一個未處理異常,終止程序運行,顯示提示信息
一個except
子句可以在括號中列出多個異常的名字去匹配相應的異常;如果省略括號,則表示全部匹配
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
try-except
語句可以帶有一個else
子句,該子句只能出現在所有except
子句之后。當try
語句沒有拋出異常時,需要執行一些代碼,可以使用這個子句
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
使用else
子句比在try
子句中附加代碼要好,因為這樣可以避免try-except
意外的截獲本來不屬于它們保護的那些代碼拋出的異常
所以,except
里面的代碼是錯誤的時候執行的;else
里面的代碼是正確的時候執行的;finally
里面的代碼是無論如何都會執行的(后面講到)
發生異常時,可能會有一個附屬值,作為異常的參數存在。這個參數是否存在、是什么類型,依賴于異常的類型。通常可以為except子句指定(as)一個變量,可以直接print出來看
異常處理器不僅僅處理那些在 try 子句中立刻發生的異常,也會處理那些 try 子句中調用的函數內部發生的異常
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError as err:
... print('Handling run-time error:', err)
...
Handling run-time error: int division or modulo by zero
raise
語句允許程序員強制拋出一個指定的異常(必須是一個異常實例或異常類)
如果你需要明確一個異常是否拋出,但不想處理它,raise
語句可以讓你很簡單的重新拋出該異常(一些監控工具的實現)
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
''' result:
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in ?
NameError: HiThere
'''
用戶可以自定義異常,異常類通常應該直接或間接從Exception
類派生,下面是一個例子,更多例子看這里
>>> class MyError(Exception):
... def __init__(self, value):
... self.value = value
... def __str__(self):
... return repr(self.value)
...
>>> try:
... raise MyError(2*2)
... except MyError as e:
... print('My exception occurred, value:', e.value)
...
''' result:
My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'
'''
前面說到的finally
語句,是無論什么情況都會執行的功能,通常放的是清理行為,如果遇到異常,執行完這段清理語句之后就結束了,下面是一個混合try-except-else-finally
的代碼塊
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")
...
>>> divide(2, 1)
result is 2
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
在真實場景的應用程序中,finally
子句用于釋放外部資源(文件 或網絡連接之類的),無論它們的使用過程中是否出錯
有些對象定義了標準的清理行為,無論對象操作是否成功,不再需要該對象的時候就會起作用。下面有兩段代碼:
# para 1
for line in open("myfile.txt"):
print(line)
# para 2
with open("myfile.txt") as f:
for line in f:
print(line)
區別在于,第一段代碼沒有關閉打開的文件,而with
語句可以使文件之類的對象確保總能及時準確地進行清理。在第二段語句執行后,文件對象總會被關閉,即使是在處理文件中的數據時出錯也一樣可以
類
關于作用域的示例(注意變量的查找順序),以下例子:
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
類定義語法
類定義就像函數定義,要先執行才能生效
class ClassName:
<statement-1>
.
.
.
<statement-N>
Python
的類對象支持屬性引用和實例化,如:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
屬性引用: MyClass.i
, MyClass.f
, MyClass.__doc__
實例化: x = MyClass()
構造函數的定義:
def __init__(self[, other params]):
pass
類方法的第一個參數一定是self
,在調用的時候省略傳入,代指實例本身
Python
的實例對象唯一可用的操作是屬性引用。和局部變量一樣,數據屬性不需要聲明,第一次使用時它們就會生成
在調用方法的時候,如:x.f()
事實上是調用了MyClass.f(x)
,這就是要在定義類方法的時候加上參數self
的原因
類和實例變量
一般來說,實例變量用于對每一個實例都是唯一的數據,類變量用于類的所有實例共享的屬性和方法
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
這樣會出現,可變對象作共享數據的問題
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
正確的做法是,將這個列表作為一個實例變量,即放入構造方法中賦值
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
類的繼承
派生類的定義例子:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
派生類定義的執行過程和基類是一樣的。構造派生類對象時,就記住了基類。這在解析屬性引用的時候尤其有用:如果在類中找不到請求調用的屬性,就搜索基類
如果基類是由別的類派生而來,這個規則會遞歸的應用上去。方法引用按如下規則解析:搜索對應的類屬性,必要時沿基類鏈逐級搜索,如果找到了函數對象這個方法引用就是合法的
派生類可能會覆蓋其基類的方法。因為方法調用同一個對象中的其它方法時沒有特權,基類的方法調用同一個基類的方法時,可能實際上最終調用了派生類中的覆蓋方法
派生類中調用基類方法,這時需要傳入self
參數了,BaseClassName.methodname(self, arguments)
判斷類或者實例之間的關系
- 函數
isinstance()
用于檢查實例類型:isinstance(obj, int)
只有在obj.__class__
是int
或其它從int
繼承的類型 - 函數
issubclass()
用于檢查類繼承:issubclass(bool, int)
為True
,因為bool
是int
的子類 - 然而,
issubclass(float, int)
為False
,因為float
不是int
的子類
Python
同樣有限的支持多繼承形式,例子如下:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
搜索順序:如果在DerivedClassName
(示例中的派生類)中沒有找到某個屬性,就會搜索Base1
,然后(遞歸的)搜索其基類,如果最終沒有找到,就搜索Base2
,以此類推
上面的搜索順序有一個問題,如果多個Base
擁有相同的基類,就會發生重復訪問基類的情況,這時,可以用super()
來動態改變解析順序
為了防止重復訪問基類,通過動態的線性化算法,每個類都按從左到右的順序特別指定了順序,每個祖先類只調用一次
私有變量
Python
中不存在只能從對象內部訪問的實例變量,一般規定私有變量以一個下劃線開頭命名,例如_spam
。此外,形如__spam
,以兩個下劃線開頭,后面至多只有一個下劃線的變量,會被替換為_classname__spam
>>> class Ms1:
... def _update(self):
... print(345)
... def __hello(self):
... print("hello world")
...
>>> m = Ms1()
>>> m._Ms1__hello()
hello world
>>> m.__hello()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Ms1' object has no attribute '__hello'
>>> m._Ms1__hello()
hello world
>>> m._update()
345
迭代器
大多數容器對象都可以用for
遍歷,如for elem in [1, 2, 3]
。在后臺,for
語句在容器對象中調用iter()
,該函數返回一個定義了__next__()
方法的迭代器對象,它在容器中逐一訪問元素,當沒有后續的元素時,__next__()
拋出一個StopIteration
異常來通知for
語句結束循環
可以使用內建的next()
函數來調用__next__()
方法
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
next(it)
StopIteration
根據之前所說的原理,可以很容易的為自己的類添加迭代器行為,以下例子:
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... print(char)
...
m
a
p
s
生成器
生成器是創建迭代器的簡單而強大的工具,它們寫起來就像是正規的函數,需要返回數據的時候使用yield
語句
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g
前面中描述了基于類的迭代器,它能做的每一件事生成器也能做到。因為自動創建了__iter__()
和__next__()
方法,生成器顯得如此簡潔。除了創建和保存程序狀態的自動方法,當發生器終結時,還會自動拋出StopIteration
異常。綜上所述,這些功能使得編寫一個正規函數成為創建迭代器的最簡單方法