《Python 3程序开发指南(第2版•修订版)》——2.5 实例-阿里云开发者社区

开发者社区> 异步社区> 正文

《Python 3程序开发指南(第2版•修订版)》——2.5 实例

简介:
+关注继续查看

本节书摘来自异步社区《Python 3程序开发指南(第2版•修订版)》一书中的第2章,第2.5节,作者[英]Mark Summerfield,王弘博,孙传庆 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.5 实例

在这一节中,我们将根据本章以及前面一章中所学的知识,提供两个虽小但完整的程序,以助于巩固到此为止所学的Python知识。第一个程序有点偏数学化,但是非常小,大约35行代码。第二个程序是关于文本处理的,并且更具体,其中包含7个函数,大约80行代码。

2.5.1 quadratic.py

二次方程是指形如ax2 + bx + c = 0的方程,其中,a不为0描述的是抛物线。这一方程的根可以由公式screenshot 得出,其中,公式的b2-4ac部分称为判别式——如果为正值,那么该方程有两个实根;如果为0,那么该方程有一个实根;如果为负值,就有两个复数根。我们将编写一个程序,该程序接受用户输入的a、b、c值(b与c均可为0),之后计算并输出方程的根1。

首先我们看一个运行的实例,之后将讲解其代码。

quadratic.py
ax^2 + bx + c = 0
enter a: 2.5
enter b: 0
enter c: -7.25
2.5x^2 + 0.0x + -7.25 = 0 → x = 1.70293863659 or x = -1.70293863659

对于系数1.5、-3、6,其输出(有些数字经过处理)为:

1.5x^2 + -3.0x + 6.0 = 0 → x = (1+1.7320508j)或x = (1-1.7320508j)

上面的输出并不能满足我们的要求——比如,我们不希望使用+ -3.0x,而更希望直接使用- 3.0x,对于系数为0的情况,则不希望方程中还显示其对应的项。在练习中,你将有机会完善本程序中存在的这些不足。

现在我们开始阅读和讲解程序代码,代码是从3个导入语句开始的:

import cmath
import math
import sys

由于用于实数与复数的平方根函数是不同的,因此,浮点数学库与复数数学库都需要导入。由于需要使用sys.float_info.epsilon将浮点数与0进行比较,因此我们还要导入sys库。

我们还需要一个从用户处获取浮点数的函数:

def get_float(msg, allow_zero):
    x = None
    while x is None:
        try:
            x = float(input(msg))
            if not allow_zero and abs(x) < sys.float_info.epsilon:
                print("zero is not allowed")
                x = None
        except ValueError as err:
            print(err)
    return x

这一函数将进行循环,直至用户输入一个有效的浮点数(比如0.5、-9、21、4.92等)。如果allow_zero为True,那么也可以接受0。

定义了get_float()函数后,代码的其余部分将得以执行,我们将分3个部分讲解这部分代码,从用户交互部分开始。

print("ax\N{SUPERSCRIPT TWO} + bx + c = 0")
a = get_float("enter a: ", False)
b = get_float("enter b: ", True)
c = get_float("enter c: ", True)

由于定义了get_float()函数,使得获取方程系数a、b、c变得很简单。布尔型的第2个参数用于确定是否可以接受0。

x1 = None
x2 = None
discriminant = (b ** 2) - (4 * a * c)
if discriminant == 0:
    x1 = -(b / (2 * a))
else:
    if discriminant > 0:
        root = math.sqrt(discriminant)
    else: # discriminant < 0
        root = cmath.sqrt(discriminant)
    x1 = (-b + root) / (2 * a)
    x2 = (-b - root) / (2 * a)

上面的代码看起来与公式似乎有所不同,这是因为我们首先从计算判别式开始。如果判别式为0,就会知道该方程只有一个实数解,因此可以直接计算;否则,我们可以先计算判别式的实数平方根或复数平方根,并进而计算出方程的根。

equation = ("{0}x\N{SUPERSCRIPT TWO} + {1}x + {2} = 0"
            " \N{RIGHTWARDS ARROW} x = {3}").format(a, b, c, x1)
if x2 is not None:
    equation += " or x = {0}".format(x2)
print(equation)

由于对这一实例而言,Python对浮点数的默认支持已足够,因此我们没有进行任何其他格式化,但是对两个特殊的字符,我们使用了Unicode字符名。

使用位置参数(用其索引位置作为字段名)的更健壮的替代方式是使用locals()返回的字典,这也是本章前面看到过的一种技术。

equation = ("{a}x\N{SUPERSCRIPT TWO} + {b}x + {c} = 0"
           " \N{RIGHTWARDS ARROW} x = {x1}").format(**locals())

并且,如果使用的是Python 3.1,我们可以忽略字段名,而由Python使用传递给str.format()的位置参数生成字段。

equation = ("{}x\N{SUPERSCRIPT TWO} + {}x + {} = 0"
           " \N{RIGHTWARDS ARROW} x = {}").format(a, b, c, x1)

这是便利的,但并不像使用命名参数那样健壮,在需要使用格式规约时也没那么丰富多变。尽管如此,对很多简单的情况,这种语法既是容易的,也是有用的。

2.5.2 csv2html.py

一个常见的需求是:获取一个数据集,并将其使用HTML呈现。在这一小节中,我们将开发一个程序,该程序读入一个文件,该文件使用的是简单的CSV(逗号分隔值)格式,输出时则使用HTML表格,其中包含该文件的数据。Python本身带有一个功能强大而复杂的csv模块,可用于处理CSV格式与类似的数据格式——但这里我们将自己编写所有代码。

CSV格式每行一个记录,每个记录使用逗号分隔为多个字段。每个字段可以是字符串,也可以是数字。字符串必须使用单引号或双引号包含起来,数字不应该使用引号包含,除非其中包含逗号。在字符串内部使用逗号是允许的,但不能充当字段分隔符。我们假定第一条记录包含字段labels。我们将要产生的输出是HTML表格,其中的文本采用左对齐方式(在HTML中是默认的),数字则采用右对齐方式,每个记录一列,每个字段一个单元。

该程序必须可以输出HTML表格的开标签,之后读取每行数据,对每行数据,输出其对应的HTML列,并在末尾输出HTML表格的闭标签。对背景色,要求第一列(该列用于显示字段标号)为浅绿,数据列的背景色则在白色与浅黄色之间变换。要注意的是,我们必须确保特殊的HTML字符(“&”、“<”与“>”)必须经过正确的转义处理,并希望字符串经过适当处理。

下面给出的是一段样本数据:

"COUNTRY","2000","2001",2002,2003,2004
"ANTIGUA AND BARBUDA",0,0,0,0,0
"ARGENTINA",37,35,33,36,39
"BAHAMAS, THE",1,1,1,1,1
"BAHRAIN",5,6,6,6,6

假定样本数据存放在文件data/co2-sample.csv中,并使用命令csv2html.py < data/ co2-sample.csv > co2-sample.html,则文件co2-sample.html包含的内容类似于如下格式:

<table border='1'><tr bgcolor='lightgreen'>
<td>Country</td><td align='right'>2000</td><td align='right'>2001</td>
<td align='right'>2002</td><td align='right'>2003</td>
<td align='right'>2004</td></tr>
...
<tr bgcolor='lightyellow'><td>Argentina</td>
<td align='right'>37</td><td align='right'>35</td>
<td align='right'>33</td><td align='right'>36</td>
<td align='right'>39</td></tr>
...
</table>

我们对输出进行了稍许处理,并忽略了某些行(使用省略号表示)。我们使用了一种非常简单的HTML到HTML 4之间的过渡格式,并且没有使用类型表,图2-7展示了在Web浏览器中输出的情况。


screenshot

在了解了程序如何使用以及其功能之后,我们开始查阅程序的实现代码。该程序从导入sys模块开始,我们没有展示该行代码,也没有展示其他导入语句,除非是不同寻常的或授权的讨论。程序的最后一个语句是一个函数调用:

main()

虽然Python不像其他语言那样需要入口点,但是在Python程序中,创建一个称为main()的函数,并通过对该函数的调用来开始程序的实际处理流程也是非常常见的。由于没有哪一个函数可以在创建之前就被调用,因此我们必须确保在其依赖的函数创建之后再调用main()。函数在文件中出现的顺序(即函数的创建顺序)则无关紧要。

在csv2html.py程序中,我们调用的第一个函数是main(),其中依次调用了print_start()与print_line(),print_line()则调用了extract_ fields()与escape_html(),图2-8中展示了使用的程序结构。


screenshot

Python读入文件时,从顶部开始执行,这一实例也是如此,首先执行的是导入语句,之后创建了main()函数,再之后创建了其他函数,其顺序与文件中出现的顺序一致。在文件尾部调用main()函数时,main()函数要调用的所有函数(以及这些函数要调用的函数)都已经存在。执行过程与我们通常认为的一样,从对main()的调用开始。

我们将依次查看每个函数,从main()开始:

def main():
    maxwidth = 100
    print_start()
    count = 0
    while True:
        try:
            line = input()
            if count == 0:
                color = "lightgreen"
            elif count % 2:
                color = "white"
            else:
                color = "lightyellow"
            print_line(line, color, maxwidth)
            count += 1
        except EOFError:
            break
    print_end()

maxwidth变量用于限制每个cell中的字符数——如果某个字段大于这个值,我们将对其削减,并通过添加省略号来表明这一点。我们下面就开始查看print_start()、print_line()以及print_end()等函数,while循环对每行输入进行迭代处理——输入可以来自用户的键盘输入,但是我们更希望来自重定向文件。我们设置了想要使用的颜色,并调用print_line()将该行以HTML表格列的形式输出。

def print_start():
    print("<table border='1'>")
def print_end():
    print("</table>")

我们也可以不创建这两个函数,而只是将相关的print()函数调用放置在main()函数中。但是我们更愿意将这些功能逻辑分离出来,因为这会使程序更加灵活(即便在较小的程序中这并不重要)。

def print_line(line, color, maxwidth):
    print("<tr bgcolor='{0}'>".format(color))
    fields = extract_fields(line)
    for field in fields:
        if not field:
            print("<td></td>")
    else:
        number = field.replace(",", "")
        try:
            x = float(number)
            print("<td align='right'>{0:d}</td>".format(round(x)))
        except ValueError:
            field = field.title()
            field = field.replace(" And ", " and ")
            if len(field) <= maxwidth:
               field = escape_html(field)
            else:
               field = "{0} ...".format(
                      escape_html(field[:maxwidth]))
            print("<td>{0}</td>".format(field))
print("</tr>")

要注意的是,我们不能使用str.split(",")将每行分隔成不同的字段,因为在引号包含的字符串内也可能包含逗号。因此,我们将这一功能实现在extract_fields()函数中。对字段列表(作为字符串,但没有包围的引号),我们在其上进行迭代,并为每个字段创建一个表格单元。

如果某字段为空,就输出一个空cell。如果某个字段使用引号进行包含,那么可能是一个字符串,也可能是一个数字,使用引号包含数字的目的是允许数字内部使用逗号,比如,“1 566”。为此,我们生成字段的一个副本将其内部的逗号移除,并尝试将其转换为一个浮点数。如果转换成功,就输出一个右对齐的单元,其中的字段四舍五入为最近的一个整数,并以整数的形式输出;如果转换失败,就以字符串形式输出该字段。这里,我们使用str.title()来整理字母的大小写,并使用and替换And,以便纠正str.title()对其进行的不必要的更改。我们之后对任意特殊的HTML字符进行转义,或者打印其完整的字段,或者打印其maxwidth个字符,并加上省略号。一种更简单的使用内部替换字段的替代方案是使用字符串分片。这种方法的另一个优势是需要较少的输入操作。

def extract_fields(line):
    fields = []
    field = ""
    quote = None
    for c in line:
        if c in "\"'":
            if quote is None: # start of quoted string
                quote = c
            elif quote == c: # end of quoted string
                quote = None
            else:
                field += c # other quote inside quoted string
            continue
        if quote is None and c == ",": # end of a field
            fields.append(field)
            field = ""
        else:
        field += c            # accumulating a field
    if field:
        fields.append(field)  # adding the last field
    return fields

该函数逐个字符读入给定的行,累积成一个字段列表——每个字段都是一个不带引号包含的字符串。该函数可以处理不带引号的字段,也可以处理使用单引号或双引号的字段,并正确处理逗号与引号(双引号字符串中的单引号,单引号字符串中的双引号)。

def escape_html(text):
    text = text.replace("&", "&amp;")
    text = text.replace("<", "&lt;")
    text = text.replace(">", "&gt;")
    return text

这一函数直截了当地使用适当的HTML实体替换每个特殊的HTML字符。我们当然必须首先替换&符号(尽管对尖括号而言,顺序并不重要)。Python的标准库包含此函数的一个稍复杂的版本——在下一个练习中,你将有机会使用这一函数,并在第7章中再一次了解这一函数。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
+关注
异步社区
异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
12049
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载