💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 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](#)...序列用于替代其它语言中的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")函数 如果你确实需要遍历一个数字序列,内置函数[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()](# "range")和[len()](# "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()](# "enumerate")函数会更加方便,请参见[*循环的技巧*](#)。 ### 4.4. [break](#)和[continue](#)语句,以及循环中[else](#)子句 [break](#)语句和C中的类似,用于跳出最近的[for](#)或[while](#)循环。 循环语句可以有一个else子句;当([for](#))循环迭代完整个列表或([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子句与[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, ... 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](#)引入函数的*定义*。其后必须跟有函数名和以括号标明的形式参数列表。组成函数体的语句从下一行开始,且必须缩进。 函数体的第一行可以是一个可选的字符串文本;此字符串是该函数的文档字符串,或称为*docstring*。(更多关于 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(它是一个内置的名称)。如果 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(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 (' Doyoureallywanttoquit?') - 给出一个可选的参数: ask_ok ('OKtooverwritethefile?',2) - 或者给出所有的参数: ask_ok ('OKtooverwritethefile?',2,'Comeon,onlyyesorno!') 此示例还引入了[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,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形式出现时,它接收一个字典(见[*映射类型 — 字典*](#)),该字典包含了所有未出现在形式参数列表中的关键字参数。它还可能与*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()](# "range")函数期望单独的*start*和*stop*参数。如果它们不是独立的,函数调用时使用 *-操作符将参数从列表或元组中分拆开来: ~~~ >>> 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](#)关键字创建小的匿名函数。下面这个函数返回它的两个参数的和:lambdaa,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.8. 插曲:编码风格 既然你将要编写更长更复杂的 Python 片段,这是谈一谈 *编码风格* 的好时机。大多数语言可以编写成(或者更准确地讲,*格式化成*)不同的风格;其中有一些会比其他风格更具可读性。让你的代码对别人更易读永远是个好想法,养成良好的编码风格对此有很大的帮助。 对于 Python 而言, [**PEP 8**](http://www.python.org/dev/peps/pep-0008) 已成为大多数项目遵循的风格指南;它给出了一个高度可读,视觉友好的编码风格。每个 Python 开发者应该阅读一下;这里是为你提取出来的最重要的要点: - 使用 4 个空格的缩进,不要使用制表符。 4 个空格是小缩进(允许更深的嵌套)和大缩进(易于阅读)之间很好的折衷。制表符会引起混乱,最好弃用。 - 折行以确保其不会超过 79 个字符。 这有助于小显示器用户阅读,也可以让大显示器能并排显示几个代码文件。 - 使用空行分隔函数和类,以及函数内的大块代码。 - 如果可能,注释独占一行。 - 使用 docstrings。 - 运算符周围和逗号后面使用空格,但是括号里侧不加空格: a=f(1,2)+g(3,4)。 - 命名您的类和函数一致 ;惯例是使用驼峰命名法的类和使用lower_case_with_underscores 的函数和方法 。始终使用self作为方法的第一个参数的名称(关于类和方法的更多信息请参见[*初识类*](#))。 - 如果希望你的代码在国际化环境中使用,不要使用奇特的编码。简单的 ASCII 在任何情况下永远工作得最好。 脚注 | [[1]](#) | 实际上,更好的描述是*通过对象的引用调用*,因为如果传递的是一个可变的对象,那么调用者可以看到被调用者对它所做的任何改变(插入到一个列表中元素)。 | |-----|-----|