使用函数组织大型程序

Intermediate

This tutorial is from open-source community. Access the source code

简介

随着你的程序开始变得越来越大,你会希望将其组织起来。本节简要介绍函数和库模块。还将介绍使用异常进行错误处理。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 中级 级别的实验,完成率为 74%。获得了学习者 100% 的好评率。

自定义函数

对于你想要复用的代码,可以使用函数。以下是一个函数定义:

def sumcount(n):
    '''
    返回前 n 个整数的和
    '''
    total = 0
    while n > 0:
        total += n
        n -= 1
    return total

要调用一个函数:

a = sumcount(100)

函数是执行某些任务并返回结果的一系列语句。需要使用 return 关键字来明确指定函数的返回值。

库函数

Python 附带了一个庞大的标准库。可以使用 import 来访问库模块。例如:

import math
x = math.sqrt(10)

import urllib.request
u = urllib.request.urlopen('http://www.python.org/')
data = u.read()

我们将在后面更详细地介绍库和模块。

错误与异常

函数将错误报告为异常。异常会导致函数中止,如果未处理,可能会使整个程序停止。

在你的 Python 交互式解释器中试试这个。

>>> int('N/A')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'N/A'
>>>

出于调试目的,错误消息会描述发生了什么、错误发生在哪里,以及一个回溯信息,显示导致失败的其他函数调用。

捕获和处理异常

异常可以被捕获和处理。

要进行捕获,可以使用 try - except 语句。

for line in file:
    fields = line.split(',')
    try:
        shares = int(fields[1])
    except ValueError:
        print("Couldn't parse", line)
  ...

ValueError 这个名称必须与你试图捕获的错误类型相匹配。

通常很难预先确切知道根据所执行的操作可能会发生哪些类型的错误。不管是好是坏,异常处理通常是在程序意外崩溃之后才添加的(即“哦,我们忘了捕获那个错误。我们应该处理它!”)。

引发异常

要引发异常,可使用 raise 语句。

raise RuntimeError('What a kerfuffle')

这将导致程序因异常回溯而中止。除非被 try-except 块捕获。

% python3 foo.py
Traceback (most recent call last):
  File "foo.py", line 21, in <module>
    raise RuntimeError("What a kerfuffle")
RuntimeError: What a kerfuffle

练习 1.29:定义一个函数

尝试定义一个简单的函数:

>>> def greeting(name):
        'Issues a greeting'
        print('Hello', name)

>>> greeting('Guido')
Hello Guido
>>> greeting('Paula')
Hello Paula
>>>

如果函数的第一行语句是一个字符串,那么它将作为文档。尝试输入诸如 help(greeting) 这样的命令来查看它的显示内容。

练习 1.30:将脚本转换为函数

把你在练习 1.27 中为pcost.py程序编写的代码,转换成一个名为portfolio_cost(filename)的函数。这个函数以文件名作为输入,读取该文件中的投资组合数据,并返回投资组合的总成本,以浮点数形式表示。

为了使用你的函数,修改你的程序,使其看起来像这样:

## pcost.py
def portfolio_cost(filename):
    """
    计算投资组合文件的总成本(股数*价格)
    """
    total_cost = 0.0

    with open(filename, "rt") as f:
        rows = f.readlines()
        headers = rows[0].strip().split(",")
        for row in rows[1:]:
            row_data = row.strip().split(",")
            nshares = int(row_data[1])
            price = float(row_data[2])
            total_cost += nshares * price

    return total_cost


import sys

if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = input("输入一个文件名:")

cost = portfolio_cost(filename)
print("总成本:", cost)

当你运行你的程序时,你应该会看到和之前一样的输出。运行程序之后,你还可以通过输入以下内容,在交互式环境中调用你的函数:

$ python3 -i pcost.py

这将允许你在交互式模式下调用你的函数。

>>> portfolio_cost('portfolio.csv')
44671.15
>>>

能够在交互式环境中试验你的代码,对于测试和调试很有用。

练习 1.31:错误处理

如果你在一个有缺失字段的文件上尝试使用你的函数,会发生什么?

>>> portfolio_cost('missing.csv')
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "pcost.py", line 11, in portfolio_cost
    nshares    = int(fields[1])
ValueError: invalid literal for int() with base 10: ''
>>>

在这个时候,你面临一个抉择。为了让程序正常运行,你可以通过删除坏行来清理原始输入文件,或者修改你的代码以某种方式处理这些坏行。

修改pcost.py程序,捕获异常,打印一条警告消息,然后继续处理文件的其余部分。

练习 1.32:使用库函数

Python 附带了一个包含许多有用函数的大型标准库。这里可能有用的一个库是csv模块。每当你需要处理 CSV 数据文件时,都应该使用它。下面是它的工作方式示例:

>>> import csv
>>> f = open('portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name','shares', 'price']
>>> for row in rows:
        print(row)

['AA', '100', '32.20']
['IBM', '50', '91.10']
['CAT', '150', '83.44']
['MSFT', '200', '51.23']
['GE', '95', '40.37']
['MSFT', '50', '65.10']
['IBM', '100', '70.44']
>>> f.close()
>>>

csv模块的一个优点是它处理了各种底层细节,比如引号和正确的逗号分隔。在上面的输出中,你会注意到它已经从第一列的名称中去掉了双引号。

修改你的pcost.py程序,使其使用csv模块进行解析,然后尝试运行之前的示例。

练习 1.33:从命令行读取

pcost.py程序中,输入文件的名称被硬编码到了代码中:

## pcost.py

def portfolio_cost(filename):
 ...
    ## 你的代码在这里
 ...

cost = portfolio_cost('portfolio.csv')
print('Total cost:', cost)

这对于学习和测试来说没问题,但在实际程序中你可能不会这样做。

相反,你可能会将文件名作为参数传递给脚本。尝试将程序的底部部分修改如下:

## pcost_1.33.py

import csv


def portfolio_cost(filename):
    """
    计算投资组合文件的总成本(股数*价格)
    """
    total_cost = 0.0

    with open(filename, "rt") as f:
        rows = csv.reader(f)
        headers = next(rows)  ## 跳过标题行
        for row in rows:
            if len(row) < 3:
                print("跳过无效行:", row)
                continue
            try:
                nshares = int(row[1])
                price = float(row[2])
                total_cost += nshares * price
            except (IndexError, ValueError):
                print("跳过无效行:", row)

    return total_cost

import sys


if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = 'portfolio.csv'

cost = portfolio_cost(filename)
print('Total cost:', cost)

sys.argv是一个列表,它包含命令行上传递的参数(如果有的话)。

要运行你的程序,你需要在终端中运行 Python。

例如,在 Unix 上的 bash 中:

$ python3 pcost.py portfolio.csv
Total cost: 44671.15
bash %

总结

恭喜你!你已经完成了函数实验。你可以在 LabEx 中练习更多实验来提升你的技能。