天天看點

4. 深入 Python 流程控制

除了前面介紹的 while 語句,Python 還從其它語言借鑒了一些流程控制功能,并有所改變。

4.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') ... More       

可能會有零到多個 elif 部分,else 是可選的。關鍵字 ‘elif’ 是 ’else if’ 的縮寫,這個可以有效地避免過深的縮進。if … elif … elif … 序列用于替代其它語言中的 

switch

 或 

case

 語句。

4.2. for 語句

Python 中的 for 語句和 C 或 Pascal 中的略有不同。通常的循環可能會依據一個等差數值步進過程(如 Pascal),或由使用者來定義疊代步驟和中止條件(如 C ),Python 的 for 語句依據任意序列(連結清單或字元串)中的子項,按它們在序列中的順序來進行疊代。例如(沒有暗指):

>>> # Measure some strings:
... words = ['cat', 'window', 'defenestrate'] >>> for w in words: ... print(w, len(w)) ... cat 3 window 6 defenestrate 12       

在疊代過程中修改疊代序列不安全(隻有在使用連結清單這樣的可變序列時才會有這樣的情況)。如果你想要修改你疊代的序列(例如,複制選擇項),你可以疊代它的複本。使用切割辨別就可以很友善的做到這一點:

>>> 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']       

4.3. range() 函數

如果你需要一個數值序列,内置函數 range() 會很友善,它生成一個等差級數連結清單:

>>> for i in range(5): ... print(i) ... 0 1 2 3 4       

range(10)

 生成了一個包含 10 個值的連結清單,它用連結清單的索引值填充了這個長度為 10 的清單,所生成的連結清單中不包括範圍中的結束值。也可以讓 range() 操作從另一個數值開始,或者可以指定一個不同的步進值(甚至是負數,有時這也被稱為 “步長”):

range(5, 10) 5 through 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       

不過,這種場合可以友善的使用 enumerate(),請參見 循環技巧。

如果你隻是列印一個序列的話會發生奇怪的事情:

>>> print(range(10)) range(0, 10)       

在不同方面 range() 函數傳回的對象表現為它是一個清單,但事實上它并不是。當你疊代它時,它是一個能夠像期望的序列傳回連續項的對象;但為了節省空間,它并不真正構造清單。

我們稱此類對象是 可疊代的,即适合作為那些期望從某些東西中獲得連續項直到結束的函數或結構的一個目标(參數)。我們已經見過的 for 語句就是這樣一個疊代器。list() 函數是另外一個( 疊代器 ),它從可疊代(對象)中建立清單:

>>> list(range(5)) [0, 1, 2, 3, 4]       

稍後我們會看到更多傳回可疊代(對象)和以可疊代(對象)作為參數的函數。

4.4. break 和 continue 語句, 以及循環中的 else 子句

break 語句和 C 中的類似,用于跳出最近的一級 for 或 while 循環。

循環可以有一個 

else

 子句;它在循環疊代完整個清單(對于 for )或執行條件為 false (對于 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       

(Yes, 這是正确的代碼。看仔細:

else

 語句是屬于 for 循環之中, 不是 if 語句。)

與循環一起使用時,

else

 子句與 try 語句的 

else

 子句比與 if 語句的具有更多的共同點:try 語句的 

else

 子句在未出現異常時運作,循環的 

else

 子句在未出現 

break

 時運作。更多關于 try 語句和異常的内容,請參見 異常處理。

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       

4.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! ...       

4.6. 定義函數

我們可以建立一個用來生成指定邊界的斐波那契數列的函數:

>>> 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       

關鍵字 def 引入了一個函數 定義。在其後必須跟有函數名和包括形式參數的圓括号。函數體語句從下一行開始,必須是縮進的。

函數體的第一行語句可以是可選的字元串文本,這個字元串是函數的文檔字元串,或者稱為 docstring。(更多關于 docstrings 的資訊請參考 文檔字元串) 有些工具通過 docstrings 自動生成線上的或可列印的文檔,或者讓使用者通過代碼互動浏覽;在你的代碼中包含 docstrings 是一個好的實踐,讓它成為習慣吧。

函數 調用 會為函數局部變量生成一個新的符号表。确切的說,所有函數中的變量指派都是将值存儲在局部符号表。變量引用首先在局部符号表中查找,然後是包含函數的局部符号表,然後是全局符号表,最後是内置名字表。是以,全局變量不能在函數中直接指派(除非用 global 語句命名),盡管他們可以被引用。

函數引用的實際參數在函數調用時引入局部符号表,是以,實參總是 傳值調用 (這裡的 值 總是一個對象 引用 ,而不是該對象的值)。[1] 一個函數被另一個函數調用時,一個新的局部符号表在調用過程中被建立。

一個函數定義會在目前符号表内引入函數名。函數名指代的值(即函數體)有一個被 Python 解釋器認定為 使用者自定義函數 的類型。 這個值可以賦予其他的名字(即變量名),然後它也可以被當做函數使用。這可以作為通用的重命名機制:

>>> fib
<function fib at 10042ed0>
>>> f = fib >>> f(100) 0 1 1 2 3 5 8 13 21 34 55 89       

如果你使用過其他語言,你可能會反對說:

fib

 不是一個函數,而是一個方法,因為它并不傳回任何值。事實上,沒有 return 語句的函數确實會傳回一個值,雖然是一個相當令人厭煩的值(指 None )。這個值被稱為 

None

 (這是一個内建名稱)。如果 

None

 值是唯一被書寫的值,那麼在寫的時候通常會被解釋器忽略(即不輸出任何内容)。如果你确實想看到這個值的輸出内容,請使用 print() 函數:

>>> fib(0) >>> print(fib(0)) None       

定義一個傳回斐波那契數列數字清單的函數,而不是列印它,是很簡單的:

>>> def fib2(n): # return Fibonacci series up to n ... """Return a list containing the Fibonacci series up to n.""" ... result = [] ... a, b = 0, 1 ... while a < n: ... result.append(a) # see below ... a, b = b, a+b ... return result ... >>> f100 = fib2(100) # call it >>> f100 # write the result [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]       

和以前一樣,這個例子示範了一些新的 Python 功能:

  • return 語句從函數中傳回一個值,不帶表達式的 return 傳回 

    None

    過程結束後也會傳回 

    None

  • 語句 

    result.append(b)

     稱為連結清單對象 

    result

     的一個 方法。方法是一個“屬于”某個對象的函數,它被命名為 

    obj.methodename

    ,這裡的 

    obj

     是某個對象(可能是一個表達式), 

    methodename

     是某個在該對象類型定義中的方法的命名。

    不同的類型定義不同的方法。不同類型可能有同樣名字的方法,但不會混淆。(當你定義自己的對象類型和方法時,可能會出現這種情況,class 的定義方法詳見 類 )。示例中示範的 

    append()

    方法由連結清單對象定義,它向連結清單中加入一個新元素。在示例中它等同于 

    result = result + [a]

    ,不過效率更高。

4.7. 深入 Python 函數定義

在 Python 中,你也可以定義包含若幹參數的函數。這裡有三種可用的形式,也可以混合使用。

4.7.1. 預設參數值

最常用的一種形式是為一個或多個參數指定預設值。這會建立一個可以使用比定義時允許的參數更少的參數調用的函數,例如:

def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while True: ok = input(prompt) if ok in ('y', 'ye', 'yes'): return True if ok in ('n', 'no', 'nop', 'nope'): return False retries = retries - 1 if retries < 0: raise OSError('uncooperative user') print(complaint)       

這個函數可以通過幾種不同的方式調用:

  • 隻給出必要的參數:

    ask_ok('Do you really want to quit?')

  • 給出一個可選的參數:

    ask_ok('OK to overwrite the file?', 2)

  • 或者給出所有的參數:

    ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

這個例子還介紹了 in 關鍵字。它測定序列中是否包含某個确定的值。

預設值在函數 定義 作用域被解析,如下所示:

i = 5

def f(arg=i): print(arg) i = 6 f()       

将會輸出 

5

重要警告: 預設值隻被指派一次。這使得當預設值是可變對象時會有所不同,比如清單、字典或者大多數類的執行個體。例如,下面的函數在後續調用過程中會累積(前面)傳給它的參數:

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       

4.7.2. 關鍵字參數

函數可以通過 關鍵字參數 的形式來調用,形如 

keyword = 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       

在函數調用中,關鍵字的參數必須跟随在位置參數的後面。傳遞的所有關鍵字參數必須與函數接受的某個參數相比對 (例如 

actor

 不是 

parrot

 函數的有效參數),它們的順序并不重要。這也包括非可選參數(例如 

parrot(voltage=1000)

 也是有效的)。任何參數都不可以多次指派。下面的示例由于這種限制将失敗:

>>> def function(a): ... pass ... >>> function(0, a=0) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: function() got multiple values for keyword argument 'a'       

引入一個形如 

**name

 的參數時,它接收一個字典(參見 Mapping Types — 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) 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")       

當然它會按如下内容列印:

-- 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
      

注意在列印關鍵字參數之前,通過對關鍵字字典 

keys()

 方法的結果進行排序,生成了關鍵字參數名的清單;如果不這樣做,列印出來的參數的順序是未定義的。

4.7.3. 可變參數清單

最後,一個最不常用的選擇是可以讓函數調用可變個數的參數。這些參數被包裝進一個元組(參見 元組和序列 )。在這些可變個數的參數之前,可以有零到多個普通的參數:

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'       

4.7.4. 參數清單的分拆

另有一種相反的情況: 當你要傳遞的參數已經是一個清單,但要調用的函數卻接受分開一個個的參數值。這時候你要把已有的清單拆開來。例如内建函數 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 !       

4.7.5. Lambda 形式

出于實際需要,有幾種通常在函數式程式設計語言例如 Lisp 中出現的功能加入到了 Python。通過 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')]       

4.7.6. 文檔字元串

這裡介紹的文檔字元串的概念和格式。

第一行應該是關于對象用途的簡介。簡短起見,不用明确的陳述對象名或類型,因為它們可以從别的途徑了解到(除非這個名字碰巧就是描述這個函數操作的動詞)。這一行應該以大寫字母開頭,以句号結尾。

如果文檔字元串有多行,第二行應該空出來,與接下來的較長的描述明确分隔。接下來的文檔應該有一或多段描述對象的調用約定、邊界效應等。

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.       

4.7.7. 函數注解

函數注解 是關于使用者自定義的函數的完全可選的、随意的中繼資料資訊。無論 Python 本身或者标準庫中都沒有使用函數注解;本節隻是描述了文法。第三方的項目是自由地為文檔,類型檢查,以及其它用途選擇函數注解。

注解是以字典形式存儲在函數的 

__annotations__

 屬性中,對函數的其它部分沒有任何影響。參數注解(Parameter annotations)是定義在參數名稱的冒号後面,緊随着一個用來表示注解的值得表達式。傳回注釋(Return annotations)是定義在一個 

->

 後面,緊随着一個表達式,在冒号與 

->

之間。下面的示例包含一個位置參數,一個關鍵字參數,和沒有意義的傳回值注釋:

>>> def f(ham: 42, eggs: int = 'spam') -> "Nothing to see here": ... print("Annotations:", f.__annotations__) ... print("Arguments:", ham, eggs) ... >>> f('wonderful') Annotations: {'eggs': <class 'int'>, 'return': 'Nothing to see here', 'ham': 42} Arguments: wonderful spam       

4.8. 插曲:編碼風格

此時你已經可以寫一些更長更複雜的 Python 程式,是時候讨論一下 編碼風格 了。大多數語言可以寫(或者更明白的說, 格式化 )作幾種不同的風格。有些比其它的更好讀。讓你的代碼對别人更易讀是個好想法,養成良好的編碼風格對此很有幫助。

對于 Python,PEP 8 引入了大多數項目遵循的風格指導。它給出了一個高度可讀,視覺友好的編碼風格。每個 Python 開發者都應該讀一下,大多數要點都會對你有幫助:

  • 使用 4 空格縮進,而非 TAB

    在小縮進(可以嵌套更深)和大縮進(更易讀)之間,4空格是一個很好的折中。TAB 引發了一些混亂,最好棄用

  • 折行以確定其不會超過 79 個字元

    這有助于小顯示器使用者閱讀,也可以讓大顯示器能并排顯示幾個代碼檔案

  • 使用空行分隔函數和類,以及函數中的大塊代碼
  • 可能的話,注釋獨占一行
  • 使用文檔字元串
  • 把空格放到操作符兩邊,以及逗号後面,但是括号裡側不加空格:

    a = f(1, 2) + g(3, 4)

  • 統一函數和類命名

    推薦類名用 

    駝峰命名

    , 函數和方法名用 

    小寫_和_下劃線

    。總是用 

    self

     作為方法的第一個參數(關于類和方法的知識詳見 初識類 )
  • 不要使用花哨的編碼,如果你的代碼的目的是要在國際化環境。Python 的預設情況下,UTF-8,甚至普通的 ASCII 總是工作的最好
  • 同樣,也不要使用非 ASCII 字元的辨別符,除非是不同語種的會閱讀或者維護代碼。

Footnotes

[1] 實際上, 引用對象調用 描述的更為準确。如果傳入一個可變對象,調用者會看到調用操作帶來的任何變化(如子項插入到清單中)。

轉載于:https://www.cnblogs.com/zqxqx/p/8088112.html