前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python指南:控制结构与函数

Python指南:控制结构与函数

作者头像
王强
发布2018-08-09 17:32:05
9710
发布2018-08-09 17:32:05
举报
文章被收录于专栏:Python爬虫实战Python爬虫实战

本章讲解Python的控制结构:分支与循环,并介绍异常处理和自定义函数相关知识。

控制结构与函数

1、控制结构

Python通过if语句实现了分支,通过while语句与for…in语句实现了循环,还有一种通过if实现的条件表达式(类似于C语言的三目运算符)。

1.1 条件分支

Python条件分支语句的最通常语法如下:

代码语言:javascript
复制
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。举个例子:

代码语言:javascript
复制
x = (1 if True else 0)
print(x)

[out]
1

注意圆括号的使用,如果不使用圆括号,我们可能掉入一些陷阱,看下面两个代码的区别:

代码语言:javascript
复制
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部分。

1.2 循环

Python提供了两种循环方式:while和for…in。

1.2.1 while循环

语法格式:

代码语言:javascript
复制
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。

代码语言:javascript
复制
# 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

由输出结果得知,我们想要的效果已经实现了。

1.2.2 for循环

语法格式:

代码语言:javascript
复制
for expression in iterable:
    for_suit
else:
    else_suit

else分支是可选的。如果在for_suite内执行了continue语句,控制流立即跳转到循环起始处,并开始下一次迭代。

下面用for…in循环实现上述list_index():

代码语言:javascript
复制
# 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

2、异常处理

Python通过产生异常来指明发生错误或异常条件。

2.1 捕获异常

异常的捕获是使用try…except块实现的,其语法格式如下:

代码语言:javascript
复制
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()函数:

代码语言:javascript
复制
# 异常版本
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能帮我们始终关闭文件。

2.2 产生异常

我们可以创建自己的异常,以产生我们所需要的异常并对其进行处理。产生异常的语法如下:

raise exception(args)

raise exception(args) from original_exception

raise

使用第一种语法是,指定的异常应该是内置的异常或者继承自Exception的自定义异常。如果给定一些文本作为该异常的参数,那么在捕捉到该异常并打印时,这些文本应该为输出信息。

使用第二种语法,也就是没有指定异常时,raise将重新产生当前活跃的异常,如果当前没有,就会产生一个TypeError

2.3 自定义异常

自定义异常时自定义的数据类型(类)。创建自定义异常的语法如下:

class exceptionName(baseException):pass

其基类应该为Exception类或继承自Exception的类。

自定义异常的一个用途是跳出深层嵌套循环

下面举一个简单的自定义异常例子:

代码语言:javascript
复制
# 自定义异常
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

3、自定义函数

函数可用于将相关功能打包并参数化。在Python中,可以创建4中函数:全局函数局部函数lambda函数方法

  • 全局函数可以由创建该函数的同一模块(同一.py文件)中的任意代码存取。
  • 局部函数(也称为嵌套函数)定义在其他函数之内,只对对其进行定义的函数时可见的。
  • Lambda函数是表达式,因此可以在需要使用的地方创建。
  • 方法是与特定数据类型关联的函数,并且只能与数据类型关联在一起使用。

函数的参数可以指定默认值,比如def add(a, b=1)。需要注意的是不允许在没有默认值的参数后面跟随默认值,比如def bad(a, b=1, c)

3.1 名称与Docstrings

对于函数或变量的名称,有一些可以考虑的经验如下:

  • 对常量使用UPPERCASE,对类(包括异常)使用TitleCase,对GUI函数与方法使用camel-Case,对其他对象使用lowercase或lowercase_with_underscores。
  • 对所有名称,避免使用缩略。
  • 函数名与方法名应该可以表明其行为或返回值。

我们可以为任何函数添加文档信息,docstring可以是简单地添加在def行之后、函数代码开始之前的字符串。以下举requests库中一个函数为例:

代码语言:javascript
复制
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的第一行知识一个简短的描述,之后是一个空白行,再之后跟随的是完整的描述信息,如果是交互式输入再执行的程序,还会给出一些实例。

3.2 参数与参数拆分

前面章节中讲过,我们可以使用序列拆分操作符(*)来提供位置参数。我们也可以在函数参数列表中使用序列拆分操作符,在创建使用可变数量的位置参数的函数时,这种方法是有效的。

代码语言:javascript
复制
# 参数拆分
def product(*args):
    print(type(args))
    print(args)
product(1, 'love', 2)
[out]
<class 'tuple'>
(1, 'love', 2)

由输出可以看出,在函数内部参数args的类型为元组,其项数随着给定的位置参数个数的变化而变化。

我们可以将关键字参数跟随在位置参数后面,例如:

代码语言:javascript
复制
# 关键字参数跟在位置参数后面
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

将*本身作为参数也是可以的,用于表明在*后不应该在出现位置参数,但关键字参数是允许的。

代码语言:javascript
复制
# *单独传入
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

就像我们可以对序列进行拆分来产生函数的位置参数一样,我们也可以使用映射拆分操作符(**)来对映射进行拆分。

代码语言:javascript
复制
# 参数序列拆分
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

3.3 存取全局范围的变量

我们经常在程序中设置一些全局的常量,这是合理的,有时候也会定义一些全局的变量,虽然这种做法不好。在函数中要对全局变量进行读取或修改,需要在前面添加 global 关键字,举个例子:

代码语言:javascript
复制
# 全局变量的存取
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变量。

3.4 Lambda函数

Lambda函数的语法格式:

lambda parameters: expression

parameters 是可选的,如果提供,通常是逗号分隔的变量名形式,也就是位置参数。expression不能包含分支或循环,也不能包含return(或yeild)语句,lambda表达式的结果是一个匿名函数。所谓匿名,就是不再使用def语句这样的标准形式定义一个函数。

我们已知三角形的底边长为b,高为h,之前我们要写求面积的函数会像这样写:

代码语言:javascript
复制
def area(b, h):
    return 0.5 * b * h

那么用匿名函数如何写呢?

代码语言:javascript
复制
area = lambda b,h: 0.5 * b * h

匿名函数不需要return来返回值,表达式本身的结果就是返回值。

匿名函数优点:

  • 使用Python写一些脚本时,使用lambda可以省去定义函数的过程,让代码更加精简。
  • 对于一些抽象的,不会被别的地方再重复使用的函数,有时候函数起个名字也是个难题,使用lambda不需要考虑命名的问题
  • 使用lambda在某些时候然后代码更容易理解

3.5 断言

为了避免无效数据对程序的影响,我们可以声明前提和后果,使用 assert 语句可以来实现该功能,其语法格式为:

assert boolean_expression, optional_expression

如果 boolean_expression 评价为 False 就产生一个 AssertionError 异常。如果给定了可选的 optional_expression ,就将其用作AssertionError异常的参数。

注意:断言是为开发者设计的,而不是面向终端用户的

有一个product函数,要求所有的参数为非0值,并将使用参数0进行的调用视为编码错误,下面给出两种等价版本:

代码语言:javascript
复制
# 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并向错误流(通常为控制台)写入错误信息:

代码语言:javascript
复制
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

推荐阅读:

  • Python指南:Python的8个关键要素
  • Python指南:数据类型
  • Python指南:组合数据类型----
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 C与Python实战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、控制结构
    • 1.1 条件分支
      • 1.2 循环
        • 1.2.1 while循环
        • 1.2.2 for循环
    • 2、异常处理
      • 2.1 捕获异常
        • 2.2 产生异常
          • 2.3 自定义异常
          • 3、自定义函数
            • 3.1 名称与Docstrings
              • 3.2 参数与参数拆分
                • 3.3 存取全局范围的变量
                  • 3.4 Lambda函数
                    • 3.5 断言
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档