Python教程之三-----深入流程控制

来源:互联网 发布:深圳程序员猝死 编辑:程序博客网 时间:2024/06/05 02:22

        除了刚才介绍的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
可以是0或者更多的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 3window 6defenestrate 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 range()函数

   如果你确实需要迭代一个数字序列,那么内置函数range()将会使这变得很容易。它会生成等差数列。
>>> for i in range(5):...     print(i)...01234
给定的末端点不是生成的序列的一部分;range(10)产生一个长度为10的序列,拥有10个值,以及对应的索引值。也可以让range函数从另外一个数字开始,或者指定一个不同的增长量(甚至是负数;有时候也可以叫做步数'step'):
range(5, 10)   5 through 9range(0, 10, 3)   0, 3, 6, 9range(-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 Mary1 had2 a3 little4 lamb
然而,在大多数情况下,使用enumerate()函数是很方便的。
如果你打印一个range将会发生一个神奇的事:
>>> 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 number3 is a prime number4 equals 2 * 25 is a prime number6 equals 2 * 37 is a prime number8 equals 2 * 49 equals 3 * 3
(是的,这就是正确的代码。请看仔细,else子句属于for循环,而不是if语句)

当使用循环时,else子句与try语句中的有更多的相似性,比if语句中的要多:try语句的else子句在没有异常的情况下运行,一个循环的else子句在没有break的情况下运行。

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 2Found a number 3Found an even number 4Found a number 5Found an even number 6Found a number 7Found an even number 8Found a number 9

4.5 pass语句

pass语句什么都不做。当在语法上需要一个语句,但是程序不需要动作的时候,就可以使用它。例如:

>>> while True:...     pass  # Busy-wait for keyboard interrupt (Ctrl+C)...

这个通常用来创建最小的类:

>>> class MyEmptyClass:...     pass...

另一个使用pass语句的情况是当你在编写一个新的代码时作为一个函数或条件体充当补位符,允许你在一个更抽象的层次上面思考。pass语句被忽略了。


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是一个好的方法,因此要养成一个习惯。


函数的执行为函数的局部变量引入了一个新的符号表。更准确的说,函数中说有变量的赋值都储存在本地符号表中;然而变量引用首先查找本地符号表,然后查找封闭函数的本地符号表,然后是全局符号表,最后是内置名称表。因此,全局变量不能直接在函数(除非是一个全局的语句)中赋值,尽管可以被引用。


函数调用的实际参数是在函数调用时在其本地符号表中引入的。因此,参数通过值调用传递(值始终是一个对象的引用,而不是对象的值)。当一个函数调用另一个函数时,将会创建一个新的本地符号表。


函数定义在当前符号表中引入函数名。函数名的值被解释器识别为一个用户定义的函数类型。这个值能被赋值给另外一个名称,那个名称同样也能当成函数使用。这是一个通用的重命名机制:

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

用其他语言的经验来说,你可能法对fib不是一个函数而是一个过程因为它并没有返回一个值。事实上,即使函数没有返回语句但它却是有返回一个值,尽管是一个相当乏味的值。这个值称为None(这是一个内置名称)。如果是唯一一个被写的值,那么解释器通常不会禁止:

>>> 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]
123

这个例子,和以前一样,演示了一些新的Python特性:

  • return语句从一个函数中返回一个值。没有表达式参数返回的时候返回None。函数的末尾,同样也返回None。
  • 语句result.append(a)调用一个列表对象result的方法。方法是属于一个对象的函数,命名为obj。methodname,其中obj是某些对象(也可能是一个表达式),并且methodname是由对象类型定义的方法的名称。不同的类型定义不同的方法。不同的类型的方法可能会有同样的名字而不会引起歧义。(可以使用classes来定义你自己的对象类型和方法)上例中的append()方法是为列表对象定义的;它在列表的结尾添加一个新的元素。它等价于result = result + [a],但更效率。

4.7 更多关于定义函数

定义一个可变数量参数的函数也是可以的。总共有3个形式,可以组合使用。

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('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 = 5def f(arg=i):    print(arg)i = 6f()
将会打印5

重要提示:默认值只会评估一次。当默认值是一个可变的对象例如列表,字典,或者大部分类的实例时将会有不同。例如,下面的函数在随后的调用中会积累参数:
def f(a, L=[]):    L.append(a)    return Lprint(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, end=' ')    print("if you put", voltage, "volts through it.")    print("-- Lovely plumage, the", type)    print("-- It's", state, "!")
接受一个必须的参数(voltage)和3个可选参数(state,action,和type)。这个函数能按以下任意一种方式调用:
parrot(1000)                                          # 1 positional argumentparrot(voltage=1000)                                  # 1 keyword argumentparrot(voltage=1000000, action='VOOOOOM')             # 2 keyword argumentsparrot(action='VOOOOOM', voltage=1000000)             # 2 keyword argumentsparrot('a million', 'bereft of life', 'jump')         # 3 positional argumentsparrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword
但是下列的调用将是无效的:
parrot()                     # required argument missingparrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argumentparrot(110, voltage=220)     # duplicate value for the same argumentparrot(actor='John Cleese')  # unknown keyword argument
在一个函数调用中,关键字参数后面必须借着可选的参数。所有的关键字参数传递必须匹配函数可接受的某哦一个参数(e.g. actor对于函数parrot来说不是一个有效的参数),并且他们的顺序并不重要。这同样包括非可选参数(e.g.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'
123
当一个最终形式为**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)    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 LimburgerIt's very runny, sir.It's really very, VERY runny, sir.----------------------------------------shopkeeper : Michael Palinclient : John Cleesesketch : Cheese Shop Sketch
注意关键字参数打印的顺序保证将与他们在函数调用中提供的顺序匹配。

4.7.3 可变参数列表

最后,最少使用的选项是指定一个函数可以拥有可变数量的参数。这些参数可以是元祖。在这些可变参数之前,可能出现0个或者更多的正常参数。

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表达式

可以使用lambda关键字来创建一些小的匿名函数。这个函数返回它2个参数: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解释器不会从多行的字符串中去掉缩进,所以如果想要这么做的话,处理文档的工具就必须去掉缩进。以下约定可以达成。紧接第一行的非空行字符串决定了整个文档字符串的缩进量。(我们不能用第一行,是因为它和字符串的引号相邻,所以在字符串中它的缩进不明显)然后从字符串的所有行去除相同的空格。没有缩进的行不应该出现,如果出现了,那么零头的行的空格也应该去掉。使用tabs(一般是8个空格)后最好做一些检查。


以下是一个多行docstring的例子:

>>> 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'
123

4.8  插曲:编程风格

既然你即将写更长的,跟复杂的Python代码段,是时候来讨论下编程风格了。大多数语言可以以不同的方式来编写(或者更简洁,格式化);一些比另一些更具有可读性。让别人更容易读懂你的代码始终是一个好的想法,采用一个好的编程风格将会对那有巨大的帮组


在Python中,PEP 8已经成为大多数项目遵循的风格指导方法;使用它可以写出一个非常具有可读性已经养眼的代码。每一个Python开发者都应该找个时间阅读以下;下面是萃取的最重要的几点:

  • 使用4空格的缩进,不要tab。                                                                        4个空格是在小的缩进(允许更深的嵌套深度)和大的缩进(更容易阅读)之间的一个折中取值。tabs容易引起困惑,最好不要使用
  • 每一行最好不要超过79个字符。                                                                                                                               这将会对一些小的显示设备有益,并且大的设备可以并排显示多分代码文件
  • 使用空行来分隔函数和类,以及函数体内大的代码块
  • 如果可以的话,每一行最好有注释
  • 使用docstrings
  • 在逗号后和操作符前后使用空格,但不是在括弧内:a = f(1,2) + g(3,4)
  • 连贯的命名你的类和函数;约定是类使用 骆驼拼写法 ,函数和方法使用带下划线的小写字母。总是使用self作为第一个方法参数的名字。
  • 如果你的代码将在国际环境里使用,请不要使用花俏的编码。Python默认是UTF-8,或者甚至是在任何情况下平实的ASCII
  • 同样的,即使是只有极少的机会会有不同语言的人来阅读或维护代码,请不要使用非ASCII码字符的标识符。