python筆記-2.流程控制語句工具
1 if語句
可能最為人所熟知的程式設計語句就是 if 語句了。例如:
>>> x = int(input("Please enter an integer: ")) Please enter an integer: 42 >>> if x < 0: ... x = 0 ... print('Negative changed to zero') ... elif x == 0: ... print('Zero') ... elif x == 1: ... print('Single') ... else: ... print('More')
可以有零個或多個 elif
部分,以及一個可選的 else 部分。關鍵字 'elif'
是'else if'
的縮寫,適合用於避免過多的縮排。一個 if ... elif ... elif ...
序列可以看作是其他語言中的 switch
或 case
語句的替代。
2 for語句
Python 中的 for 語句與你在 C 或 Pascal 中所用到的有所不同。
Python 中的 for 語句並不總是對算術遞增的數值進行迭代,或是給予使用者定義迭代步驟和暫停條件的能力(如同 C),而是對任意序列進行迭代(例如列表或字串),條目的迭代順序與它們在序列中出現的順序一致,例如:
>>> # Measure some strings: ... words = ['cat', 'window', 'defenestrate'] >>> for w in words: ... print(w, len(w)) ... cat 3 window 6 defenestrate 12
在遍歷同一個集合時修改該集合的程式碼可能很難獲得正確的結果。通常,更直接的做法是迴圈遍歷該集合的副本或建立新集合:
# Strategy: Iterate over a copy for user, status in users.copy().items(): if status == 'inactive': del users[user] # Strategy: Create a new collection active_users = {} for user, status in users.items(): if status == 'active': active_users[user] = status
3 range()函式
如果你確實需要遍歷一個數字序列,內建函式 range() 會派上用場。它生成算術級數:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
給定的終止數值並不在要生成的序列裡;range(10) 會生成 10 個值,並且是以合法的索引生成一個長度為 10 的序列。range 也可以以另一個數字開頭,或者以合定的幅度增加(甚至是負數;有時這也被叫做 '步進')
range(5, 10)
5, 6, 7, 8, 9
range(0, 10, 3)
0, 3, 6, 9
range(-10, -100, -30)
-10, -40, -70
要以序列的索引來迭代,您可以將range()
和 len()
組合如下:
>>> 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
如果你只打印 range,會出現奇怪的結果:
>>> print(range(10))
range(0, 10)
range() 所返回的物件在許多方面表現得像一個列表,但實際上卻並不是。此物件會在你迭代它時基於所希望的序列返回連續的項,但它沒有真正生成列表,這樣就能節省空間。
我們稱這樣物件為 iterable
,也就是說,適合作為這樣的目標物件:函式和結構期望從中獲取連續的項直到所提供的項全部耗盡。我們已經看到 for 和結就是這樣一種結構,而接受可迭代物件的函式的一個例子是 sum()
:
>>> sum(range(4)) # 0 + 1 + 2 + 3
6
4 break,continue,和迴圈中的else
break 語句,和 C 中的類似,用於跳出最近的 for 或 while 迴圈.
迴圈語句可能帶有 else 子句;它會在迴圈耗盡了可迭代物件 (使用 for) 或迴圈條件變為假值 (使用 while) 時被執行,但不會在迴圈被 break 語句終止時被執行。以下搜尋素數的迴圈就是這樣的一個例子:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
(是的,這是正確的程式碼。仔細看: else 子句屬於 for 迴圈, **不屬於 ** if 語句。)
當和迴圈一起使用時,else 子句與 try 語句中的 else 子句的共同點多於 if 語句中的同類子句: try 語句中的 else 子句會在未發生異常時多於,而迴圈中的 else 子句則會在未發生 break 時執行。
continue 語句也是借鑑自 C 語言,表示繼續迴圈中的下一次迭代:
>>> for num in range(2, 10):
... if num % 2 == 0:
... print("Found an even number", num)
... continue
... print("Found a number", num)
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9
5 pass語句
pass 語句什麼也不做。當語法上需要一個語句,但程式需要什麼動作也不做時,可以使用它。例如:
>>> while True:
... pass # Busy-wait for keyboard interrupt (Ctrl+C)
...
這通常用於建立最小的類:
>>> class MyEmptyClass:
... pass
...
pass 的另一個可以使用的場合是在你編寫新的程式碼時作為一個函式或條件子句體的佔位符,允許你保持在更抽象的層次上進行思考。 pass 會被靜默地子句:
>>> def initlog(*args):
... pass # Remember to implement this!
...
pass 的另一個可以使用的場合是在你編寫新的程式碼時作為一個函式或條件子句體的佔位符,允許你保持在更抽象的層次上進行思考。 pass 會被靜默地子句:
>>> def initlog(*args):
... pass # Remember to implement this!
...
6 定義函式
關鍵字 def 引入一個函式 定義。它必須後跟函式名稱和帶括號的形式引數列表。構成函式體的語句從下一行開始,並且必須縮排。
我們可以建立一個輸出任意範圍內 Fibonacci 數列的函式:
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Now call the function we just defined:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
函式體的第一個語句可以(可選的)是字串文字;這個字串文字是函式的文件字串或 docstring 。
函式的 執行 會引入一個用於函式區域性變數的新符號表。更確切地說,函式中所有的變數賦值都將儲存在區域性符號表中;而變數引用會首先在區域性符號表數中找,然後是外層函式的區域性符號表,再然後是全域性符號表,最後是內建名稱數中號表。因此,全域性變數和外層函式的變數不能在函式內部直接賦值(除非數中 global 語句中定義的全域性變數,或者是在 nonlocal 語句中定義的外數中數的變數),儘管它們可以被引用。
在函式被呼叫時,實際引數(實參)會被引入被呼叫函式的本地符號表中;因此,實參是通過 按值呼叫 傳遞的(其中 值 始終是物件 引用 而不是物件的值)。當一個函式呼叫另外一個函式時,將會為該呼叫建立一個新的本象的號表。
預設值是在 定義過程 中在函式定義處計算的,所以
i = 5
def f(arg=i):
print(arg)
i = 6
f()
會列印 5。
7 關鍵字引數
可以使用形如 kwarg=value 的 關鍵字引數 來呼叫函式。例如下面的函式:
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
但下面的函式呼叫都是無效的:
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
當存在一個形式為 **name 的最後一個形參時,它會接收一個字典 (參見對映型別 --- dict),其中包含除了與已有形參相對應的關鍵字引數以外的所有對映字引數。這可以與一個形式為 *name,接收一個包含除了已有形參列表對映的位置引數的元組的形參組合使用 (*name 必須對映在 **name 之前。) 例如,如果我們這樣定義一個函式:
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)
for kw in keywords:
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")
當然它會列印:
-- 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.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
注意列印時關鍵字引數的順序保證與呼叫函式時提供它們的順序是相匹配的。
8 特殊引數
預設情況下,函式的引數傳遞形式可以是位置引數或是顯式的關鍵字引數。為了確保可讀性和執行效率,限制允許的引數傳遞形式是有意義的,這樣開發者為了檢視函式定義即可確定引數項是僅按位置、按位置也按關鍵字,還是僅按關為了傳遞。
函式的定義看起來可以像是這樣:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
|- | Positional or keyword |
| - Keyword only
-- Positional only
在這裡 / 和 * 是可選的。如果使用這些符號則表明可以通過何種形參將引數值傳遞給函式:僅限位置、位置或關鍵字,以及僅限關鍵字。關鍵字形將參被稱為命名形參。
如果函式定義中未使用 / 和 *,則引數可以按位置或按關鍵字傳遞給函式。
特定形參可以被標記為 僅限位置。
如果是 僅限位置 的形參,則其位置是重要的,並且該形參不能作為關鍵字傳入。
僅僅限置形參要放在 / (正斜槓 ) 之前。這個 / 被用來從邏輯上分隔僅限位僅限參和其它形參。
如果函式定義中沒有 /,則表示沒有僅限位置形參。
在 / 之後的形參可以為 位置或關鍵字 或 僅限關鍵字。
要將形參標記為 僅限關鍵字,即指明該形參必須以關鍵字引數的形式傳入,應在引數列表的第一個 僅限關鍵字 形參之前放置一個 *。
請考慮以下示例函式定義並特別注意 / 和 * 標記:
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
第一個函式定義 standard_arg
是最常見的形式,對呼叫方式沒有任何限制,引數可以按位置也可以按關鍵字傳入:
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
第二個函式pos_only_arg
在函式定義中帶有 /,限制僅使用位置形參。:
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
第三個函式kwd_only_args
在函式定義中通過 * 指明僅允許關鍵字引數:
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
而最後一個則在同一函式定義中使用了全部三種呼叫方式:
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'
最後,請考慮這個函式定義,它的位置引數 name 和 **kwds 之間由於存在關鍵字名稱 name 而可能產生潛在衝突:
def foo(name, **kwds):
return 'name' in kwds
任何呼叫都不可能讓它返回 True,因為關鍵字 'name' 將總是繫結到第一個形參。例如:
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
但使用 / (僅限位置引數 ) 就可能做到,因為它允許 name 作為位置引數,也允許 'name' 作為關鍵字引數的關鍵字名稱:
def foo(name, /, **kwds):
return 'name' in kwds
>>> foo(1, **{'name': 2})
True
換句話說,僅限位置形參的名稱可以在 **kwds
中使用而不產生歧義。
9 解包引數列表
當引數已經在列表或元組中但要為需要單獨位置引數的函式呼叫解包時,會發生相反的情況。例如,內建的 range() 函式需要單獨的 start 和 stop 生相。如果它們不能單獨使用,可以使用 *-操作符來編寫函式呼叫以便從列生相元組中解包引數:
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]
同樣的方式,字典可使用 ** 操作符來提供關鍵字引數:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
10 Lambda表示式
可以用 lambda 關鍵字來建立一個小的匿名函式。這個函式返回兩個引數的和: lambda a, b: a+b 。Lambda 函式可以在需要函式物件的任何地方使用。它們在語法上限於單個表示式。從語義上來說,它們只是正常函式定義的語法糖。與巢狀函式定義一樣,lambda 函式可以引用所包含域的變數:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
上面的例子使用一個 lambda 表示式來返回一個函式。
另一個用法是傳遞一個小函式作為引數:
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
11 文件字串
以下是有關文件字串的內容和格式的一些約定。
第一行應該是物件目的的簡要概述。為簡潔起見,它不應顯式宣告物件的名稱或型別,因為這些可通過其他方式獲得(除非名稱恰好是描述函式操作的動詞)。這一行應以大寫字母開頭,以句點結尾。
如果文件字串中有更多行,則第二行應為空白,從而在視覺上將摘要與其餘描述分開。後面幾行應該是一個或多個段落,描述物件的呼叫約定,它的副作用等。
Python 解析器不會從 Python 中刪除多行字串文字的縮排,因此處理文件的工具必須在需要時刪除縮排。這是使用以下約定完成的。文件字串第一行 * 之後* 的第一個非空行確定整個文件字串的縮排量。(我們不能使用第一行,因為它通常與字串的開頭引號相鄰,因此它的縮排在字串文字中不明顯。)然後從字串的所有行的開頭剝離與該縮排 "等效" 的空格。縮排更少的行不應該出現,但是如果它們出現,則應該剝離它們的所有前導空格。應在轉化不應符為空格後測試空格的等效性(通常轉化為 8 個空格)。
下面是一個多行文件字串的例子:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.