专栏首页Python爬虫实战Python指南:控制结构与函数

Python指南:控制结构与函数

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

控制结构与函数

1、控制结构

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

1.1 条件分支

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部分。

1.2 循环

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

1.2.1 while循环

语法格式:

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

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

1.2.2 for循环

语法格式:

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

2、异常处理

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

2.1 捕获异常

异常的捕获是使用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能帮我们始终关闭文件。

2.2 产生异常

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

raise exception(args)

raise exception(args) from original_exception

raise

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

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

2.3 自定义异常

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

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

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库中一个函数为例:

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 参数与参数拆分

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

# 参数拆分
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

3.3 存取全局范围的变量

我们经常在程序中设置一些全局的常量,这是合理的,有时候也会定义一些全局的变量,虽然这种做法不好。在函数中要对全局变量进行读取或修改,需要在前面添加 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变量。

3.4 Lambda函数

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来返回值,表达式本身的结果就是返回值。

匿名函数优点:

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

3.5 断言

为了避免无效数据对程序的影响,我们可以声明前提和后果,使用 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

推荐阅读:

  • Python指南:Python的8个关键要素
  • Python指南:数据类型
  • Python指南:组合数据类型----

本文分享自微信公众号 - C与Python实战(CPythonPractice),作者:C与Python实战

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-04-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Python指南:高级程序设计之过程型程序设计进阶

    Python 中,函数本身是一种对象,函数名就是对函数的对象引用。如果我们写一个函数名,其后面没有小括号,Python 会知道我们是将其当做对象引用。

    王强
  • numpy基础操作快速入门

    由于numpy不是python自带库,需要自己下载安装(如果用的是Anaconda,则不需要再去下载numpy库,因为其自带python环境以及许多第三方pyt...

    王强
  • ID生成算法-雪花算法介绍及实现

    SnowFlake 算法生成的 ID 是一个 64 位的整数,它的结构如下图所示:

    王强
  • Python基础之(七)函数

    在Python中,规定了一种定义函数的格式,下面的举例就是一个函数,以这个函数为例来说明定义函数的格式和调用函数的方法。

    py3study
  • Python之路【第八篇】:Python

    在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码会越来越长,越来越不容易维护。

    py3study
  • JavaScript 模式》读书笔记(4)— 函数1

      从这篇开始,我们会用很长的章节来讨论函数,这个JavaScript中最重要,也是最基本的技能。本章中,我们会区分函数表达式与函数声明,并且还会学习到局部作用...

    zaking
  • R语言基础教程——第六章:函数

    一个函数是组合在一起以执行特定任务的一组语句。R具有大量内置函数,当然用户也可以创建自己的功能。

    DoubleHelix
  • 支持向量机入门简介

    我们会通过分享有用的图书馆和资源而不是用复杂的数学知识来带你入门 SVM 。

    人工智能资讯小编
  • 支持向量机简介

    在Statsbot团队发布关于时间序列异常检测的帖子之后,许多读者要求我们向他们介绍支持向量机的方法。现在是向您介绍SVM(支持向量机)的时候了,而不用您辛苦的...

    Lethe丶L
  • 一套使用注入和Hook技术托管入口函数的方案

            工作中,我们可能会经常使用开源项目解决一些领域中的问题。这种“拿来主义”是一种“专业人干专业事”的思想,非常实用。(转载请指明出于breakso...

    方亮

扫码关注云+社区

领取腾讯云代金券