4.深入python流程控制
除了前面介绍的while语句,python还从其他语言借鉴了一些流程控制功能,并有所改变。
4.1 if语句
如下示例:
可能会有零到多个elif部分,else是可选的。关键字”elif”是”else if”的缩写,这个可以有效地避免过深的缩进。if...elif...elif...序列用于替代其它语言中的switch或case语句
4.2for语句
Python中的for语句和C或java中的略有不同。通常的循环可能会依据一个等差数值步进过程,或有用户来定义迭代步骤和中止条件(C),python的for语句依据任意序列(链表或字符串)中的子项,按它们在序列中的顺序来进行迭代。例如(没有暗指):
在迭代过程中修改迭代序列不安全(只有在使用链表这样的可变序列是才会有这样的情况)。如果你想要修改你迭代它的复本。使用切割标识就可以很方便的做到这一点:
4.3range()函数
如果你需要一个数值序列,内置函数range()会很方便,它生成一个等差级数链表:
range(5)生成了一个包含5个值的链表,它用链表的索引值填充了这个长度为5的列表,所生成的链表中不包括范围中的结束值。也可以让range()操作从另一个数值开始,或者可以指定一个不同的步进值(甚至是负数,有时这也被称为”步长”):
需要迭代链表索引的话,如下所示结合使用range()和len()
不过,这种场合可以方便地使用enumerate(),请参见循环技巧
如果你只是打印一个序列的话会发生奇怪的事情:
在不同方面range()函数返回的对象表现为它是一个列表,但事实上它并不是。当你迭代它时,它是一个能够像期望的序列返回连续项的对象;但为了节省空间,它并不真正构造列表。
我们称此类对象是可迭代的,即合适作为那些期望从某些东西中获得连续项直到结束的函数或结构的一个目标(参数)。我们已经见过的for语句就是这样一个迭代器。list()函数是另外一个(迭代器),它可从迭代(对象)中创建列表:
稍后我们会看到更多返回迭代(对象)和以可迭代(对象)作为参数的函数
4.4break和continue语句,以及循环中的else子句
break语句和C中的类似,用于跳出最近的一级for或while循环
循环可以有一个else子句,他在循环迭代完整个列表(对于for)或执行条件为false(对于while)时执行,但循环被break中止的情况下不会执行。以下搜索素数的示例程序演示了这个子句:
(Yes,这是正确的代码。看仔细:else语句是属于for循环之中,不是if语句)
与循环一起使用时,else子句与try语句的else子句比与if语句的具有更多的共同点:try语句的else子句在未出现异常时运行。更多关于try语句和异常的内容,请参见异常处理。
continue语句是从C中借鉴来的,它表示循环继续执行下一次迭代:
4.5pass语句
pass语句什么也不做。它用于那些语法上必须要有什么语句,但程序什么也不做的场合,例如:
另一方面pass可以在创建代码时用来做函数或控制体的占位符。可以让你在更抽象的级别上思考。Pass可以默默的被忽视:
4.6定义函数
我们可以创建一个用来生成指定边界的斐波那契数列的函数:
关键字def引入了一个函数定义。在其后必须跟有函数名和包括形式参数的圆括号。函数体语句从下一行开始,必须是缩进的。
函数体的第一行语句可以是可选的字符串文本,这个字符串是函数的文档字符串,或者称为docstring。(更多关于docstrings的信息请参考文档字符串)有些工具通过docstrings自动生成在线的或可打印的文档,或者让用户通过代码交互浏览;在你的代码中包含docstrings是一个好的实践,让它成为习惯吧。
函数调用会为函数局部变量生成一个新的符号表。确切得说,所有函数中的变量赋值都是将值存储在局部符号表。变量引用首先在局部符号表中查找,然后是全局符号表,最后是内置名字表。因此全局变量不能在函数中直接赋值(除非用global语句命名),尽管它们可以被引用。
函数引用的实际参数在函数调用时引入局部符号表,因此实参总是传值调用(这里的值总是一个对象引用,而不是该对象的值)。一个函数被另一个函数调用时,一个新的局部符号表在调用过程中被创建
一个函数定义会在当前符号表内引入函数名。函数名指代的值(即函数体)有一个被python解释器认定为用户自定义函数的类型。这个值可以赋予其他名字(即变量名),然后它也可以被当做函数使用。这可以作为通用的重命名机制。
如果你使用过其他语言,你可能会反对说:fib不是一个函数,而是一个方法,因为他并不返回任何值。事实上,没有return语句的函数确实会返回一个值,虽然是一个相当令人厌烦的值(值None)。这个值被称为None(这是一个内建名称)。如果None值是唯一被书写的值,那么在写的时候通常会被解释器忽略(即不输出任何内容)。如果你确实想看到这个值的输出内容,请使用print()函数:
定义一个返回斐波那契数列数字列表的函数,而不是打印他,是很简单的:
和以前一样,这个例子演示了一些新的python功能
-return语句从函数中返回一个值,不带表达式的return返回None
语句result.append(b)称为链表对象result的一个方法。方法是一个”属于”某个对象的函数,它被命名为obj.methodename,这里的obj是某个对象(可能是一个表达式),methodename是某个在该对象类型定义中的方法的命名。
不同的类型定义不同的方法。不同类型可能有同样名字的方法,但不会混淆。(当你定义自己的对象类型和方法时,可能会出现这种情况,class的定义方法详见类)。示例中演示的append()方法由链表对象定义,它向链表中加入一个新元素。在示例中它等同于result=result+[a],不过效率更高。
4.7深入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关键字。它测试序列中是否包含某个确定的值
默认值在函数定义作用域被解析,如下所示:
输出为5,值得思考?
重要警告:
默认值只被赋值一次。这使得当默认值是可变对象时会有所不同,比如列表,字典或者大多数类的示例。例如,下面的函数在后续调用过程中会累积(前面)传给它的参数:
如果你不想让默认值在后续调用中累积,你可以像下面一样定义函数:
4.7.2关键字参数
函数可以通过关键字参数的形式来调用,形如keyword=value.例如,以下的函数
def car(name,volume=4,engine=‘China‘,velocity=90,color="red"): print("The car name:",name) print("The car engine:",engine) print("The car volume:",volume) print("The car color:",color)
接受一个必选参数(name)以及四个可选参数(volume,action,velocity和color)。可以用一下的任一方法调用:
car("BMW") The car name: BMW The car engine: China The car volume: 4 The car color: red
不过一下几种调用时无效的:
在函数调用中,关键字的参数必须跟随在位置参数的后面。传递的所有关键字参数必须与函数接受的某个参数相匹配,它们的顺序并不重要。这也包括非可选参数(例如:car(name=”BMW”)也是有效的)。任何参数都不可以多次赋值。
引入一个形如**name的参数时,它接收一个字典(参见 Mapping Types--dict),该字典包含了所有未出现在形式参数列表中的关键字参数。这里可能还会组合使用一个形如*name(下小节详细介绍)的形式参数,它接收一个元组(下一节中会详细介绍),包含了所有没有出现在形式参数列表中的参数值(*name必须在**namez之前出现)。例如,我们这样定义一个函数:
def chessshop(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])
它可以像这样调用:
chessshop("Limburger","It‘s very runny,sir", "It‘s really very,VERY ruuny,sir.", shopkeeper="Michael Pain", client="John Clease", 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 ruuny,sir. ---------------------------------------- client : John Clease shopkeeper : Michael Pain sketch : Cheese Shop Sketch
意在打印关键字参数之前,通过对关键字字典key()方法的结果进行排序,生成了关键字参数名的列表;如果不这样做,打印出来的参数的顺序是未定义的
4.7.3可变参数列表
最后,一个最不常用的选择是可以让函数调用可变个数的参数。这些参数被包装进一个元组(参见元组和序列)。在这些可变个数的参数之前,可以有零到多个普通的参数:
通常,这些可变参数是参数列表中的最后一个,因为它们将把所有的剩余输入参数传递给函数。任何出现在*args后的参数是关键字参数,这意味着他们只能被用作关键字,而不是位置参数。
4.7.4参数列表的分拆
另外一种相反的情况:当你要传递的参数已经是一个列表,但要调用的函数却接受分开一个个的参数值。这时候你要把已有的列表拆开来。例如内置函数range()需要独立的start,stop参数。你可以在调用函数时加一个*操作符自动把参数列表拆开:
以同样的方式,可以使用**操作符分拆关键字参数为字典
4.7.5 lambda 形式
出于实际需要,有几种通常在函数式编程语言例如Lisp中出现的功能加入到了python。通过lambda关键字,可以创建短小的匿名函数。这里有一个函数返回它的两个参数的和:lambda a,b:a+b。Lambda形式可以用于任何需要的函数对象。出于语法限制,它们只能有一个单独的表达式。语义上讲,它们只是普通函数定义中的一个语法技巧。类似于嵌套函数定义,lambda形式可以从外部作用域引用变量:
上面的示例使用lambda表达式返回一个函数。另一个用途是将一个小函数作为参数传递:
4.7.6文档字符串
这里介绍文档字符串的概念和格式
第一行应该是关于对象用途的简介。简短起见,不用明确的陈述对象名或类型,因为他们可以从别的途径了解到(除非这个名字碰巧就是描述这个函数操作的动词)。这一行应该大写字母开头,以句号结尾
如果文档字符串有多行,第二行应该空出来,与接下来的详细描述明确分隔。接下来的文档应该有一或多段描述对象的调用约定,边界效应等
4.7.7函数注解
函数注解是关于用户自定义的函数的完全可选的,随意的元数据信息。无论python本身或者标准库中都没有使用函数注解:本节只是描述了语法。第三方的项目是自由地为文档,类型检查,以及其它用途选择函数注解
注解是以字典形式存储在函数的__annotations__属性中,对函数的其他部分没有任何影响。参数注解(parameter annotations)是定义在参数名称的冒号后面,紧随着一个用来表示注解的值得表达式。返回注释(Return annotations)是定义在一个->后面,紧随着一个表达式,在冒号与->之间。下面的示例包含一个位置参数,一个关键字参数,和没有意义的返回注释:
4.8插曲:编码风格
*使用4空格缩进,而非Tab。在小缩进(可以嵌套更深)和大缩进(更易读)之间,4空格是一个很好的折中
*使用空行分隔函数和类,以及函数中的大块代码
*使用文档字符串
*统一函数和类命名。推荐类名用驼峰命名,函数和方法名用小写和下划线。总是用self作为方法得第一个参数(关于类和方法的知识详见初始类)
补充:
实际上,引用对象调用描述的更为准确。如果传入一个可变对象,调用者会看到调用操作带来的任何变化