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]实际上, 引用对象调用 �述的更为准确。如果传入一个��对象,调用者会看到调用�作带�的任何�化(如�项�入到列表中)。