除了刚刚介绍的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部分,其他部分是可选的。 关键字'elif'是'else if'的缩写,对于避免过多的缩进非常有用。 一个if ... elif ... elif ...序列是在其他语言中找到的switch或case语句的替代品。
4.2. for 语句
Python中的for语句与C或Pascal中的for语句有所不同。 Python的for语句并不总是迭代数字的算术级数(比如在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']
用 for w in words[:],这个例子会试图创建一个无限的列表,一遍又一遍地插入defenestrate。
4.3. The range() 函数
如果您确实需要迭代一系列数字,则内置函数range()将派上用场。 它生成算术级数:
>>> for i in range(5): ... print(i) ... 0 1 2 3 4
给定的终点不是生成的序列的一部分; range(10)生成10个值,即长度为10的序列的项目的合法索引。可以让范围从另一个数字开始,或指定不同的增量(甚至是负数;有时称为“步骤”):
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()函数很方便,请参阅循环技术。
如果您只打印一个范围,会发生一件奇怪的事情:
>>> print(range(10)) range(0, 10)在许多方面,range()返回的对象的行为就好像它是一个列表,但事实上并非如此。 它是一个对象,它在您迭代时返回所需序列的连续项,但它并不真正生成列表,从而节省空间。
我们说这样的对象是可迭代的,也就是说,适合作为函数和构造的目标,它们期望从它们可以获得连续项目的东西,直到耗尽耗尽为止。 我们已经看到for语句就是这样一个迭代器。 函数list()是另一个; 它会根据迭代创建列表:
>>> list(range(5)) [0, 1, 2, 3, 4]
稍后我们将看到更多函数返回迭代器并将迭代器作为参数。
4.4. 循环中的break/continue/else块
与C一样,break语句跳出了最内层的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
(是的,这是正确的代码仔细观察:else子句属于for循环,而不是if语句。)
与循环一起使用时,else子句与if语句的else子句相比,它与try语句中的else子句更相似:try语句的else子句在没有发生异常时运行,而循环中的else子句在没有发生中断时运行。 有关try语句和异常的更多信息,请参阅处理异常。
同样借用自C中continue语句,是直接进行循环中的下一次迭代:
>>> 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) ...
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。 (关于文档字符串的更多信息可以在文档字符串部分找到。)有一些工具可以使用文档自动生成在线或打印文档,或让用户交互式浏览代码;在您编写的代码中包含文档字符串是一种很好的做法,请养成习惯。
函数的执行引入了一个用于函数局部变量的新符号表。更确切地说,函数中的所有变量赋值都将值存储在本地符号表中;而变量引用首先在本地符号表中查找,然后在封闭函数的本地符号表中,然后在全局符号表中,最后在内置名称表中查找。因此,全局变量不能直接在函数内赋值(除非使用global语句),尽管它们可能被引用。
函数调用的实际参数(参数)在被调用函数的本地符号表中引入时被调用;因此,参数通过值调用传递(其中值始终是对象引用,而不是对象的值)。 [1]当函数调用另一个函数时,为该调用创建一个新的本地符号表。
函数定义在当前符号表中引入函数名称。函数名称的值具有由解释器识别为用户定义函数的类型。这个值可以分配给另一个名字,然后也可以作为一个函数使用。这是一个通用的重命名机制:
>>> fib <function fib at 10042ed0> >>> f = fib >>> f(100) 0 1 1 2 3 5 8 13 21 34 55 89
来自其他语言,你可能会说fib不是一个函数,而是一个过程,因为它不返回一个值。 事实上,即使没有返回语句的函数也会返回一个值,尽管这是一个相当无聊的值。 这个值被称为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语句返回一个函数的值。 没有表达式参数的情况下返回None。 落在函数的末尾也会返回None。
语句result.append(a)调用列表对象result的一个方法。 方法是一个函数,它属于一个对象,名为obj.methodname,其中obj是某个对象(这可能是一个表达式),而methodname是由该对象的类型定义的方法的名称。 不同类型定义不同的方法。 不同类型的方法可以具有相同的名称而不会造成歧义。 (可以使用类定义您自己的对象类型和方法,请参见类)示例中显示的方法append()是为列表对象定义的; 它在列表的末尾添加了一个新元素。 在这个例子中,它等价于result = result + [a],但效率更高。
4.7.更多关于定义函数
4.7.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('你真的想退出吗?')
给出一个可选的参数:ask_ok('确定覆盖文件?',2)
或者甚至给出所有参数:ask_ok('确定覆盖文件?',2,'来吧,只有是或不是!')
这个例子还介绍了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 关键字参数
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 <module> TypeError: function() got multiple values for keyword argument 'a'
当表单**名称的最终形式参数存在时,它将接收包含除正式参数对应的所有关键字参数外的字典(请参见映射类型 - dict)。 这可以与形式*名称的形式参数(在下一小节中描述)相结合,其接收包含形式参数列表之外的位置参数的元组。 (*名称必须在**名称之前出现)。例如,如果我们定义如下的函数:
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.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()函数需要单独的启动和停止参数。 如果它们不能单独使用,请使用* -操作符写入函数调用以将参数从列表或元组中解开:
>>> 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表达式
>>> 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.7.7 功能注释
注释以字典的形式存储在函数的__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.8 插曲:编码风格
现在您要编写更长,更复杂的Python,现在是讨论编码风格的好时机。 大多数语言可以用不同的风格编写(或更简洁,格式化); 有些比其他更可读。 让别人轻松阅读你的代码总是一个好主意,采用一种很好的编码风格对此非常有帮助。
对于Python而言,PEP 8已经成为大多数项目遵循的风格指南; 它促进了非常可读和令人喜欢的编码风格。 每个Python开发者都应该在某个时候阅读它; 这里是为你提取的最重要的点:
- 使用4空格缩进,并且没有制表符。 4个空格是小缩进(允许更大的嵌套深度)和大缩进(更容易读取)之间的良好折衷。制表符引入了混淆,最好省略。
- 自动换行不要超过79个字符。这有助于用户使用小型显示器,并可以在较大的显示器上并排显示多个代码文件。
- 使用空行来分隔函数和类,以及函数内的更大块代码。
- 如果可能,请将注释放在他们自己的一行上。
- 使用docstrings。
- 在运算符和逗号后面使用空格,但不能直接在包围结构中使用空格:a = f(1,2)+ g(3,4)。
- 一致地命名你的类和函数;约定是使用CamelCase类和lower_case_with_underscores函数和方法。始终使用self作为第一个方法参数的名称(有关类和方法的更多信息,请参见首先查看类)。
- 如果您的代码旨在用于国际环境,则不要使用奇特的编码。在任何情况下,Python的默认值,UTF-8或甚至纯ASCII都能最好地工作。
- 同样,不要在标识符中使用非ASCII字符,如果只有轻微的机会,人们说不同的语言会读取或维护代码。