4.更多的控制流工具

除了前面介绍的while语句,Python也有其它语言常见的流程控制语句,但是稍有不同。

4.1. if语句

最为人所知的语句类型可能是if语句。例如:

>>> x = int(raw_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...序列用于替代其它语言中的switchcase语句。

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()非常方便。它将生成包含等差数列的列表:

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

给定的终点永远不会在生成的列表中;range(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

然而,在大部分情况下使用enumerate()函数会更加方便,请参见循环的技巧

4.4. breakcontinue语句,以及循环中else子句

break语句和C中的类似,用于跳出最近的forwhile循环。

循环语句可以有一个else子句;当(for)循环迭代完整个列表或(while)循环条件变为假,而非由break语句终止时,就会执行这个else语句。下面循环搜索质数的代码例示了这一点:

>>> 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语句。)

与在if语句中的用法相比,循环中的else子句与try语句的else子句有更多的共同点: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.定义函数

我们可以创建一个生成任意Fibonacci series的函数:

>>> 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,
...         a, b = b, a+b
...
>>> # 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引入函数的定义它必须紧跟函数名称和带圆括号正式的参数列表。语句,该函数体开始在下一行,并必须缩进。

函数体的第一个语句 (可选) 可以是一个字符串文字 ;此字符串是该函数的文档字符串或文档字符串(更多 docstrings 可以找到有关文档字符串一节中)。有工具,使用 docstrings 可以自动生成联机帮助或印刷文档,或允许用户以交互方式浏览代码 ;它的良好做法,在您编写的代码中包含 docstrings 所以使它成为习惯。

执行一个函数会引入一个用于函数的局部变量的新符号表。更确切地说,在一个函数中的所有变量赋值将值存储在本地符号表 ;而变量引用第一次看看在本地符号表中,然后在本地符号表的封闭的函数,然后在全局符号表中,和最后的内置名称的表中。然而,在函数内部无法给一个全局变量直接赋值(除非在一个global语句中命名),虽然可以引用它们。

到调用函数的实际参数 (参数) 介绍本地符号表中被调用的函数被调用 ; 时因此,参数的传递使用传值调用(这里的始终是对象的引用,不是对象的值)。[1]当一个函数调用另一个函数时,是为该调用创建一个新的本地符号表。

函数定义引入了当前的符号表中的函数名称。函数名称的值已为用户定义的函数,解释器识别的类型。此值可以分配给另一个名称,然后也可作为函数。这将作为一个一般的重命名机制:

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

如果你使用过其他语言,你可能会反对说:fib不是一个函数,而是一个过程(子程序),因为它并不返回任何值。事实上,甚至功能没有return语句返回一个值,尽管是一种相当无聊。此值被称为(它是一个内置的名称)。如果 None只是唯一的输出,解释器通常不会打印出来。如果你真的想使用打印,您可以看到它:

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

return Fibonacci series up to n

>>> 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.7.更多关于定义函数

它也是可以定义具有可变数目的参数的函数。有三种形式,可以结合。

4.7.1.默认参数值

最有用的形式是指定一个或多个参数的默认值。这将创建一个可调用的函数实参少于它定义为允许。例如:

def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
    while True:
        ok = raw_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 IOError('refusenik 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。 关键字参数

函数也可以通过kwarg=value形式的关键字参数调用。例如,下面的函数:

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print "-- This parrot wouldn't", action,
    print "if you put", voltage, "volts through it."
    print "-- Lovely plumage, the", type
    print "-- It's", state, "!"

接受一个必选参数(voltage)和三个可选参数(state,actiontype)。可以在下列任一方式调用此函数:

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形式出现时,它接收一个字典(见映射类型 — 字典),该字典包含了所有未出现在形式参数列表中的关键字参数。它还可能与*name形式的参数(在下一小节中所述)组合使用,*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))

4.7.4.开箱参数列表

相反的情况发生时参数列表或元组中已有但需要解压需要单独的位置参数的函数调用。例如,内置的range()函数期望单独的startstop参数。如果它们不是可用的分开,写的函数调用与*-运算符进行解包的列表或元组的参数:

>>> range(3, 6)             # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> 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,
...     print "if you put", voltage, "volts through it.",
...     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表达式

可以使用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 分析器不会在 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.8.间奏曲: 编码样式

既然要编写更长更复杂的 Python 代码, 这就要对其编码风格进行讨论了 。大多数语言可以编写成(或者更准确地说,被格式化成)不同风格的代码;其中一些会比其他的具有更好的可读性。让你的代码更易读一直是个好想法,而养成良好的编码风格对此益处良多。

对于 Python 而言, PEP 8 已成为大多数项目遵循的编码风格指南;它促进了非常易读和眼睛讨好的编码风格。每个 Python 开发者应该看它在一些点 ;以下是其中的一些要点:

  • 使用缩进 4 空间和没有的选项卡。

    4 个空格是小缩进 (允许更多的嵌套深度) 和大缩进 (易于阅读) 的折衷。Tabs 会引起混乱,最好弃用。

  • 换行以确保一行的字符数不超过 79 个。

    这帮助用户解决小型显示器,并使它可能有几个代码文件--并排在更大的显示器上。

  • 使用空行分隔函数和类,以及更大块的函数内的代码。

  • 如果可能,付诸表决意见在他们自己的行上。

  • 使用文档字符串。

  • 运算符周围和逗号后面使用空格,但是括号里侧不加空格: a = f(1, 2) + g(3, 4)

  • 保持类和函数的命名一致;通常是使用驼峰命名法命名类,使用下划线小写字母命名函数和方法 。始终使用self作为方法的第一个参数的名称(关于类和方法的更多信息,请参见初识类)。

  • 如果希望代码在全球环境中使用,不要使用奇特的编码。在任何情况下的纯 ASCII 效果最佳。

脚注

[1]实际上,更好的描述是通过对象的引用调用,因为如果传递的是一个可变的对象,那么调用者可以看到被调用者对它所做的任何改变(插入到一个列表中元素)。