本章讲解Python的控制结构:分支与循环,并介绍异常处理和自定义函数相关知识。
控制结构与函数
Python通过if语句实现了分支,通过while语句与for…in语句实现了循环,还有一种通过if实现的条件表达式(类似于C语言的三目运算符)。
Python条件分支语句的最通常语法如下:
if boolean_expression1:
suite1
elif boolean_expression2:
suite2
...
elif boolean_expressionN:
suiteN
else:
else_suit
可以有0个或多个elif语句,最后一个else语句是可选的。如果在某个分支什么都不想做,可以使用pass作为该分支的suite。
用条件分支实现三目运算符:
expression1 if boolean_expression else expression2
如果boolean_expression为True,条件表达式为expression1,否则为expression2。举个例子:
x = (1 if True else 0)
print(x)
[out]
1
注意圆括号的使用,如果不使用圆括号,我们可能掉入一些陷阱,看下面两个代码的区别:
x = 10 + 5 if False else 0
print('first:', x)
x = 10 + (5 if False else 0)
print('secont:', x)
[out]
first: 0
secont: 10
从结果可以看出,如果不使用圆括号,Python将"10+5"看做条件表达式的expression1部分。
Python提供了两种循环方式:while和for…in。
语法格式:
while boolean_expression:
while_suit
else:
else_suit
else分支是可选的。如果boolean_expression为True,while_suite就会执行,否则循环终止。如果在while_suite内部执行了continue语句,就会跳转到循环起始处,并对boolean_expression的取值进行重新评估。
存在else分支的话,如果循环是正常终止的,else_suite就会执行。如果由于break语句、返回语句或由于发生异常导致跳出循环,else_suite不会执行。
让我们看一下else分支的实际使用。str.index()与list.index()返回给定字符串或数据想得索引位置,如果找不到则产生ValueError异常。现在我们改变一下策略:如果找不到数据项,返回-1。
# while...else...实例
def list_index(lst, target):
index = 0
while index < len(lst):
if lst[index] == target:
break
else:
index += 1
else:
index = -1
return index
# 测试
lst = [1, 2, 'cat', 'apple']
print('index of "apple":', list_index(lst, 'apple'))
print('index of 9:', list_index(lst, 9))
[out]
index of "apple": 3
index of 9: -1
由输出结果得知,我们想要的效果已经实现了。
语法格式:
for expression in iterable:
for_suit
else:
else_suit
else分支是可选的。如果在for_suite内执行了continue语句,控制流立即跳转到循环起始处,并开始下一次迭代。
下面用for…in循环实现上述list_index():
# for...in版本
def list_index2(lst, target):
for index, value in enumerate(lst):
if value == target:
break
else:
index = -1
return index
# 测试
lst = [1, 2, 'cat', 'apple']
print('index of "apple":', list_index(lst, 'apple'))
print('index of 9:', list_index(lst, 9))
[out]
index of "apple": 3
index of 9: -1
Python通过产生异常来指明发生错误或异常条件。
异常的捕获是使用try…except块实现的,其语法格式如下:
try:
try_suite
except exception_group1 as variable1:
except_suite1
...
except exception_groupN as variableB:
except_suiteN
else:
else_suite
finally:
finally_suite
至少要包含一个except块,else和finally块都是可选的。在try_suite正常执行完毕是,会执行else_suite——如果发生异常,就不会执行。如果存在一个finally块,则最后总会执行。
每个except分支的as variable
是可选的,如果使用,该变量就会包含发生的异常,并可以在异常块的suite中进行存取。
要与异常组进行匹配,异常必须与组中列出的异常类型(或其中某一个)一致,或者与组中列出的异常类型(或其中某一个)的子类,下列列出Python异常系统部分截图:
Python异常体系部分截图
我们使用异常来实现前面的list_index()函数:
# 异常版本
def list_index3(lst, target):
try:
index = lst.index(target)
except ValueError:
index = -1
return index
# 测试
lst = [1, 2, 'cat', 'apple']
print('index of "apple":', list_index(lst, 'apple'))
print('index of 9:', list_index(lst, 9))
[out]
index of "apple": 3
index of 9: -1
try…except…finally块的一种常见应用是处理文件错误,我们打开文件产生异常、处理过程产生异常或者正常处理完成后,无论如何我们都需要关闭文件,finally能帮我们始终关闭文件。
我们可以创建自己的异常,以产生我们所需要的异常并对其进行处理。产生异常的语法如下:
raise exception(args)
raise exception(args) from original_exception
raise
使用第一种语法是,指定的异常应该是内置的异常或者继承自Exception的自定义异常。如果给定一些文本作为该异常的参数,那么在捕捉到该异常并打印时,这些文本应该为输出信息。
使用第二种语法,也就是没有指定异常时,raise将重新产生当前活跃的异常,如果当前没有,就会产生一个TypeError。
自定义异常时自定义的数据类型(类)。创建自定义异常的语法如下:
class exceptionName(baseException):pass
其基类应该为Exception类或继承自Exception的类。
自定义异常的一个用途是跳出深层嵌套循环。
下面举一个简单的自定义异常例子:
# 自定义异常
class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
try:
raise MyError(10)
except MyError as e:
print('There is a MyError, value:', e.value)
[out]
There is a MyError, value: 10
函数可用于将相关功能打包并参数化。在Python中,可以创建4中函数:全局函数、局部函数、lambda函数、方法。
函数的参数可以指定默认值,比如def add(a, b=1)
。需要注意的是不允许在没有默认值的参数后面跟随默认值,比如def bad(a, b=1, c)
。
对于函数或变量的名称,有一些可以考虑的经验如下:
我们可以为任何函数添加文档信息,docstring可以是简单地添加在def行之后、函数代码开始之前的字符串。以下举requests库中一个函数为例:
def get(url, params=None, **kwargs):
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)
对函数文档而言,如果比函数本身还长,也并非不同寻常,常规的做法是,docstring的第一行知识一个简短的描述,之后是一个空白行,再之后跟随的是完整的描述信息,如果是交互式输入再执行的程序,还会给出一些实例。
前面章节中讲过,我们可以使用序列拆分操作符(*)来提供位置参数。我们也可以在函数参数列表中使用序列拆分操作符,在创建使用可变数量的位置参数的函数时,这种方法是有效的。
# 参数拆分
def product(*args):
print(type(args))
print(args)
product(1, 'love', 2)
[out]
<class 'tuple'>
(1, 'love', 2)
由输出可以看出,在函数内部参数args的类型为元组,其项数随着给定的位置参数个数的变化而变化。
我们可以将关键字参数跟随在位置参数后面,例如:
# 关键字参数跟在位置参数后面
def sum_of_powers(*args, power=1):
result = 0
for arg in args:
result += arg ** power
return result
print(sum_of_powers(1, 3, 5))
print(sum_of_powers(1, 3, 5, power=2))
[out]
9
35
将*本身作为参数也是可以的,用于表明在*后不应该在出现位置参数,但关键字参数是允许的。
# *单独传入
def heron(a, b, c, *, units='meters'):
s = (a + b + c)/2
return '{} {}'.format(s, units)
print(heron(23, 24, 15))
print(heron(23, 24, 15, units='inches'))
print(heron(23, 24, 15, 'inches'))
[out]
31.0 meters
31.0 inches
Traceback (most recent call last):
File "D:\Programming\Python\第四章 控制结构与函数.py", line 110, in <module>
print(heron(23, 24, 15, 'inches'))
TypeError: heron() takes 3 positional arguments but 4 were given
就像我们可以对序列进行拆分来产生函数的位置参数一样,我们也可以使用映射拆分操作符(**)来对映射进行拆分。
# 参数序列拆分
def print_dict(key='defkey', value='defvalue'):
string = 'key = {}, value = {}'.format(key, value)
return string
options = dict(key='hello', value='world')
print(print_dict(**options))
[out]
key = hello, value = world
我们经常在程序中设置一些全局的常量,这是合理的,有时候也会定义一些全局的变量,虽然这种做法不好。在函数中要对全局变量进行读取或修改,需要在前面添加 global 关键字,举个例子:
# 全局变量的存取
Price = 8.9
def raise_price():
global Price
print('The original price is: ${}. '.format(Price))
Price += 1
print('The lastest price is: ${}. '.format(Price))
raise_price()
[out]
The original price is: $8.9.
The lastest price is: $9.9.
global 的作用是高职Python,Price 变量作用范围是全局的,对变量的赋值应该应用于全局变量,而不是创建一个同名的本地变量。如果不使用global语句,程序也可以运行,但是Python会在局部(函数)范围内查找,由于找不到就创建一个新的名为Price的局部变量,而不改变全局的Price变量。
Lambda函数的语法格式:
lambda parameters: expression
parameters 是可选的,如果提供,通常是逗号分隔的变量名形式,也就是位置参数。expression不能包含分支或循环,也不能包含return(或yeild)语句,lambda表达式的结果是一个匿名函数。所谓匿名,就是不再使用def语句这样的标准形式定义一个函数。
我们已知三角形的底边长为b,高为h,之前我们要写求面积的函数会像这样写:
def area(b, h):
return 0.5 * b * h
那么用匿名函数如何写呢?
area = lambda b,h: 0.5 * b * h
匿名函数不需要return来返回值,表达式本身的结果就是返回值。
匿名函数优点:
为了避免无效数据对程序的影响,我们可以声明前提和后果,使用 assert 语句可以来实现该功能,其语法格式为:
assert boolean_expression, optional_expression
如果 boolean_expression 评价为 False 就产生一个 AssertionError 异常。如果给定了可选的 optional_expression ,就将其用作AssertionError异常的参数。
注意:断言是为开发者设计的,而不是面向终端用户的。
有一个product函数,要求所有的参数为非0值,并将使用参数0进行的调用视为编码错误,下面给出两种等价版本:
# assert语句,版本1
def product1(*args):
assert all(args), '0 argument'
result = 1
for arg in args:
result *= arg
return result
# 版本2
def product2(*args):
result = 1
for arg in args:
result *= arg
assert result, '0 argument'
return result
版本1对每个调用检查所有的参数,版本2只对结果进行检查。如果某个参数为0,就会产生一个AssertionError并向错误流(通常为控制台)写入错误信息:
x = product1(0, 1, 2, 3, 4)
[out]
Traceback (most recent call last):
File "D:\xxx\第四章 控制结构与函数.py", line 160, in <module>
x = product1(0, 1, 2, 3, 4)
File "D:\xxx\第四章 控制结构与函数.py", line 144, in product1
assert all(args), '0 argument'
AssertionError: 0 argument
在程序准备就绪将要发布时,手动去除assert语句是低效的,我们可以告诉Python不执行assert语句:运行程序时,在命令行中指定 -O 选项。另一种方法是将环境变量 PYTHONOPTIMIZE 设置为 O 。
推荐阅读: