4. 深入了解流程控制
*******************

除了刚介绍的 "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" 陳述式。

如果你要將同一個值與多個常數進行比較，或者檢查特定的型別或屬性，你可能
會發現 "match" 陳述式也很有用。更多的細節，請參閱 match 陳述式。


4.2. "for" 陳述式
=================

在 Python 中的 "for" 陳述式有點不同於在 C 或 Pascal 中的慣用方式。相較
於只能疊代 (iterate) 一個等差數列（如 Pascal），或給予使用者定義疊代步
驟與終止條件（如 C），Python 的 "for" 陳述式疊代任何序列（list 或者字
串）的元素，順序與它們出現在序列中的順序相同。例如（無意雙關）：

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

在疊代一個集合的同時修改該集合的內容，很難獲取想要的結果。比較直觀的替
代方式，是疊代該集合的副本，或建立一個新的集合：

   # Create a sample collection
   users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}

   # 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


4.3. "range()" 函式
===================

如果你需要疊代一個數列的話，使用內建 "range()" 函式就很方便。它可以生
成一等差數列：

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

給定的結束值永遠不會出現在生成的序列中；"range(10)" 生成的 10 個數值，
即對應存取一個長度為 10 的序列內每一個項目的索引值。也可以讓 range 從
其他數值開始計數，或者給定不同的公差（甚至為負；有時稱之為 step）：

   >>> list(range(5, 10))
   [5, 6, 7, 8, 9]

   >>> list(range(0, 10, 3))
   [0, 3, 6, 9]

   >>> list(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()" 函式將更為方便，詳見迴圈技巧。

如果直接印出一個 range 則會出現奇怪的輸出：

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

在很多情況下，由 "range()" 回傳的物件表現得像是一個 list（串列）一樣，
但實際上它並不是。它是一個在疊代時能夠回傳所要求的序列中所有項目的物件
，但它不會真正建出這個序列的 list，以節省空間。

我們稱這樣的物件為 *iterable*（可疊代物件），意即能作為函式及架構中可
以一直獲取項目直到取盡的對象。我們已經了解 "for" 陳述式就是如此的架構
，另一個使用 iterable 的函式範例是 "sum()"：

   >>> sum(range(4))  # 0 + 1 + 2 + 3
   6

待會我們可以看到更多回傳 iterable 和使用 iterable 為引數的函式。在資料
結構章節中，我們會討論更多關於 "list()" 的細節。


4.4. 迴圈內的 "break" 和 "continue" 陳述式及 "else" 子句
========================================================

"break" 语句将跳出最近的一层 "for" 或 "while" 循环。

"for" 或 "while" 循环可以包括 "else" 子句。

在 "for" 循环中，"else" 子句会在循环成功结束最后一次迭代之后执行。

在 "while" 循环中，它会在循环条件变为假值后执行。

无论哪种循环，如果因为 "break" 而结束，那么 "else" 子句就 **不会** 执
行。

下面的搜索质数的 "for" 循环就是一个例子：

   >>> 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" 子句用於迴圈時，相較於搭配 "if" 陳述式使用，它的行為與 "try"
陳述式中的 "else" 子句更為相似："try" 陳述式的 "else" 子句在沒有發生例
外 (exception) 時執行，而迴圈的 "else" 子句在沒有任何 "break" 發生時執
行。更多有關 "try" 陳述式和例外的介紹，見處理例外。

"continue" 陳述式，亦承襲於 C 語言，讓所屬的迴圈繼續執行下個疊代：

   >>> for num in range(2, 10):
   ...     if num % 2 == 0:
   ...         print("Found an even number", num)
   ...         continue
   ...     print("Found an odd number", num)
   ...
   Found an even number 2
   Found an odd number 3
   Found an even number 4
   Found an odd number 5
   Found an even number 6
   Found an odd number 7
   Found an even number 8
   Found an odd number 9


4.5. "pass" 陳述式
==================

"pass" 陳述式不執行任何動作。它可用在語法上需要一個陳述式但程式不需要
執行任何動作的時候。例如：

   >>> while True:
   ...     pass  # Busy-wait for keyboard interrupt (Ctrl+C)
   ...

這經常用於建立簡單的 class（類別）：

   >>> class MyEmptyClass:
   ...     pass
   ...

"pass" 亦可作為一個函式或條件判斷主體的預留位置，在你撰寫新的程式碼時
讓你保持在更抽象的思維層次。"pass" 會直接被忽略：

   >>> def initlog(*args):
   ...     pass   # Remember to implement this!
   ...


4.6. "match" 陳述式
===================

"match" 陳述式會拿取一個運算式，並將其值與多個連續的模式 (pattern) 進
行比較，這些模式是以一個或多個 case 區塊來表示。表面上，這類似 C、Java
或 JavaScript（以及許多其他語言）中的 switch 陳述式，但它與 Rust 或
Haskell 等語言中的模式匹配 (pattern matching) 更為相近。只有第一個匹配
成功的模式會被執行，而它也可以將成分（序列元素或物件屬性）從值中提取到
變數中。

最簡單的形式，是將一個主題值 (subject value) 與一個或多個字面值
(literal) 進行比較：

   def http_error(status):
       match status:
           case 400:
               return "Bad request"
           case 404:
               return "Not found"
           case 418:
               return "I'm a teapot"
           case _:
               return "Something's wrong with the internet"

請注意最後一段：「變數名稱」"_" 是作為*通用字元 (wildcard)*的角色，且
永遠不會匹配失敗。如果沒有 case 匹配成功，則不會執行任何的分支。

你可以使用 "|"（「或」）來將多個字面值組合在單一模式中：

   case 401 | 403 | 404:
       return "Not allowed"

模式可以看起來像是拆解賦值 (unpacking assignment)，且可以用來連結變數
：

   # point is an (x, y) tuple
   match point:
       case (0, 0):
           print("Origin")
       case (0, y):
           print(f"Y={y}")
       case (x, 0):
           print(f"X={x}")
       case (x, y):
           print(f"X={x}, Y={y}")
       case _:
           raise ValueError("Not a point")

請仔細研究那個例子！第一個模式有兩個字面值，可以想作是之前所述的字面值
模式的延伸。但是接下來的兩個模式結合了一個字面值和一個變數，且該變數*
繫結 (bind)*了來自主題 ("point") 的一個值。第四個模式會擷取兩個值，這
使得它在概念上類似於拆解賦值 "(x, y) = point"。

如果你要用 class 來結構化你的資料，你可以使用該 class 的名稱加上一個引
數列表，類似一個建構式 (constructor)，但它能夠將屬性擷取到變數中：

   class Point:
       def __init__(self, x, y):
           self.x = x
           self.y = y

   def where_is(point):
       match point:
           case Point(x=0, y=0):
               print("Origin")
           case Point(x=0, y=y):
               print(f"Y={y}")
           case Point(x=x, y=0):
               print(f"X={x}")
           case Point():
               print("Somewhere else")
           case _:
               print("Not a point")

你可以將位置參數 (positional parameter) 與一些能夠排序其屬性的內建
class（例如 dataclasses）一起使用。你也可以透過在 class 中設定特殊屬性
"__match_args__"，來定義模式中屬性們的特定位置。如果它被設定為 ("x",
"y")，則以下的模式都是等價的（且都會將屬性 "y" 連結到變數 "var"）：

   Point(1, var)
   Point(1, y=var)
   Point(x=1, y=var)
   Point(y=var, x=1)

理解模式的一種推薦方法，是將它們看作是你會放在賦值 (assignment) 左側內
容的一種延伸形式，這樣就可以了解哪些變數會被設為何值。只有獨立的名稱（
像是上面的 "var"）能被 match 陳述式賦值。點分隔名稱（如 "foo.bar"）、
屬性名稱（上面的 "x=" 及 "y="）或 class 名稱（由它們後面的 "(...)" 被
辨識，如上面的 "Point"）則永遠無法被賦值。

模式可以任意地被巢套 (nested)。例如，如果我們有一個由某些點所組成的簡
短 list，我們就可以像這樣加入 "__match_args__" 來對它進行匹配：

   class Point:
       __match_args__ = ('x', 'y')
       def __init__(self, x, y):
           self.x = x
           self.y = y

   match points:
       case []:
           print("No points")
       case [Point(0, 0)]:
           print("The origin")
       case [Point(x, y)]:
           print(f"Single point {x}, {y}")
       case [Point(0, y1), Point(0, y2)]:
           print(f"Two on the Y axis at {y1}, {y2}")
       case _:
           print("Something else")

我們可以在模式中加入一個 "if" 子句，稱為「防護 (guard)」。如果該防護為
假，則 "match" 會繼續嘗試下一個 case 區塊。請注意，值的擷取會發生在防
護的評估之前：

   match point:
       case Point(x, y) if x == y:
           print(f"Y=X at {x}")
       case Point(x, y):
           print(f"Not on the diagonal")

此種陳述式的其他幾個重要特色：

* 與拆解賦值的情況類似，tuple（元組）和 list 模式具有完全相同的意義，
  而且實際上可以匹配任意的序列。一個重要的例外，是它們不能匹配疊代器
  (iterator) 或字串。

* 序列模式 (sequence pattern) 可支援擴充拆解 (extended unpacking)：
  "[x, y, *rest]" 與 "(x, y, *rest)" 的作用類似於拆解賦值。"*" 後面的
  名稱也可以是 "_"，所以 "(x, y, *_)" 會匹配一個至少兩項的序列，且不會
  連結那兩項以外的其餘項。

* 映射模式 (mapping pattern)："{"bandwidth": b, "latency": l}" 能從一
  個 dictionary（字典）中擷取 ""bandwidth"" 及 ""latency"" 的值。與序
  列模式不同，額外的鍵 (key) 會被忽略。一種像是 "**rest" 的拆解方式，
  也是可被支援的。（但 "**_" 則是多餘的做法，所以它並不被允許。）

* 使用關鍵字 "as" 可以擷取子模式 (subpattern)：

     case (Point(x1, y1), Point(x2, y2) as p2): ...

  將會擷取輸入的第二個元素作為 "p2"（只要該輸入是一個由兩個點所組成的
  序列）。

* 大部分的字面值是藉由相等性 (equality) 來比較，但是單例物件
  (singleton) "True"、"False" 和 "None" 是藉由標識值 (identity) 來比較
  。

* 模式可以使用附名常數 (named constant)。這些模式必須是點分隔名稱，以
  免它們被解釋為擷取變數：

     from enum import Enum
     class Color(Enum):
         RED = 'red'
         GREEN = 'green'
         BLUE = 'blue'

     color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))

     match color:
         case Color.RED:
             print("I see red!")
         case Color.GREEN:
             print("Grass is green")
         case Color.BLUE:
             print("I'm feeling the blues :(")

關於更詳細的解釋和其他範例，你可以閱讀 **PEP 636**，它是以教學的格式編
寫而成。


4.7. 定義函式 (function)
========================

我們可以建立一個函式來產生費式數列到任何一個上界：

   >>> 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*。（關於 docstring 的細節請參見說明文件字串
(Documentation Strings)段落。）有些工具可以使用 docstring 來自動產生線
上或可列印的文件，或讓使用者能以互動的方式在原始碼中瀏覽文件。在原始碼
中加入 docstring 是個好慣例，應該養成這樣的習慣。

函式*執行*時會建立一個新的符號表 (symbol table) 來儲存該函式內的區域變
數 (local variable)。更精確地說，所有在函式內的變數賦值都會把該值儲存
在一個區域符號表。然而，在引用一個變數時，會先從區域符號表開始搜尋，其
次為外層函式的區域符號表，其次為全域符號表 (global symbol table)，最後
為所有內建的名稱。因此，在函式中，全域變數及外層函式變數雖然可以被引用
，但無法被直接賦值（除非全域變數是在 "global" 陳述式中被定義，或外層函
式變數在 "nonlocal" 陳述式中被定義）。

在一個函式被呼叫的時候，實際傳入的參數（引數）會被加入至該函式的區域符
號表。因此，引數傳入的方式為*傳值呼叫 (call by value)*（這裡傳遞的*值*
永遠是一個物件的*參照 (reference)*，而不是該物件的值）。[1] 當一個函式
呼叫別的函式或遞迴呼叫它自己時，在被呼叫的函式中會建立一個新的區域符號
表。

函式定義時，會把該函式名稱加入至當前的符號表。函式名稱的值帶有一個型別
，並被直譯器辨識為使用者自定函式 (user-defined function)。該值可以被指
定給別的變數名，使該變數名也可以被當作函式使用。這是常見的重新命名方式
：

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

如果你是來自別的語言，你可能不同意 "fib" 是個函式，而是個程序
(procedure)，因為它並沒有回傳值。實際上，即使一個函式缺少一個 "return"
陳述式，它亦有一個固定的回傳值。這個值稱為 "None"（它是一個內建名稱）
。在直譯器中單獨使用 "None" 時，通常不會被顯示。你可以使用 "print()"
來看到它：

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

如果要寫一個函式回傳費式數列的 list 而不是直接印出它，這也很容易：

   >>> 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(a)" 调用了列表对象 "result" 的 *方法*。 方法是‘
  从属于’对象的函数，其名称为 "obj.methodname"，其中 "obj" 是某个对象
  （可以是一个表达式），"methodname" 是由对象的类型定义的方法名称。 不
  同的类型定义了不同的方法。 不同的类型的方法可以使用相同的名称而不会
  产生歧义。 （使用 *类* 可以定义自己的对象类型和方法，参见 Class（類
  別）。） 在示例中显示的方法 "append()" 是由列表对象定义的；它会在列
  表的末尾添加一个新元素。 在本例中它等同于 "result = result + [a]"，
  但效率更高。


4.8. 深入了解函式定義
=====================

定義函式時使用的引數 (argument) 數量是可變的。總共有三種可以組合使用的
形式。


4.8.1. 預設引數值
-----------------

為一個或多個引數指定預設值是很有用的方式。函式建立後，可以用比定義時更
少的引數呼叫該函式。例如：

   def ask_ok(prompt, retries=4, reminder='Please try again!'):
       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 ValueError('invalid user response')
           print(reminder)

該函式可以用以下幾種方式被呼叫：

* 只給必要引數："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"。

**重要警告**：預設值只求值一次。當預設值為可變物件，例如 list、
dictionary（字典）或許多類別實例時，會產生不同的結果。例如，以下函式於
後續呼叫時會累積曾經傳遞的引數：

   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.8.2. 關鍵字引數
-----------------

函式也可以使用*關鍵字引數*，以 "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

函式呼叫時，關鍵字引數 (keyword argument) 必須在位置引數 (positional
argument) 後面。所有傳遞的關鍵字引數都必須匹配一個可被函式接受的引數（
"actor" 不是 "parrot" 函式的有效引數），而關鍵字引數的順序並不重要。此
規則也包括必要引數，（"parrot(voltage=1000)" 也有效）。一個引數不可多
次被賦值，下面就是一個因此限制而無效的例子：

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

當最後一個參數為 "**name" 形式時，它接收一個 dictionary（字典，詳見 映
射类型 --- dict），該字典包含所有可對應形式參數以外的關鍵字引數。
"**name" 可以與 "*name" 參數（下一小節介紹）組合使用，"*name" 接收一個
tuple，該 tuple 包含一般參數以外的位置引數（"*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

注意，關鍵字引數的輸出順序與呼叫函式時被提供的順序必定一致。


4.8.3. 特殊參數
---------------

在預設情況，引數能以位置或明確地以關鍵字傳遞给 Python 函式。為了程式的
可讀性及效能，限制引數的傳遞方式是合理的，如此，開發者只需查看函式定義
，即可確定各項目是按位置，按位置或關鍵字，還是按關鍵字傳遞。

函式定義可能如以下樣式：

   def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
         -----------    ----------     ----------
           |             |                  |
           |        Positional or keyword   |
           |                                - Keyword only
            -- Positional only

"/" 和 "*" 是選擇性的。這些符號若被使用，是表明引數被傳遞給函式的參數
種類：僅限位置、位置或關鍵字、僅限關鍵字。關鍵字參數也稱為附名參數
(named parameters)。


4.8.3.1. 位置或關鍵字引數 (Positional-or-Keyword Arguments)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

若函式定義中未使用 "/" 和 "*" 時，引數可以按位置或關鍵字傳遞給函式。


4.8.3.2. 僅限位置參數 (Positional-Only Parameters)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

此處再詳述一些細節，特定參數可以標記為*僅限位置*。若參數為*僅限位置*時
，它們的順序很重要，且這些參數不能用關鍵字傳遞。僅限位置參數必須放在
"/"（斜線）之前。"/" 用於在邏輯上分開僅限位置參數與其餘參數。如果函式
定義中沒有 "/"，則表示沒有任何僅限位置參數。

"/" 後面的參數可以是*位置或關鍵字*或*僅限關鍵字*參數。


4.8.3.3. 僅限關鍵字引數 (Keyword-Only Arguments)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

要把參數標記為*僅限關鍵字*，表明參數必須以關鍵字引數傳遞，必須在引數列
表中第一個*僅限關鍵字*參數前放上 "*"。


4.8.3.4. 函式範例
~~~~~~~~~~~~~~~~~

請看以下的函式定義範例，注意 "/" 和 "*" 記號：

   >>> 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 some positional-only arguments passed as keyword arguments: '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 some positional-only arguments passed as keyword arguments: 'pos_only'

最後，請看這個函式定義，如果 "**kwds" 內有 "name" 這個鍵，可能與位置引
數 "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" 中使用，而不產生歧義。


4.8.3.5. 回顧
~~~~~~~~~~~~~

此用例決定哪些參數可以用於函式定義：

   def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

說明：

* 如果不想讓使用者使用參數名稱，請使用僅限位置。當參數名稱沒有實際意義
  時，若你想控制引數在函式呼叫的排列順序，或同時使用位置參數和任意關鍵
  字時，這種方式很有用。

* 當參數名稱有意義，且明確的名稱可讓函式定義更易理解，或是你不希望使用
  者依賴引數被傳遞時的位置時，請使用僅限關鍵字。

* 對於應用程式介面 (API)，使用僅限位置，以防止未來參數名稱被修改時造成
  API 的中斷性變更。


4.8.4. 任意引數列表 (Arbitrary Argument Lists)
----------------------------------------------

最後，有個較不常用的選項，是規定函式被呼叫時，可以使用任意數量的引數。
這些引數會被包裝進一個 tuple 中（詳見 Tuples 和序列 (Sequences)）。在
可變數量的引數之前，可能有零個或多個普通引數：

   def write_multiple_items(file, separator, *args):
       file.write(separator.join(args))

通常，這些 *variadic*（可變的）引數會出現在參數列表的最末端，這樣它們
就可以把所有傳遞給函式的剩餘輸入引數都撈起來。出現在 "*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.8.5. 拆解引數列表（Unpacking Argument Lists）
-----------------------------------------------

當引數們已經存在一個 list 或 tuple 裡，但為了滿足一個需要個別位置引數
的函式呼叫，而去拆解它們時，情況就剛好相反。例如，內建的 "range()" 函
式要求分開的 *start* 和 *stop* 引數。如果這些引數不是分開的，則要在呼
叫函式時，用 "*" 運算子把引數們從 list 或 tuple 中拆解出來：

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

同樣地，dictionary（字典）可以用 "**" 運算子傳遞關鍵字引數：

   >>> 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.8.6. Lambda 運算式
--------------------

"lambda" 關鍵字用於建立小巧的匿名函式。"lambda a, b: a+b" 函式返回兩個
引數的和。Lambda 函式可用於任何需要函式物件的地方。在語法上，它們被限
定只能是單一運算式。在語義上，它就是一個普通函式定義的語法糖
(syntactic sugar)。與巢狀函式定義一樣，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.8.7. 說明文件字串 (Documentation Strings)
-------------------------------------------

以下是關於說明文件字串內容和格式的慣例。

第一行都是一段關於此物件目的之簡短摘要。為保持簡潔，不應在這裡明確地陳
述物件的名稱或型別，因為有其他方法可以達到相同目的（除非該名稱剛好是一
個描述函式運算的動詞）。這一行應以大寫字母開頭，以句號結尾。

文件字串為多行時，第二行應為空白行，在視覺上將摘要與其餘描述分開。後面
幾行可包含一或多個段落，描述此物件的呼叫慣例、副作用等。

Python 剖析器 (parser) 不會去除 Python 中多行字串的縮排，因此，處理說
明文件的工具應在必要時去除縮排。這項操作遵循以下慣例：在字串第一行*之
後*的第一個非空白行決定了整個說明文件字串的縮排量（不能用第一行的縮排
，因為它通常與字串的開頭引號們相鄰，其縮排在字串文本中並不明顯），然後
，所有字串行開頭處與此縮排量「等價」的空白字元會被去除。不應出現比上述
縮進量更少的字串行，但若真的出現了，這些行的全部前導空白字元都應被去除
。展開 tab 鍵後（通常為八個空格），應測試空白字元量是否等價。

下面是多行說明字串的一個範例：

   >>> 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.8.8. 函式註釋 (Function Annotations)
--------------------------------------

函式註釋是選擇性的元資料（metadata）資訊，描述使用者定義函式所使用的型
別（更多資訊詳見 **PEP 3107** 和 **PEP 484**）。

*标注* 以字典的形式存放在函数的 "__annotations__" 属性中而对函数的其他
部分没有影响。 形参标注的定义方式是在形参名后加冒号，后面跟一个会被求
值为标注的值的表达式。 返回值标注的定义方式是加组合符号 "->"，后面跟一
个表达式，这样的校注位于形参列表和表示 "def" 语句结束的冒号。 下面的示
例有一个必须的参数、一个可选的关键字参数以及返回值都带有相应的标注:

   >>> def f(ham: str, eggs: str = 'eggs') -> str:
   ...     print("Annotations:", f.__annotations__)
   ...     print("Arguments:", ham, eggs)
   ...     return ham + ' and ' + eggs
   ...
   >>> f('spam')
   Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
   Arguments: spam eggs
   'spam and eggs'


4.9. 間奏曲：程式碼風格 (Coding Style)
======================================

現在你即將要寫更長、更複雜的 Python 程式，是時候討論一下*編碼樣式*了。
大多數語言都能以不同的樣式被書寫（或更精確地說，被*格式化*），而有些樣
式比其他的更具可讀性。能讓其他人輕鬆閱讀你的程式碼永遠是一個好主意，而
使用優良的編碼樣式對此有極大的幫助。

對於 Python，大多數的專案都遵循 **PEP 8** 的樣式指南；它推行的編碼樣式
相當可讀且賞心悅目。每個 Python 開發者都應該花點時間研讀；這裡是該指南
的核心重點：

* 用 4 個空格縮排，不要用 tab 鍵。

  4 個空格是小縮排（容許更大的巢套深度）和大縮排（較易閱讀）之間的折衷
  方案。Tab 鍵會造成混亂，最好別用。

* 換行，使一行不超過 79 個字元。

  換行能讓使用小顯示器的使用者方便閱讀，也可以在較大顯示器上並排陳列多
  個程式碼檔案。

* 用空行分隔函式和 class（類別），及函式內較大塊的程式碼。

* 如果可以，把註解放在單獨一行。

* 使用說明字串。

* 運算子前後、逗號後要加空格，但不要直接放在括號內側："a = f(1, 2) +
  g(3, 4)"。

* Class 和函式的命名樣式要一致；按慣例，命名 class 用 "UpperCamelCase"
  （駝峰式大小寫），命名函式與 method 用 "lowercase_with_underscores"
  （小寫加底線）。永遠用 "self" 作為 method 第一個引數的名稱（關於
  class 和 method，詳見 初見 class）。

* 若程式碼是為了用於國際環境時，不要用花俏的編碼。Python 預設的 UTF-8
  或甚至普通的 ASCII，就可以勝任各種情況。

* 同樣地，若不同語言使用者閱讀或維護程式碼的可能性微乎其微，就不要在命
  名時使用非 ASCII 字元。

-[ 註解 ]-

[1] 實際上，*傳址呼叫 (call by object reference)* 的說法可能較為貼切。
    因為，若傳遞的是一個可變物件時，呼叫者將看得見被呼叫者對物件做出的
    任何改變（例如被插入 list 內的新項目）。
