Python-精粹-全-

Python 精粹(全)

原文:Lean Python

协议:CC BY-NC-SA 4.0

一、入门指南

电子补充材料

本章的在线版本(doi:10.1007/978-1-4842-2385-7 _ 1)包含补充材料,可供授权用户使用。

Python 解释器

Python 解释器是一个读取 Python 程序语句并立即执行它们的程序(完整文档见[8])。要使用解释器,您需要在工作站上打开一个终端窗口或命令提示符。解释器以两种模式运行。 1

对话方式

您可以将解释器用作交互工具。在交互模式下,您运行 Python 程序,您将看到一个新的提示,>>>,然后您可以逐个输入 Python 语句。在 Microsoft Windows 中,您可能会看到类似这样的内容:

C:\Users\Paul>python 
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:43:06) [MSC v.1600 32 bit (Intel)] on win32 
Type "help", "copyright", "credits" or "license" for more information. 
>>> _ 

解释程序立即执行程序语句。当你想试验或尝试新事物时,交互模式真的很有用。例如,有时您需要查看特定函数(您以前没有使用过)的行为。在其他情况下,您可能需要查看一段失败的代码单独做了什么。

提示符可用于输入定义类或函数的单行命令或代码块(稍后讨论)。下面显示了一些命令示例:

1   >>> dir(print) 
2   ['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__','__name__','__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__'] 
3   >>> 
4   >>> 123.456 + 987.654 
5   1111.11 
6   >>> 
7   >>> 'This'+'is'+'a'+'joined'+'up'+'string.' 
8   'Thisisajoinedupstring.' 
9   >>> 
10  >>> len('Thisisajoinedupstring.') 
11  22 

第 1 行的 dir()命令列出了一个对象的所有属性,如果您需要知道可以对一个对象类型做什么,这会很有帮助。没有参数的 dir() run 告诉你有哪些模块可用。dir(print)显示了 print()的所有内置方法的列表,其中大部分您永远都不需要。

如果您键入一个表达式值,如第 4 行,123.456 + 987.654,解释器将执行计算并提供结果。第 7 行的表达式将字符串连接成一个长字符串。第 10 行的 len()函数给出了一个字符串的字符长度。

如果您在交互模式下定义了一个新的函数 2 ,解释器会提示您完成定义,并将一个空行作为函数的结束。

1   >>> def addTwoNumbers(a,b): 
2   ...     result = a + b 
3   ...     return result 
4   ... 
5   >>> addTwoNumbers(3,6) 
6   9 
7   >>> 

注意,例如,当解释器希望在函数中提供更多代码时,它会打印省略号提示(...).在函数定义的情况下,空行(见上面的第 4 行)完成函数定义。

我们在第 1 行到第 3 行定义函数(注意缩进),空行 4 结束定义。我们调用第 5 行的函数,加上 6 + 3,结果是(正确的)9。

交互式解释器的另一个特性是 help()函数。您可以使用它来查看内置关键字和函数的文档。例如:

>>>                                                                                                        help(open) 
Help on built-in function open in module io: 

open(...) 
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) -> file object 

... etc. etc. 
注意

Python 交互式解释器对于尝试和探索该语言的特性非常方便。

命令行模式

在命令行模式下,Python 程序仍然在命令行中运行,但是您要在命令中添加一个文件名(包含您的程序):

python myprogram.py 

解释器读取文件的内容(本例中是 myprogram.py),扫描并验证代码,编译程序,然后执行程序。如果解释器在你的程序语法中遇到错误,它将报告一个编译错误。如果程序在执行过程中失败,您将看到一个运行时错误。如果程序成功执行,您将看到程序的输出。

您不需要担心解释器是如何做的,但是您需要熟悉它产生的错误消息的类型。

我们使用命令行模式来执行文件中的程序。

编码、测试和调试 Python 程序

创建新程序的正常步骤如下:

  1. 创建新的。包含 Python 程序(有时称为源代码)的 py 文件。

  2. 编辑您的。py 文件来创建新代码(或修改现有代码)并保存文件。

  3. 在命令提示符下运行程序来测试它,并解释结果。

  4. 如果程序没有按照要求工作,或者您需要添加更多的功能,请找出需要进行哪些更改,然后转到步骤 2。

用注释记录代码通常是个好主意。这是编辑过程的一部分,步骤 2。如果你需要对一个工作程序进行修改,你可以从第 2 步开始。

编写新程序通常被称为编码。当你的程序不能正常工作时,让程序完全按照你的要求去做通常被称为调试

注释、代码块和缩进

像所有编程语言一样,Python 也有我们必须遵循的惯例。一些编程语言使用标点符号,如大括号({})和分号(;)来构造代码块。Python 有些不同(也更容易看),因为它使用空白和缩进来定义代码结构。 3 有时候代码需要一点解释,所以我们用注释来帮助代码的读者(包括你)理解它。

我们用一些例子来介绍缩进和注释。

| ##哈希后的一些文本#brdr = 2 #粗边框 | 任何出现在散列字符(#)之后的文本都会被解释器忽略,并被视为注释。我们使用注释来提供文档。 | | def my_func(a,b,c): d = a + b + c......如果 this_var==23:doThis()doThat()...否则:do _ 其他()... | 冒号(:)表示区分代码块的标题行的结尾。标题行后面的语句应该缩进。冒号最常用于 if、elif、else、while 和 for 语句以及函数定义(以 def 关键字开头)的末尾。 | | 定义 addTwoNumbers(a,b):"将两个数相加"返回 a + b | 在本例中,引号中的文本是一个 docsctring。这个文本是一个帮助(addTwoNumbers)命令在交互式解释器中显示的内容。 | | 如果 long_var 为真&& \middle==10 && \small_var 是假的:...... | 行尾的反斜杠字符(\)表示该语句扩展到下一行。一些非常长的语句可能会超过几行。 | | xxxxxxxxxxxxxx:大会大会大会 xxxxxxxxx:xxxxxxxxxxx:持续时间 xxxxxxxxxxx:电影站电影站 | 一旦带有冒号的标题行出现,所有代码块都将缩进。该块中的所有语句必须具有相同的缩进。代码块可以按照相同的规则相互嵌套:一个代码块中的所有代码都有相同的缩进。缩进通常使用四个空格的增量来实现。 | | a = b+ c;p = q + ra = b + cp = q + r | 分号字符(;)可用于在一行中连接多个语句。第一行相当于它后面的两行。 |

变量

一个变量是程序内存中的一个命名位置,可以用来存储一些数据。命名变量有一些规则:

  • 第一个字符必须是字母或下划线(_)。

  • 附加字符可以是字母数字或下划线。

  • 名称区分大小写。

通用赋值操作

当你在一个变量中存储数据时,这被称为赋值。赋值语句将一个值或表达式的结果放入变量中。赋值的一般格式是:

var = expression 

表达式可以是文字、计算、函数调用或三者的组合。一些表达式生成一个值列表;例如:

var1, var2, var3 = expression 

这里还有一些例子:

>>> # 3 into integer myint 
>>> myint = 3            
>>> 
>>> # a string of characters into a string variable 
>>> text = 'Some text' 
>>> # a floating point number 
>>> cost = 3 * 123.45 
>>> # a longer string 
>>> Name = 'Mr' + ' ' + 'Fred' + ' ' + 'Bloggs' 
>>> # a list 
>>> shoppingList = ['ham','eggs','mushrooms'] 
>>> # multiple assignment (a=1, b=2, b=3) 
>>> a, b, c = 1, 2, 3 

其他赋值操作

扩充赋值提供了一个稍微短一点的符号,其中变量的值以某种方式进行了调整。

|

这个任务

|

相当于

|
| --- | --- |
| x+=1x-=23x/=6x*=2.3 | x = x + 1x = x–23x = x / 6x = x * 2.3 |

多重赋值提供了一个稍微短一点的符号,其中几个变量同时被赋予相同的值。

|

这个任务

|

相当于

|
| --- | --- |
| a = b = c = 1 | a = 1b = 1c = 1 |

所谓的多重赋值提供了一个稍微短一点的符号,其中几个变量同时被赋值。

|

这个任务

|

说明

|
| --- | --- |
| x,y,z = 99,100,'好'p,q,r = myFunc() | 结果是:x=99,y= 100,z= '好'如果 myFunc()返回三个值,则 p、q 和 r 被赋予这三个值。 |

Python 关键字

像所有编程语言一样,在 Python 中,一些单词有定义的含义,是为 Python 解释器保留的。你不能使用这些词作为变量名。注意都是小写。

| 和 | 如同 | 维护 | 破裂 | | 班级 | 继续 | 极好的 | 是吗 | | 艾列弗 | 其他 | 除...之外 | 高级管理人员 | | 最后 | 为 | 从 | 全球的 | | 如果 | 进口 | 在 | 存在 | | 希腊字母的第 11 个 | 不 | 或者 | 及格 | | 打印 | 上升 | 返回 | 尝试 | | 正在… | 随着 | 产量 |   |

有大量的内置名称,除了它们的预期用途之外,您一定不能使用。真、假和无的情况都很重要。这里列出了最常见的几种。

| 真实的 | 错误的 | 没有人 | 丙烯腈-丁二烯-苯乙烯 | | 全部 | 任何的 | 儿童健康研究(Child Health Research 的缩写) | 词典 | | 目录 | evaluate 评价 | 出口 | 文件 | | 漂浮物 | 格式 | 投入 | (同 Internationalorganizations)国际组织 | | 最大 | 部 | 然后 | 目标 | | 打开 | 打印 | 放弃 | 范围 | | 圆形物 | 设置 | 潜艇用热中子反应堆(submarine thermal reactor 的缩写) | 总额 | | 元组 | 类型 | 变动 | 活力 |

要查看这些内置模块的列表,请在 shell 中列出 builtins 模块的内容,如下所示:

>>> dir(__builtins__) 

特殊标识符

Python 还提供了一些使用下划线的特殊标识符。它们的名称将采用以下形式:

_xxx 
__xxx__ 
__xxx 

大多数情况下,你可以忽略这些。 4 然而,你在编程中可能遇到的一个是特殊的系统变量:

__name__ 

此变量指定如何调用模块。name 包含:

  • 模块的名称(如果已导入)。

  • 字符串“main”如果直接执行。

你经常在模块的底部看到下面的代码。解释器加载你的程序并在必要时运行它。

if __name__ == '__main__': 
main() 

Python 模块

Python 代码通常存储在文本文件中,由 Python 解释器在运行时读取。通常,程序变得如此之大,以至于把它们分成更小的模块是有意义的。可以使用 import 语句将一个模块导入到其他模块中。

import othermod     # makes the code in                **othermod** 
import mymodule     # and                **mymodule**                available 

典型程序结构

相同的程序或模块结构一次又一次地出现,所以你应该尝试并遵循它。这样,你知道从其他程序员那里可以期待什么,他们也知道从你那里可以期待什么。

| #!/usr/bin/python | 仅在 Linux/Unix 环境中使用(告诉 shell 在哪里可以找到 Python 程序)。 | | ##本模块有#有趣的事情,比如#计算薪水# | 模块应该有一些说明性的文本来描述或记录它们的行为。 | | 从日期时间导入日期时间 | 首先是模块导入,这样它们的内容可以在模块的后面使用。 | | now =datetime.now() | 创建一个模块中所有类和函数都可以访问的全局变量。 | | 类别 bookClass(对象):"图书对象"def __init__(self,title):self.title =标题返回 | 首先出现类定义。导入该模块的代码可以使用这些类。 | | def textbook_):"测试测试..."title= "如何测试 Py "book=bookClass(标题)印刷(“测试书籍”) | 接下来定义函数。导入时,函数作为 module.function()进行访问。 | | if __name__=='__main__ ':教科书 _) | 如果导入,模块定义类和函数。如果这个模块运行,这里的代码(例如 testBook())被执行。 |

脚注

有许多标志和选项可以在解释器中使用,但是我们不需要它们。

当然,我们稍后会谈到这些。

顺便说一下,Python 3 不允许混合空格和制表符(与版本 2 不同)。

你真正需要知道的只是 name 变量和创建新对象时调用的 init()方法。不要以下划线开始变量名,这样就没问题了。

二、Python 对象

Python 中的每个变量实际上都是一个对象。使用 Python 并不需要编写面向对象(OO)的代码,但是 Python 的构造方式鼓励使用面向对象的方法。 1

对象类型

所有变量都有一个类型,可以通过使用内置的 type() 函数来查看。

>>> type(23)
<type 'int'>
>>> type('some more text')
<type 'str'>
>>> c=[1,2,'some more text']
>>> type(c)
<type 'list'>

其他类型包括“类”、“模块”、“函数”、“文件”、“布尔值”、“非类型”和“长型”。

特殊常量 True 和 False 是“布尔”类型。特殊常量 None 是“NoneType”。

要查看任何对象的文本定义,可以使用 str()函数;例如,变量 c 可以表示为一个字符串:

>>> str(c)
"[1,2,'some text']"

工厂功能

有一系列函数可以直接创建变量类型。下面是最常用的。

| int(4.0) | 创建整数 4 | | str(4) | 创建字符串“4” | | 列表(1,2,3,4) | 创建列表[1,2,3,4] | | 元组(1,2,3,4) | 创建元组(1,2,3,4) | | dict(一=1,二=2) | 创建字典{'one':1,' two':2} |

数字

整数没有现实的限制;例如,您可以存储和操作 1000 位数的数字。这些数字存储为“长”数字,例如:

>>> 12345678901234567890
12345678901234567890

实数(即带小数点的数字)以所谓的双精度存储。例如:

>>>1 / 7.0
0.14285714285714285

实际的精确度取决于您使用的硬件架构。

非常大或非常小的实数可以用科学符号来描述。

>>> x = 1E20
>>> x / 7.0
1.4285714285714285e+19
>>> int(x/7.0)
14285714285714285714286592
>>> y = 1E-20
>>> y / 7.0
1.4285714285714285e-21

算术运算符

四个算术运算符按预期工作。

| #添加 2 + 32.0 + 3#减法 3 - 23.0 - 2#乘法 3 * 23 * 2.03.0 * 2.0#部门 3 / 2-6 / 2-6 / 43 / 2.0 | #整数 5# Real 5.0(如果一个或多个操作数#都是真实的)#整数 1#真实 1.0#整数 6#真正的 6.0#真正的 6.0 所有除法都产生实数# 1.5# -3.0# -1.5# 1.3 |

其他操作员

| #模数 15 % 4#指数运算 4 ** 3-4 ** 34 ** -3 | # Real 3.0(之后的余数#用 15 除以 4)#整数 64#整数-64(' –'适用于#结果)#实数 0.015625(注意:负数#指数强制操作数为实数#数字 |

转换函数

| int(1.234)int(-1.234)长(1.234)长型(-1.234)龙(' 1234 ')长型(' 1.234 ')长整型(浮点型(' 1.234 ')浮子(4)浮动(' 4.321 ') | #整数 1#整数-1# Long1L#龙-1L#长 1234L# **错误**需要 2 转换数量#龙 1L(后两转换次数)#真正的 4.0#实数 4.321 |

布尔数字

布尔值实际上是作为整数保存的,但它的值可以是 True 或 False。

| 布尔(23)布尔型(0)布尔("任何文本")布尔型(“”)布尔值([]) | # True -所有非零整数# False-零# True–任何字符串# False–零长度字符串# False–空列表 |

随机数

两个随机数生成器很有用(需要导入 random 模块)。

| 随机导入 random.randint 随机. random() | #生成一个随机整数#介于 a 和 b 之间,包括 a 和 b。#生成随机实数#介于 0.0 和 1.0 之间的数字 |

序列:字符串、列表和元组

到目前为止,我们已经看了保存单个值的变量。一个序列是一个变量,它将多个值保存为一个数组。每个元素都可以通过其在序列中的位置来寻址,作为从第一个元素的偏移。三种类型的序列如下:

  • 字符串:共同构成文本字符串的一系列字符。

  • 列表:一个值序列,其中每个值都可以使用从列表中第一个条目的偏移量来访问。

  • Tuples :值的序列,很像列表,但是一个 tuple 中的条目是不可变的;它们不能被改变。

我们将查看所有序列共有的 Python 特性,然后分别查看这三种类型。

序列存储和访问

序列的元素存储为一系列连续的内存位置。序列中的第一个元素可以在位置 0 处访问,最后一个元素在位置n–1 处访问,其中 n 是序列中元素的个数(见图 2-1 )。

A433333_1_En_2_Fig1_HTML.jpg

图 2-1。序列元素的存储

您可以遍历序列 x 的元素,序列 x 有 n 个元素,从元素 x[0]开始,每次 x[1]加+1,x[2] x[n-1]等等。也可以从末尾开始迭代,每次减 1:x[n-1],x[n-2] x[0]。

成员资格

常见的检查是确定序列中是否存在某个值。例如:

| 一个在轨道上 9 英寸[1,2,3,4,5,6] | #真的#错误 | | “x”不在“下一个”中“红色”不在[“茶色”、“粉色”]中 | #错误#真的 |

串联 2

两个或多个序列可以加在一起形成更长的序列。加号(+)用于连接字符串、列表或元组。

| 序列 1 + 序列 2 | #产生新的序列#将序列 2 追加到#序列 1 | | '+'乔先生'+'肥皂先生' | # 'mrjoesoap ' |

序列元素和切片

序列是元素的有序列表,因此单个元素由它相对于第一个元素的偏移量来标识。切片是按顺序选择这些元素的子集的一种便捷方式,可以产生新的序列。使用以下符号标识切片:

**[startindex:endindex]** 

切片将由从 startindex 开始到 endindex(不包括 endindex)的元素组成。

一些例子会使理解变得更容易:

| mylist=['a ',' b ',' c ',' d ',' e']mylist[0]mylist[3]mylist[5]mylist[-1]mylist[1:3]mylist[:4]mylist[3:] | #一个有五个的列表元素数量# 'a '# 'd '#导致错误# 'e '# ['b ',' c']# ['a ',' b ',' c']# ['d ',' e'] |

序列可以嵌套,并且可以使用多个索引来访问元素,例如:

mylist = [1,2,3,['a','b','c'],5]
mylist[2]              # 3
mylist[3]              # ['a','b','c']
mylist[3][1]           # 'b'

序列内置函数

| mylist=[4、5、6、7、1、2、3]len(seq)len(mylist)最大序列最大值(米列表)我的(磨坊主) | #序列的长度# 7inseq 的最大值# 7# 1–最低 |

用线串

一个字符串是组成一段文本的一系列字符。字符串是不可变的,但是您可以通过给同一个字符串变量分配一个新的字符串来更新字符串的值。

>>> mystr = 'Paddington Station'
>>> mystr=mystr.upper()     # replaces mystr 
>>> mystr
PADDINGTON STATION

分配

只要匹配,可以用单引号(')或双引号(")来分隔字符串。您可以将其中一个引用嵌入到另一个引用中。

>>> text = 'Hello World!'
>>> longtext = "A longer piece of text"
>>> print(text)
Hello World!
>>>longtext
'A longer piece of text'
>>> text = 'Paul said, "Hello World!"'
>>>print(text)
Paul said, "Hello World!"

访问子字符串

当然,您可以使用切片来访问子字符串:

| 保罗说,“你好”文本[:4]文本[-4:]正文[5:9]文本[0:4] + 文本[12:14] | # '保罗'# ' "嗨"# '说'# '保罗希' |

字符串比较

字符串可以比较为 3 如下:

| MCR > liv"丽芙>一切"' mcr'=='X 'X'>'t ' | #真的#错误#错误#错误 |

成员资格(搜索)

我们可以逐个字符或使用子串来检查子串是否在一个字符串中。结果是一个布尔值。

| “任务”中的“a”“任务”中的“w”在“任务”中“作业”不在“任务”中“任务”中的“任务” | #真的#错误#真的#真的#真的 |

特殊字符和转义

字符串可以包含非打印字符和控制字符(例如制表符、换行符和其他特殊字符),方法是用反斜杠()对它们进行“转义”。常见的转义字符如下:

\0 Null character
\t Horizontal tab
\n Newline character
\' Single quote
\" Double quote
\\ Backslash
>>> multiline='Line 1\nLine 2\nLine 3'
>>> print(multiline)
Line 1
Line 2
Line 3

三重引号

带有嵌入换行符的较长文本可以使用三重引号符号进行赋值;例如:

>>> multiline="""Line1
¼ Line 2
¼ Line 3"""
>>> multiline
'Line 1\nLine 2\nLine 3'

字符串格式

百分比(%)运算符提供了字符串格式功能。该功能的结构如下:

**formatstring**                                                        **% (arguments to format)** 

formatstringis 是一个字符串,包含要输出的文本,其中嵌入了转换符号,用百分号(%)表示。这些是常见的转换符号:

| %c | 长度为 1 的单个字符/字符串 | | %s | 线 | | %d | 有符号十进制整数 | | %f | 浮点数 | | %% | 百分比字符 |

以下是一些例子:

>>> ntoys = 4
>>> myname='Fred'
>>> length = 1234.5678
>>> '%s has %d toys' % (myname,ntoys)
'Fred has 4 toys'
>>> 'is %s playing?' % (myname)
'is Fred playing?'
>>> 'length= %.2f cm' % length
'length= 1234.56 cm'        
>>> 'units are %6s meters' % length

在前面的示例中,. 2f 中的. 2 表示小数位数。%6s 中的 6 表示字段宽度为 6 个字符。

字符串函数

有大量的内置字符串函数。最常见的如下图所示。注意,这些都返回一个新的字符串;他们不改变字符串,因为字符串是不可变的。

| text = '这是文本'nums = '123456 '#查找文本 text.find('is ')text.find('您的')#验证检查 text.isalpha()text.isdigit()isdigit 号码()#串联' . join((文字,数字)' . join((文字,数字)#改变大小写 text.upper()text.lower()#拆分字符串 text.split(' ')#替换 text.replace('is ',' was ')#去釉 text.rstrip()text.lstrip()text.strip() | # returns2#返回-1#所有阿尔法战士?真实的#所有数字?错误的#真的# '这是文本 123456 '# '这是文本 123456 '# '这是文本'# '这是文本'#字符串列表:#['This ',' is ',' text']#这是文本#删除尾随空格#删除前导空格#删除尾随和前导空格 |

列表

列表广泛用于存储按顺序收集和处理的值,例如从文本文件中读取或写入文本文件的文本行,或者根据它们在数组中的位置或偏移量查找准备好的值。

创建列表

| mylist = []names=['汤姆','迪克','哈里']mixedlist = [1,2,3,'四']elist = [1,2,3,[4,5,6]] | #一个空列表#字符串列表#混合列表#类型#嵌入列表 |

您可以使用 len()函数找到列表的长度。长度是列表中元素的数量。列表 mylist 的最后一个元素索引将作为 mylist[len(mylist)-1]来访问。

| l = len(姓名) | # 3 |

如下面的代码片段所示,访问前面列表中的值。

| 姓名[1]混合列表[0]混合列表[3]混合列表[2:4]伊利斯[2]伊利斯[3]伊利斯[3][1] | # '迪克'# 1# '四'# [3,'四']# 3# [4,5,6]# 5 |

如果您试图访问列表中不存在的元素,您将得到一个“列表索引超出范围”的错误。

更新列表

使用 append()方法将条目添加到列表的末尾。使用 del 语句删除条目。例如:

| mylist = []mylist.append('Tom ')。mylist.append('Dick ')。mylist.append('Harry ')的缩写#更改条目米列表[1]= '比尔'#删除条目 del mylist[1] | #一个空列表# ['汤姆']# ['汤姆','迪克']# ['汤姆','迪克','哈里']# ['汤姆','比尔','哈里']-[“汤姆”,“哈里”] |

索引

membership (in,not in)操作符返回布尔值 True 或 False,而 index()方法在列表中找到一个条目,并返回该条目的偏移量。如果找不到条目,它将返回一个错误。

| mylist=['Tom '、' Dick '、' Harry']mylist.index('Dick ')mylist.index('Henry ') | # 1# ValueError:亨利#不在列表中 |

序列操作和功能

序列运算符——比较、切片、成员和连接——都与处理字符串的方式相同。

序列函数— len()、max()、min()、sum()、sorted()和 reversed()—都按预期工作。

元组

像数字和字符串一样,元组是不可变的。它们对于您可能重用的预置查找或验证器非常有用。

创建元组

为了区分元组和列表,Python 使用括号()将条目括在元组中。

>>> mynumbers = (1,2,3,4,5,6,7)
>>> months=('Jan','Feb','Mar','Apr','May','Jun',
¼ 'Jul','Aug','Sep','Oct','Nov','Dec')
>>> mixed = ('a',123,'some text',[1,2,3,'testing'])

# accessing tuples
>>> mynumbers[3]               # 4
>>> months[3:6]                # ('Apr','May','Jun')
>>> mixed[2]+' '+mixed[3][3]   # 'some text testing'

您可以使用 len()函数找到元组的长度。长度是列表中元素的数量。

如果您试图访问列表中不存在的元素,您将得到一个“元组索引超出范围”错误。

序列操作和功能

序列操作符——比较、切片、成员和连接——都按预期工作。index()方法的工作方式与列表完全一样。序列函数— len()、max()、min()、sum()、sorted()和 reversed()—都按预期工作。

字典

如果我们想让我们的程序记住一个值的集合,我们可以使用列表,我们可以使用这些值的索引来访问条目。但是,要找到我们想要的值,我们必须知道该值的偏移量(或者搜索它)。

字典提供了基于键/值对的查找工具。字典中条目的顺序是不确定的(事实上,它是随机的),但是每个条目都可以通过使用它的键来检索。密钥必须是唯一的;每个关键字在字典中只能有一个条目。

创建字典

您可以使用一组键/值对来创建字典。

>>> # days of week – seven key-value pairs
>>> wdays={'M':'Monday','T':'Tuesday',
¼ 'W':'Wednesday','Th':'Thursday',
¼ 'F':'Friday','Sa':'Saturday',
¼ 'Su':'Sunday'}
>>> wdays['M']
'Monday'
>>> wdays['W']
'Wednesday'
>>> wdays['Su']
'Sunday'

>>> newdict = {}      # empty dictionary

更新字典

您可以使用 dict[key]约定来更新字典。

>>> newdict = {}      # empty dictionary

>>> newdict['1st'] = 'first entry' # add 1st entry
>>> newdict['2nd'] = 'second entry'# add 2nd entry
>>> newdict['1st'] = 'new value'   # update 1st entry

>>> del newdict['2nd']             # delete 2nd entry

>>> len(newdict)                   # 1

字典操作

序列操作符——比较、成员和连接——都按预期工作。以下是一些您可能会觉得有用的字典操作:

# days of week – seven key/value pairs

# key existence
>>> 'Sa' in wdays 
True
>>> 'Sp' in wdays
False

# create list of keys
>>> wdays.keys() 
['M','T','W','Th','F','Sa','Su']

# create an iterable list of values
>>> wdays.values() 
dict_values(['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'])

# look up a key with a default if key not found
>>> wdays.get('X','Not a day') 
'Not a day'

脚注

我们在第六章中介绍了面向对象。

建议您使用 join() string 方法来连接字符串列表或元组,因为这样效率更高。例如:

'-'.联接((' a ',' b ',' c ',' d '))

'阿-b-c-d '

3 你可以在 http://www.asciitable.com/的看到 ASCII 码的校对顺序。空格位于数字字符之前,数字字符位于大写字母之前;小写字母排在最后。

三、程序结构

决策

一些简单的工具可能是按顺序运行的简短语句列表,但是一个有用的程序通常需要做出决策和选择。程序中的决策决定了程序的发展方向。决策是由 if 语句做出的。

if 语句

if 语句依赖于某种形式的测试或条件。if 语句的标准格式如下所示:

if test:               # note the colon ':'.
   statement1          # The statements following the
   statement2          # if are indented and executed
   statement3          # if the test is True.

在这种情况下,如果 test 为真,则执行这三条语句。否则这些语句被跳过,解释器跳到代码块后面的语句。

通常,测试的错误结果有自己的代码块,如下所示:

if test:               # note the colon ':'.
   DoThis()            # DoThis() ... if test=True
else:                  # note the colon after 'else'
   DoThat()            # DoThat() ... if test=False

else:关键字将测试的真实结果与错误结果分开,并指示编译器采用替代代码块:DoThat()。

到目前为止,我们已经看到了只有真和假结果的二元决策。一些 if 语句从两个以上的选项中进行选择。在这种情况下,elif:关键字分隔不同的选择,如果没有满足其他测试,else:关键字是最终选择。例如:

1   If code=='RED':
2       SoundRedAlert()
3   elif code=='AMBER':
4       GiveWarning()
5   else:
6       pass
注意

if 及其对应的 elif 和 else 关键字的缩进必须相同。

pass 语句

在前面的例子中,根据代码的值,程序选择做某事,但第三个选择是通过。pass 语句是一个“什么都不做”的语句。第三个子句(else)不是绝对必要的,但是 pass 通常用于明确显示程序正在做什么(或没有做什么)。

测试类型

if 语句中应用的测试可以采取多种形式,但是这里总结了主要的模式。

  • 比较

    var1 > var2       # greater than
    var1 == var2      # equal to
    var1 != var2      # not equal to
    
  • 序列(列表、元组或字典)成员资格

    var in seq
    var not in seq
    
  • 序列长度

    len(x)>0       # sequence has entries?
    
  • 布尔值

    fileopen       # fileopen==True?
    not fileopen   # fileopen==False?
    
  • 有值?

    var            # not None (or zero or '')
    
  • 验证

    var.isalpha()    # alphanumeric?
    var.isdigit()    # is all digits?
    
  • 计算

    (price*quantity) > 100.0 # cost>100?
    (cost-budget) > 0.0      # overbudget?
    

在计算的情况下,使用大括号来强制计算通常比依赖默认的运算符优先级更好。

有些决策更复杂,需要多次测试才能实现。例如:

if hungry and foodInFridge and notTooTired:
    cookAMeal()
else:
    getTakeAway()

在这种情况下,and 运算符连接三个条件,并且所有条件都必须为真,cookAMeal()才能执行。

有三种逻辑运算符——and、or 和 not——可用于连接决策。

决策可以嵌套;也就是说,它们可以出现在其他决策的缩进代码块中。例如:

if age>19:
    if carValue>10000:
        if gotConvictions:
           rejectInsuranceApplication()

循环和迭代

有些功能需要重复执行活动来处理大量项目,包括:

  • 值列表。

  • 词典中的条目。

  • 数据库中的数据行。

  • 磁盘文件中的文本行。

这些结构通常被称为循环,对某些数据项重复执行定义的代码块,直到满足(或不满足)某些条件或测试。这些循环是使用 for 和 These 语句实现的。

For 语句

forstatement 充当代码块的头语句,该代码块将被执行,直到满足某个条件。for 语句操作一组可迭代的元素,通常是一个序列。

>>> theTeam=['Julia','Jane','Tom','Dick','Harry']
>>> for person in theTeam:
...    print('%s is in the team' % person)
Julia is in the team
Jane is in the team
Tom is in the team
Dick is in the team
Harry is in the team

一般格式为“for var in seq:”。

对于序列成员的每次迭代,变量 var 取列表或元组中条目的值,或者取字典中键的值。

有时我们不想遍历一个列表或字典,但是我们想执行一个循环特定的次数。Python 提供了一个有用的 range()函数,为我们生成一个特定大小的可迭代列表。它可以接受三个参数—start、end 和 step—分别指定第一个数字、最大数字和生成范围内元素之间的增量。如果只提供一个 number 参数,它会创建一个列表,其中包含从零开始的整数元素的数量。

>>> range(10)
range(0,10)         # a list of ten elements 0-9
>>> range(1,10)     # default step=1
range(1,10)         # list: [1,2,3,4,5,6,7,8,9]
>>> range(1,20,3)   # steps of 3
range(1,20,3)       # list: [1,4,7,10,13,16,19]

>>> for i in range(3):
...    print(i)
0
1
2

While 语句

whilestatement 类似于 for 语句,因为它为要重复多次的代码块提供了一个 header 语句。while 语句重复循环,直到不满足测试,而不是一组可迭代的元素。

>>> n=4
>>> while n>0:
...    print(n)
...    n-=1
4
3
2
1

通常,要满足的条件是在循环中递减的计数器或其值在循环内改变的布尔值,然后循环终止。

>>> foundSmith=False
>>> while not foundSmith:
...   name=getNextName()   # get next person record
...   if name=='smith':    # name is 'smith'?
              foundSmith=True

break 语句

breakstatement 用于终止当前循环,并继续执行 for 或 while 代码块后的下一条语句。

1   while True:     # this is an infinite loop
2      command=input('Enter command:')
3      if command=='exit': # infinite till user exits
4           break              # skips to line 7
5       else:
6           doCommand(command) # execute command
7   print('bye')

继续陈述 1

continuestatement 用在循环的代码块中,用于从当前代码块退出并跳到循环的下一次迭代。while 或 for 循环测试检查正常。

1   while True:     # this is an infinite loop
2       command=input('Enter command:') 
3       if len(command)==0:  # no command - try again
4           continue     # goes to next loop (line 1)
5       elif command=='exit': # user exit
6           print('Goodbye')
7           break        # skips to line 10
8       else:
9           doCommand(command)
10   print('bye')

列出理解

一个列表理解(也称为列表组件)是一种以优雅的速记方式动态创建元素列表的方法。假设您想要创建一个前十个整数的平方列表。您可以使用以下代码:

squares=[]
for i in range(1,11):
   squares.append(i*i)

或者你可以用这个:

squares=[i*i for i in range(1,11)]

listcomps 的语法是:

[expr for element in iterable if condition]

if 条件可用于从 iterable 中选择元素。下面是一些使用这种语法的例子:

# a list of even numbers between 1 and 100
evens = [i for i in range(1,100) if not i % 2]

# a list of strings in lines containing 'True'
trulines = [l for l in lines if l.find('True')>-1]

使用函数

为什么要写函数?

当你写更复杂的程序时,你可以选择用长的、复杂的模块来写,但是复杂的模块更难写,更难理解。更好的方法是将一个复杂的程序模块化成更小、更简单、更集中的模块和功能。

将大型程序分割成模块和功能的主要动机是为了更好地管理过程的复杂性。

  • 模块化将复杂性“分割并征服”成更小的不太复杂的代码块,因此设计更容易。

  • 做好一件事的函数更容易理解,对你和其他程序员非常有用。

  • 功能通常可以在系统的不同部分重用,以避免重复代码。

  • 如果你想改变一些行为,如果是在一个函数中,你只需要在一个地方改变代码。

  • 较小的函数更容易测试、调试和运行。

重要的是,如果你选择使用别人写的函数,你不应该太担心它是如何工作的,但你需要信任它。 2

什么是函数?

函数是一段程序代码,它:

  • 一个独立连贯的功能。

  • 可由其他程序和模块调用。

  • 调用模块使用参数(如果需要)传递数据。

  • 能够将结果返回给调用者(如果需要)。

您已经了解了相当多的内置 Python 函数。其中之一是 len()函数。我们只是调用 len()并将一个序列作为参数传递。我们不需要编写自己的 len()函数,但是假设我们确实编写了自己的函数(仅用于列表和字典)。它可能看起来像这样:

>>> def lenDictList(seq):
...    if type(seq) not in [list,dict]: # a seq?
...         return -1                   # no - fail!
...    nelems=0                         # length zero
...    for elem in seq:                 # for elem
...       nelems+=1                     # add one
...
... return nelems                       # length
...

标题行具有独特的格式:

  • 关键字 def 表示它是一个新函数。

  • 函数名 lenDictList(符合变量命名规则)。

  • 用大括号将参数括起来(无、1 或更多)。

  • 表示标题行结束的冒号。

函数内部的代码是缩进的。代码使用函数定义中的参数,不需要定义它们(它们将由调用模块传递)。以下是一些例子:

>>> l = [1,2,3,4,5]
>>> d = {1:'one',2:'two',3:'three'}
>>> lenDictList(l)
5
>>> lenDictList(d)
3
>>> lenDictList(34)
-1

请注意,real len()可以处理包括元组和字符串在内的任何序列,并且具有更好的错误处理能力。这是一个过于简化的版本。

返回值

使用 returned 语句将函数的结果返回给调用者。在前面的示例中,有一个返回值:所提供的列表或字典的长度,如果参数都不是,则返回值为–1。

一些函数不返回结果;他们只是退出。

程序员可以选择如何设计他或她的功能。以下是一些 return 语句的示例。

| 返回返回 True 返回 False 返回 r1、r2、r3 返回字典(a=v1,b=v2) | #不返回值#没错–也许是成功?#错误–也许是失败?#返回三个结果#返回字典 |

调用函数

通过使用函数名并添加圆括号将变量或值作为参数传递来调用函数。你已经知道 len()了。发明其他函数是为了说明如何使用函数。

>>> count = len(seq)      # length of a sequence
>>>
>>> # the call below returns three results, the
>>> # maximum, the minimum, and the average of
>>> # the numbers in a list
>>> max, min, average = analyse(numlist)
>>>
>>> # the next call provides three parameters
>>> # and the function calculates a fee
>>> fee = calculateFee(hours, rate, taxfactor)
注意

赋值左边的变量数量必须与函数提供的返回值数量相匹配。

命名参数

如果一个函数只有一个参数,那么你可能不用担心它在函数调用中的名字。但是,有时并不要求提供所有的参数,它们可以采用默认值。在这种情况下,您不必为参数提供值。如果您在函数调用中命名了一些参数,那么您必须在未命名的参数之后提供命名的参数。这里有一个例子:

| 定义 fn(a、b、c=1.0):返回 a*b*cfn(1,2,3)fn(1,2)联合国(1、b=2)fn(a=1,b=2,c=3)联合国(1、b = 2,3) | # 1*2*3 = 6# 1 * 2 * 1 = 2–c =默认 1.0# 1 * 2 * 1 = 2–相同的结果# 1*2*3 = 6 -和以前一样#错误!你必须提供*未命名后的#命名参数*#参数 |
注意

在代码中,必须先定义一个函数,然后才能调用它。函数调用不能出现在函数定义之前,否则你会得到一个“未定义”的错误。

变量作用域

函数内部定义和使用的变量对于函数外部的其他函数或代码是不可见或不可用的。但是,如果您在模块中定义了一个变量,并在该模块中调用了一个函数,那么该变量在被调用的函数中是可用的。如果一个变量是在模块中所有函数之外定义的,那么该变量对模块中的所有函数都是可用的。 3 例如:

sharedvar="I'm sharable"   # a var shared by both
                           # functions
def first():
   print(sharedvar)        # this is OK
   firstvar='Not shared'   # this is unique to first
   return

def second():
   print(sharedvar)        # this is OK
   print(firstvar)         # this would fail!
   return

脚注

当然,在下面的例子中,需要定义 doCommand()函数。

所有开源或免费使用的库都带有健康警告,但是如果你在程序员网站和书中看到许多对库的引用,你可以有理由相信它是有效的。

有时候创建共享变量很方便,这样可以节省你的时间和把它们作为参数添加到模块中函数的麻烦。但是,如果您将这些变量用作在函数之间传递数据的地方,您可能会发现难以诊断的问题。将它们视为只读变量将减少出现难以调试的问题的机会。

四、输入和输出

如果一个程序不产生任何输出,它就不会很有用,不是吗?如果一个程序不接受一些不时变化的数据,它会一次又一次地产生相同的结果,这也不会很有用(至少在第一次运行后)。因此,大多数程序需要接受一些输入或输入数据,这样它们才能产生输出数据、输出或结果。

在本章中,我们将介绍三种重要的输入/输出机制:

  • 显示的输出。

  • 通过键盘从用户那里获取数据。

  • 从磁盘文件获取输入并将输出写入磁盘文件。

显示输出

你已经多次看到 print()函数 1 了。从程序中获取输出的最常见方式是使用 print()语句。Print 是一个函数,它把要显示的项目作为它的参数。或者,您还可以定义一个分隔符,该分隔符位于显示的项目和一个可以替换换行符的行终止符值之间。函数调用如下所示:

print(arg1,arg2,arg3...,sep=' ',end='\n')

下面是一些正在使用的 print()函数的例子。

>>> boy="Jack"
>>> girl="Jill"
>>> print("Hello World!")
Hello World!
>>> print(boy,'and',girl,'went up the hill')
Jack and Jill went up the hill

使用字符串格式化功能是很常见的。

>>> print('%d plus %d makes %d' % (3, 7, 10))
3 plus 7 makes 10

您可以通过将 end 参数设置为空字符串(或其他内容)来取消尾部换行符。

>>> #
>>> # the end= argument defaults to '\n'
>>> # if you change it, there won't be a newline
>>> #
>>> print('one...','two...','three',end='')
one... two... three>>>    # note the >>> prompt

字符串分隔符默认为单个空格,但可以通过将其设置为空字符串来更改或取消。

>>> #
>>> # the sep= argument defaults to a space ' '
>>> # but you can change it, for example...
>>> #
>>> print('one...','two...','three',sep='***')
one...***two...***three

获取用户输入

将数据输入程序最简单的方法是使用 input() 2 函数。它有一个参数,就是您在命令行上看到的提示符。该函数返回一个字符串值,因此如果您需要用逗号分隔的数值或多个值,您必须在使用数据之前解析和处理代码中的文本。

>>> yourName=input('Enter your name: ')
Enter your name: Paul
>>> print('Your name is',yourName)
Your name is Paul

如果您要求用户输入一个整数,您应该检查输入的文本是否有效,是否可以转换为整数。为此,您可以执行以下操作:

  • 使用 len(text)验证是否输入了某些文本。

  • 使用字符串函数 text.isdigit()检查表示数字的文本。

  • 使用 int(text)将文本转换为整数,以便进行处理。

你可能听说过“垃圾进,垃圾出”的概念。如果您不验证进入程序的数据,它的行为可能是不可预测的,或者它可能失败,或者只是产生奇怪的结果。不要忘记,黑客可以利用糟糕的输入验证在互联网网站上造成混乱。

注意

作为程序员,您有责任确保您的程序只接受符合验证规则的数据。

写入和读取文件

总有一天,你要在磁盘或其他设备上读写文本文件。我们在这里特别关注纯文本文件以及如何访问它们。

打开文件

要访问磁盘上的文件,需要创建一个 file 对象,并使用 open()函数来实现。公开呼叫的格式是:

fileobj = open(filename,mode)

命名的文件通常会在当前目录中打开,但是该名称可以包含路径,因此它可以打开任何磁盘上和任何目录中的任何文件(本地权限允许)。该模式告诉解释器打开文件,读“r”,写“w”,或附加“a”。

表 4-1 显示了用三种模式值打开现有和不存在的文件的结果。

表 4-1。用三种模式值打开文件
|

开放模式

|

文件存在

|

文件不存在

|
| --- | --- | --- |
| r′ | 打开阅读 | 没有这样的文件或目录错误 |
| w ' | 被空文件覆盖并打开以供写入 | 打开以供书写 |
| 一个 | 打开以追加 | 创建新的空文件并打开进行写入 |

注意

使用写模式时要小心;您可能会覆盖包含有价值数据的文件并丢失它。

以下是一些如何打开文件的示例:

fname='myfile.txt'
fp = open(fname,'r')  # open for reading (must exist)
fp = open(fname,'w')  # creates new file for writing
fp = open(fname,'a')  # opens file for appending

关闭文件

一旦完成了对文件的读写,最好使用 close()函数关闭它。

fp = open(fname,'w')  # open for writing
#
# do some writing, etc.
#
fp.close()

如果不显式关闭文件,应该不会遇到什么大问题,但是为了完整和整洁起见,最好将 open()和 close()函数配对。

读取文件

从文件中读取数据的标准函数是 read()。它将文件的全部内容读入一个字符串变量。然后,可以将内容拆分成由换行符(' \n ')分隔的单独的行。

fp = open(fname,'r')  # open for reading
text = fp.read()
lines=text.split('\n')
fp.close()

将文件读入内存的一种更常见的方法是 readlines(),它返回包含每一行的列表。

fp = open(fname,'r')  # open for reading
lines = fp.readlines()
fp.close()

刚刚显示的行列表中的每个条目的末尾都有一个换行符,所以清理 readlines()数据的一个好方法是使用列表理解:

lines = [line.rstrip() for line in fp.readlines()]

如果您想逐行读取文件,最好的方法是利用文件对象本身返回一个迭代器的事实,如下所示:

fp = open(fname,'r')  # open for reading
for eachLine in fp:
  #
  # process each line in turn
  #
  print(eachLine,end='')  # suppress the extra \n
fp.close()
注意

无论以哪种方式读取文件,读取的文本都包含尾随换行符;你必须自己移除它。

写入文件

将数据写入文件的标准函数是 write(),它的工作方式与您预期的完全一样。

fp.write(textline)

请注意,write()函数在写之前不会在文本中添加新行。这里有一个简单的例子:

fp = open('text.txt','w')
while True:
    text = input('Enter text (end with blank):')
    if len(text)==0:
        break
    else:
        fp.write(text+'\n')
fp.close()

如果没有在 write 语句中添加结尾的' \n '换行符,所有的文本行将被合并成一个长字符串。如果您有一个字符串列表,您可以在一条语句中将该列表写成一个文件,但是您必须记住在每个字符串后面附加一个换行符,以使文件如您所期望的那样出现。

以下是将列表写入文件的两种方式:

lines=['line 1','line 2','line 3','line 4']

# write all lines with no '\n'
fp.writelines(lines)

# writes all line with '\n'
fp.writelines([line+'\n' for line in lines])
注意

write()和 writelines()函数不追加尾随换行符(\ n);你必须自己做那件事。

访问文件系统

有许多有用的文件系统函数。它们都可以使用 os 模块获得,您必须导入该模块。

import os

# remove a file (deleteme.txt) from disk
os.unlink('deleteme.txt') 
# rename file on disk (from file.txt to newname.txt)
os.rename('file.txt','newname.txt') 
# change current/working directory
os.chdir(newdirectory) 
# create list of files in a directory
filelist = os.listdir(dirname) 
# obtain current directory
curdir = os.getcwd() 
# create a directory
os.mkdir(dirname) 
# remove a directory (requires it to be empty)
os.rmdir(dirname) 

# in the following examples, we need to use
# the os.path module
#
# does the file/directory exist?
exists = os.path.exists(path) 
# does path name exist and is it a file?
isfile = os.path.isfile(filepathname) 
# does path name exist and is it is directory?
isdir = os.path.isdir(filepath) 

命令行参数

input()函数允许您在程序的任何时候从使用键盘的用户那里获得输入。不过,通常情况下,在命令行中直接在程序名后面输入对用户来说更方便。大多数命令行工具都有选项和数据可在其过程中使用;例如:

python mycopy.py thisfile.txt thatfile.txt

这可能是一个将一个文件复制到另一个文件的程序。

参数从 sys 模块的 sys.argv 列表中捕获。下面是演示如何捕获命令行参数(command.py)的一些代码:

import sys

nargs=len(sys.argv)
print('%d argument(s)' % (nargs))
n=0
for a in sys.argv:
    print('  arg %d is %s' % (n,a))
    n+=1

让我们试着用三个参数运行我们的程序:

D:\LeanPython>python command.py arg1 arg2 arg3
4 argument(s)
  arg 0 is command.py
  arg 1 is arg1
  arg 2 is arg2
  arg 3 is arg3

注意,第一个(元素 0)参数总是程序本身的名称。

脚注

请注意,在版本 2 中,print 是一个语句,而不是一个函数,因此它的行为有所不同。Print()是第三版常见的绊脚石,所以看看 https://docs.python.org/3/whatsnew/3.0.html 的。

2 在版本 2 中,Python 改为使用函数 raw_input()。它的工作方式与版本 3 中的 input()函数完全一样。

五、使用模块

一旦在一个 Python 文件中有超过一百(或几百)行代码,在同一个地方管理所有的函数和类可能会有点混乱。将您的代码分成两个或更多模块文件,每个模块文件覆盖功能的一个方面,可以大大简化事情。

使用 might 语句,标准库中(以及您可能不时下载和使用的其他库中)的特性和函数作为模块对您的程序可用。

无论您使用自己开发的模块还是标准库,包含代码的机制都是相同的。

从模块导入代码

导入语句的格式如下:

import modulename [*as name*]

该语句导入模块 modulename。可选的“as name”部分允许您在代码中用不同的名称引用该模块。如果这条语句没有错误,那么该模块中的所有函数和类都可以使用。

模块来自 Python 路径

当 Python 遇到 import modulename 语句时,它会寻找一个名为 modulename.py 的文件来加载。 1 看起来哪里都不像。Python 有一个名为 Python path 的内部变量。您可以通过检查 sys.path 变量来了解它是什么。

以下交互显示了 Windows 计算机上的路径。

>>> import sys
>>> sys.path
['', 'C:\\Windows\\SYSTEM32\\python34.zip', 'c:\\Python34\\DLLs', 'c:\\Python34\\lib', 'c:\\Python34', 'c:\\Python34\\lib\\site-packages']
>>>

Python 路径是由 Python 安装过程设置的目录列表,但是您也可以根据自己的情况访问和更改路径。

当您安装其他库(例如,从 PyPI)时,路径可能会更新。如果您将所有 Python 代码保存在一个地方(在同一个当前目录中),您将永远不需要更改 Python 路径。如果您在不同的位置处理许多模块,您只需要担心 Python 路径。

创建和使用您自己的模块

我们假设你有一个 Python 代码的模块,名为 mod1.py 代码中模块的名称将是 mod1。您还有一个名为 testmod.py 的程序文件。让我们看看每个文件中的一些示例代码。

这是 mod1.py:

def hello():
    print('hello')

writtenby='Paul'

class greeting():
    def morning(self):
        print('Good Morning!')
    def evening(self):
        print('Good Evening!')

现在,假设我们有一个测试程序 mod1test1.py:

import mod1 as m1

print(writtenby)

m1.hello()
print('written by', m1.writtenby)

greet = m1.greeting()
greet.morning()
greet.evening()

现在,您可以在第 1 行看到,我们已经将 mod1 作为 m1 导入。这意味着使用 m1 前缀引用模块 mod1 中的代码。如果我们刚刚导入模块,我们将使用前缀 mod1。当我们运行这段代码时,我们会得到以下结果:

D:\LeanPython\programs>python mod1test1.py
hello
written by Paul
Good Morning!
Good Evening!

我们来看另一个测试文件(mod1test2.py)。

from mod1 import greeting,hello,writtenby

hello()
hello.writtenby='xxx'
print('written by', hello.writtenby)
print(writtenby)

greet = greeting()
greet.morning()
greet.evening()

在本例中,我们使用以下格式导入了函数和类:

from module import function1, function2…

当我们运行这段代码时,我们会得到以下结果:

D:\LeanPython\programs>python mod1test2.py
hello
written by xxx
Paul
Good Morning!
Good Evening!

您可以看到,导入格式允许我们删除导入函数的前缀。

我们现在只导入我们想要的,我们也可以命名导入的函数和类而不用前缀。我们也可以使用以下格式:

from module import *

在这种情况下,我们从模块中导入所有的函数和类。对于 mod1test2.py,结果应该是相同的;不需要前缀。

当我们从通常有大量函数和类的标准库中导入时,这种方式更有效。

注意

一般来说,给你导入的模块命名,只导入你需要的;也就是避免“导入*”。

脚注

导入语句假设你的模块文件以'结尾。py。

六、面向对象

什么是面向对象 1

专业的编程方法已经从用函数中定义的功能层次来设计系统转向了面向对象(OO)的方法。我们在这里看 Python 如何完全支持面向对象。

在本书中,我们只能简单介绍一下如何使用对象。我们通过一个例子来介绍一些最基本的概念。我们在解释中使用了一些面向对象的概念,所以你需要对面向对象有一个基本的了解才能理解这一部分。

在 OO 设计中,系统是由定义明确的对象的协作集合组成的。一个对象向另一个对象发送消息来实现某个目标,而不是一个功能使用另一个功能。消息实际上是一个函数调用,但是函数(通常称为方法)与所讨论的对象相关联,而方法的行为取决于对象的性质。

你已经在很多地方碰到过物体。事实上,在 Python 中一切都是对象;我们只是没有详细阐述这个面向对象的世界观。例如,字符串有几个有用的方法:

newstring = text.upper()  # uppercase the string
newstring = text.lower()  # lowercase the string
newstring = text.split('\n')  # split lines

字符串函数返回一个新的变量——新的对象——有时称为字符串类的实例 2 。对象(新字符串)是对原始字符串调用的方法的输出。

在更复杂的对象中,对象本身具有我们可以设置和获取的属性。 3 例如我们可能会创建一个人对象。person 对象允许的函数或方法将包括 new 或 create 方法以及各种 set 方法和 get 方法;例如:

fred=customer(name='Fred') # create new customer
fred.setAge(49)            # change age to 49
fred.gender='Male'         # Fred is male
cdate=fred.getCreateDate() # get the creation date
age = fred.getAge()        # get Fred's age
del fred                   # Fred no longer wanted L

在前面的例子中,您可以看到有一些 get 和 set 方法来获取和设置 Fred 的属性,比如他的年龄和创建日期。除非通过这些简单的 get 和 set 函数,否则不能直接访问他的年龄和创建日期。

不过,有一个属性可以直接检查:fred.gender。它不是一个方法,因为在我们引用它时没有关联的括号。我们可以直接访问这个属性,也可以像其他变量一样通过赋值来设置它。

使用类创建对象

在我们的 Python 代码中,通过引用一个类来定义一个新对象。就像我们可以创建 int()、list()和 str()类型一样,我们可以使用类定义来定义自己更复杂的对象。

类别定义是新对象的模板。它定义了创建该类的新实例所需的机制和数据、它的属性(私有和公共)以及可用于设置和获取属性或更改对象状态的方法。

注意,我们并不推荐这是 person 类的完美实现;这只是一个说明类定义、对象属性和方法的使用的例子。

定义 person 类的模块是 people.py:

1   from datetime import datetime
2  
3   class person(object):
4       "Person Class"
5       def __init__(self,name,age,parent=None):
6           self.name=name
7           self.age=age
8           self.created=datetime.today()
9           self.parent=parent
10          self.children=[]
11          print('Created',self.name,'age',self.age)
12  
13      def setName(self,name):
14          self.name=name
15          print('Updated name',self.name)
16  
17      def setAge(self,age):
18          self.age=age
19          print('Updated age',self.age)
20          
21      def addChild(self,name,age):
22          child=person(name,age,parent=self)
23          self.children.append(child)
24          print(self.name,'added child',child.name)
25  
26      def listChildren(self):
27          if len(self.children)>0:
28              print(self.name,'has children:')
29              for c in self.children:
30                 print('  ',c.name)
31          else:
32              print(self.name,'has no children')
33      
34      def getChildren(self):
35          return self.children
  • 第 1 行导入了我们稍后需要的日期时间和打印模块。

  • 第 3 行开始了类定义。我们已经使用了通用对象类型,但是类可以子类化并继承另一个父类的属性和方法。

  • 第 5 行到第 11 行创建了一个新对象。Python 不需要“新”方法:当对象被创建时,Python 会寻找 init()方法来初始化对象。这里使用通过创建调用传递的参数来初始化对象属性。 4

  • 第 13 行到第 19 行中的两个方法更新对象属性的名称和年龄。

  • 在第 21 到 24 行,addChild 方法创建了一个新的 person,它是当前 person 的子代。子对象存储在属性 children 中,这是每个子对象的 person 对象列表。

  • 在第 26 到 32 行,listChildren 方法打印出这个人的孩子的名字。

  • 在第 34 和 35 行,getChildren 方法返回一个包含这个人的孩子的列表。

注意

类方法通常不会打印信息消息来描述它们的行为。这个类中的检测代码显示了内部的情况。

我们编写了一个名为 testpeople.py 的程序来测试 person 类。嵌入的注释应该解释发生了什么。

1   >>> from people import person
2   >>> #
3   ... #   create a new instance of class person
4   ... #   for Joe Bloggs, age 47
5   ... #
6   ... joe=person('Joe Bloggs',47)
7   Created Joe Bloggs age 47
8   >>> #
9   ... #   use the age attribute to verify
10  ... #   Joe's age
11  ... #
12  ... print("Joe's age is",joe.age)
13  Joe's age is 47
14  >>> print("Joe's full name is ",joe.name)
15  Joe's full name is  Joe Bloggs
16  >>> #
17  ... #   add children Dick and Dora
18  ... #
19  ... joe.addChild('Dick',7)
20  Created Dick age 7
21  Joe Bloggs added child Dick
22  >>> joe.addChild('Dora',9)
23  Created Dora age 9
24  Joe Bloggs added child Dora
25  >>> #
26  ... #   use the listChildren method to list them
27  ... #
28  ... joe.listChildren()
29  Joe Bloggs has children:
30     Dick
31     Dora
32  >>> #
33  ... #   get the list variable containing Joe's children
34  ... #
35  ... joekids=joe.getChildren()
36  >>> #
37  ... #   print Joe's details.
38  ... #   NB the vars() function lists the values
39  ... #   of the object attributes
40  ... #
41  ... print("** Joe's attributes **")
42  ** Joe's attributes **
43  >>> print(vars(joe))
44  {'age': 47, 'children': [<people.person object at 0x021B25D0>, <people.person object at 0x021B2610>], 'name': 'Joe Bloggs', 'parent': None, 'created': datetime.datetime(2014, 4, 4, 8, 23, 5, 221000)}
45  >>> #
46  ... #   print the details of his children
47  ... #   from the list we obtained earlier
48  ... #
49  ... print("** Joe's Children **")
50  ** Joe's Children **
51  >>> for j in joekids:
52  ...     print(j.name,'attributes')
53  ...     print(vars(j))
54  ...
55  Dick attributes
56  {'age': 7, 'children': [], 'name': 'Dick', 'parent': <people.person object at 0x021B2590>, 'created': datetime.datetime(2014, 4, 4, 8, 23, 5, 229000)}
57  Dora attributes
58  {'age': 9, 'children': [], 'name': 'Dora', 'parent': <people.person object at 0x021B2590>, 'created': datetime.datetime(2014, 4, 4, 8, 23, 5, 231000)}
59  >>>
  • 第 1 行导入了我们需要的模块。

  • 第 6 行创建了一个名叫 Joe Bloggs 的人。

  • 第 12 行到第 15 行打印 Joe Bloggs 的详细信息。

  • 第 19 到 24 行向 Joe 的记录中添加了两个孩子。注意,person 类为每个孩子添加了新的 person 对象。

  • 第 28 行调用 joe.listChildren()方法来列出 joe 的孩子的详细信息。

  • 第 35 行使用 joe.getChildren()方法获取 joe 的子对象列表。

  • 第 41 到 43 行使用 vars()函数收集 joe 对象的属性。您可以看到为该对象定义的所有变量,包括 Joe 的孩子的列表。

  • 第 51 到 53 行遍历 joekids 列表,打印子对象的属性。

前面的叙述向您展示了面向对象在 Python 语言中是如何工作的。面向对象是一个庞大而复杂的主题,需要仔细解释。 5

脚注

1 面向对象是个大话题。你可以在 http://en.wikipedia.org/wiki/Object-oriented_programming看到面向对象编程的概述。

当你从一个类定义中创建新的对象时,这个对象有时被称为实例,创建过程被称为实例化。在我们的描述中,我们使用了这个词。

至于我们是隐藏属性,只通过方法(私有属性)使其可用,还是向外界公开(公共),这是一个设计选择。

注意,类中所有方法的第一个参数是“self”,即对象本身。这个参数在类内部使用,并不向调用这些方法的代码公开,您将在下面的测试中看到这一点。“自我”属性是公共的。

面向对象的 Python 实现在某些方面不同于其他语言,例如 Java。Python 对面向对象有一种更“放松”的态度,这使得一些事情对程序员来说更容易,但也意味着程序员在他或她的编码中需要更加小心。由你来决定哪种方法是最好的。

七、异常和错误处理

异常和错误

由于多种原因,您的代码可能会出错。一个原因是程序员写了一些有错误的代码。错误、缺陷和 bug 是我们给程序代码中不太正确的部分贴上的标签。错误的发生是因为我们作为程序员并不完美。我们是人,总会犯一些错误。在你编程生涯的早期,你会发现一切都不是一帆风顺的。 1

有时,您可以控制程序的输入数据和行为,但是您的代码被处理成曲线球的方式之多,超出了您处理它们的能力。从这个挑战中诞生了一种叫做防御性编程的方法。 2

防御性编程不是一种胆怯的方法或平庸的态度。这是一门直面失败可能性的学科。这个规程的一个重要部分是错误和异常处理的有效实现。

让我们看一个例子。假设您有一些实现除法运算的代码,如下所示:

quotient = a / b

可能会出什么问题?如果 b 的值为零,那么明显的问题就出现了。我们会看到什么?

>>> quotient = 73 / 0.0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: float division by zero
>>>

我们感兴趣的是文本 ZeroDivisionError:float division by zero。Python 解释器引发了一个错误 ZeroDivisionError,并提供了一条错误消息,尽管这条消息非常简洁。

如果这个代码片段在程序中间,程序就会失败并终止。如果我们想让程序处理错误并继续下一次计算,那对我们来说没什么用。我们使用术语错误处理或者更常见的是异常处理,来指代我们是如何做到这一点的。

Python 有许多内置的异常类型——ZeroDivisionError 只是其中之一——我们可以捕获这些事件,并在代码中处理它们。下面是一个示例程序 division.py:

1   print('Input two numbers. the first will be divided by the second')
2   
3   afirst = input('first number:')
4   first=float(afirst)
5   asecond = input('second number:')
6   second = float(asecond)
7   
8   quotient = first / second
9   print('Quotient first/second = ',quotient)

如果你运行这个,输入 1 和 2,结果是 0.5,没问题。如果您输入 1 和 0,您将再次得到 ZeroDivisionError 消息。

这是这个程序的新版本,divisionHandled.py。

1   print('Input two numbers. The first will be divided by the second')
2   
3   afirst = input('1st number:')
4   asecond = input('2nd number:')
5   
6   try:
7       first=float(afirst)
8       second = float(asecond)
9       quotient = first / second
10      print('Quotient 1st/2nd = ',quotient)
11  except Exception as diag:
12      print(diag.__class__.__name__,':',diag)

在本例中,我们在第 6 行的 try:子句中包含了一些代码(两个文本到浮点数的转换和除法)。如果 try:子句中的任何代码引发错误,它将被第 11 行的 except:子句捕获。

except:子句标识异常类型,并且可选地标识存储异常数据的变量。Exception 是错误类型的顶级类,因此它捕获所有错误。在 except 代码块中,代码打印诊断信息。classname 属性,用于命名错误类型。

目前为止,一切顺利。如果你玩这个程序,你可以尝试输入错误的数据,如下所示。

D:\LeanPython\programs>python divisionHandled.py
Input two numbers. The first will be divided by the second
1st number:
2nd number:
ValueError : could not convert string to float:

D:\LeanPython\programs>python divisionHandled.py
Input two numbers. The first will be divided by the second
1st number:1
2nd number:0
ZeroDivisionError : float division by zero

第一次运行时,浮点转换代码失败。通过查看代码,我们知道这是第一次转换,但不知道代码的最终用户可能会感到困惑。因此,一般来说,建议:

  • 我们处理特定的错误,而不是无所不包。

  • 给每一段代码提供自己的异常处理程序来定位错误。

这是我们完全错误处理代码的最终版本:divisionHandledV2.py。

1   print('Input two numbers. The first will be divided by the second')
2   
3   afirst = input('1st number:')
4   try:
5     first=float(afirst)
6     asecond = input('2nd number:')
7     try:
8       second = float(asecond)
9       try:
10        quotient = first / second
11        print('Quotient 1st/2nd = ',quotient)
12      except ZeroDivisionError as diag:
13        print(diag,': 2nd number must be non-zero')
14    except ValueError as diag:
15      print(diag,'2nd number')
16  except ValueError as diag:
17      print(diag,'1st number')

有点棘手的是,在测试之前很难知道会出现什么类型的错误,所以一般的方法可能是这样的:

  • 首先,在您预期会发生异常的地方捕获所有异常。

  • 其次,测试你能想到的所有异常,触发它们,并记下它们发生的位置。

  • 对于您找到的每种异常类型,创建一个特定于该异常的异常子句。

顺便说一下,请注意,如果将多个异常类型放在一个 tuple 中,就有可能捕获它们;例如:

except (ValueError, ZeroDivisionError) [as e]:

处理出现的错误的一个显而易见的替代方法是对输入数据的验证更加严格。在前面的例子中,这可能是一个更好的选择。但是,在某些情况下,无法提前检查异常,因为导致异常的数据值是中间计算的结果,可能不容易预测。

异常类型的范围很大。它们涵盖了类型转换、算术、文件 I/O、数据库访问、字典和列表元素违规等等。完整的清单见附录。

脚注

可以说,你不能通过把事情做对来学习。你只会从错误中吸取教训。你会犯很多错误;这不是问题所在。如果你不从中吸取教训,问题就来了。你可能会学到的一句口头禅是,“快速失败!”从失败中学习。

2 见【http://en.wikipedia.org/wiki/Defensive_programming】的。

八、测试您的代码

将代码模块化并进行测试 1

到目前为止,我们已经解释了如何利用 Python 的特性来创建有一定用途和价值的软件。当你写一点代码时,接下来要做的很自然的事情就是尝试或者测试它。

我用来演示 Python 特性的程序无需任何干预即可运行,或者需要用户通过 input()函数输入一些信息。随着你越来越擅长编程,你会变得更加雄心勃勃,创建更大的程序。然后你意识到测试变得更加困难,也更加重要。正如我前面所说的,将程序分成函数和模块将使测试和调试更容易。

测试驱动开发

随着你的程序变得越来越大越来越复杂,犯错误或者做出有不必要的副作用的改变的机会也会增加。那么,我们是否应该在每次做出改变时都运行之前的所有测试呢?这将是有帮助的,但是一次又一次地运行同样的测试难道不会让你厌烦吗?当然有可能。

测试驱动开发(TDD)编程方法越来越受欢迎。它是这样工作的:

  • 开发人员在编写代码之前,首先编写他们的(自动化)测试。

  • 他们运行他们的测试,看着他们失败,然后添加或修改代码使他们通过。

  • 当他们的测试通过时,他们寻找机会来改进他们代码的设计。你能想到为什么吗?

TDD 可能并不总是最好的方法,但是在编写更大的程序时,最好将代码模块化。当涉及到编写和测试类和函数时,使用单元测试框架来创建自动化测试是非常有意义的。

单元测试框架

在这一章中,我简要介绍了如何使用 unittest 框架[20]来测试 Python 模块。

假设我们需要编写一个执行简单算术的函数。向该函数传递两个数字和一个运算符,该运算符可以是“+”、“-”、“*”或“/”中的任何一个,以模拟加、减、乘或除。就这么简单。我们的函数调用可能如下所示:

result, msg = calc(12.34, '*', 98.76) 

result 将是计算的结果,如果出现错误,则为 None。如果计算由于某种原因失败,msg 将包含结果的字符串版本或错误消息。

本例中调用 calc 函数的代码行看起来像一个测试,不是吗?是的,除了我们没有检查输出(result 和 msg)是否正确。在这种情况下,我们预计:

  • 结果的值为 1218.6984。

  • msg 的值为“1218.6984”。

下面是 calc.py 文件中 calc 函数的一个可能实现:

def calc(a, op, b): 

if op not in '+-/*': 
return None, 'Operator must be +-/*' 

try: 
if op=='+': 
result=a+b 
elif op=='-': 
result=a-b 
elif op=='/': 
result=a/b 
else: 
result=a*b 
except Exception as e: 
return None,e.__class__.__name__ 

return result,str(result) 

calc 函数几乎不做检查。它不检查两个数字参数 a 和 b 的数值。之后,它尝试进行计算,但会捕获任何出现的异常,并将异常的名称传递回 msg。

现在,为了为 calc 函数创建一组测试,我们创建了一个 testcalc.py 文件,如下所示:

1   import unittest 
2   import calc 
3   # 
4   #   define the test class 
5   # 
6   class testCalc(unittest.TestCase): 
7  
8       def testSimpleAdd(self): 
9           result,msg = calc.calc(1,'+',1) 
10          self.assertEqual(result,2.0) 
11  
12      def testLargeProduct(self): 
13          result,msg = calc.calc(123456789.0, '*',987654321.0) 
14          self.assertEqual(result, 1.2193263111263526e+17) 
15      
16      def testDivByZero(self): 
17          result,msg = calc.calc(6,'/',0.0) 
18          self.assertEqual(msg,'ZeroDivisionError') 
19  # 
20  #   create the test suite 
21  # 
22  TestSuite = unittest.TestSuite() 
23  # 
24  #   add tests to the suite 
25  # 
26  TestSuite.addTest(testCalc("testSimpleAdd")) 
27  TestSuite.addTest(testCalc("testLargeProduct")) 
28  TestSuite.addTest(testCalc("testDivByZero")) 
29  # 
30  #   create the test runner 
31  # 
32  runner = unittest.TextTestRunner() 
33  # 
34  #   execute the tests 
35  # 
36  runner.run(TestSuite) 
  • 第 1 行导入了 unittest 模块,我们将使用它来测试 calc 函数。

  • 第 2 行导入 calc 模块(要测试的模块)。

  • 第 6 行定义了定义测试的类(testCalc)。

  • 第 8 行到第 18 行定义了三个测试。每个的格式都是相似的。

    • 每个测试都有一个唯一的名称(通常是 test...).

    • 它以某种方式调用要测试的函数。

    • 它执行一个断言来检查正确性(我们将在后面进一步讨论断言)。

  • 第 22 行定义了将要运行的测试套件。

  • 第 26 到 28 行将测试添加到测试套件中(注意,我们可以用不同的测试选项创建多个测试套件)。

  • 第 32 行定义了测试运行程序。

  • 第 36 行运行测试。

当我们运行测试时,我们得到这样的结果:

D:\LeanPython\programs>python testcalc.py 
... 
---------------------------------------------------- 
Ran 3 tests in 0.001s 

OK 

运行测试时会出现三个点,代表每个测试的成功执行。如果一个测试失败了,我们会看到一个 Python 错误消息,指出异常和程序中失败发生的行号。

仅仅为了运行几个测试而编写的所有代码可能看起来有点多余。也许这看起来有点罗嗦,但每个电话都有目的。但是,请注意,一旦设置好了,如果我想添加一个新的测试,我会创建一个新的测试调用(例如,第 8–10 行)并将该测试添加到套件中(例如,第 26 行)。因此,一旦你设置好了,创建大量的测试就很容易了。calc 函数是一个非常简单的例子。更现实(和复杂)的类和函数有时需要 20 或 30 甚至数百次测试。

注意

将模块作为开源库提供的程序员通常会在他们的模块中包含一大套测试。 2

断言

良好测试的关键是选择应用于代码的输入或激励(函数调用;例如,第 9 行)和对结果执行的检查。这些检查是作为断言实现的。unittest 模块提供了大约 20 种不同的断言变体。例子包括如下:

  • 断言相等。这种变化断言两个值(结果和您的预测结果)完全相等。

  • assertTrue。一个表达式是真的吗?

  • assertIn。是序列中的值吗?

  • assertGreaterEqual。一个值是否大于或等于另一个值?

更复杂的测试场景

unittest 框架有更多的特性。例如 TestCase 类中的 setup()和 teardown()方法。这些方法是在每个测试用例之前和之后自动调用的,以执行一个标准的设置(变量、数据或环境)来允许每个测试正确运行。测试后,拆卸过程会进行整理(如有必要)。

注意

我们现在已经介绍了 Python 语言的基本元素,并探索了用于测试的 unittest 模块。现在,让我们看看使用一些流行的库来做一些有用的事情。

脚注

大多数程序被分成模块,由程序员分别测试。这种测试通常被称为单元或组件测试。

如果他们不这样做,也许他们的模块应该被避免。

九、访问网络

Python 有标准的 1 库,使程序员能够编写使用和实现互联网服务的客户机和服务器,比如电子邮件、文件传输协议(FTP),当然还有网站。

在这一章中,我们来看看如何使用 Python 来访问网站和服务。假设您想从网站下载一个页面,并保存检索到的 HTML。用户需要输入该网站的 URL。也许您希望能够向 URL 添加一个查询字符串,以便将数据传递给请求,然后您希望显示响应或将其保存到磁盘。

当然,你应该设计你的程序分阶段工作:

  1. 向用户询问 URL。

  2. 要求将查询字符串追加到 URL。

  3. 询问是否保存到磁盘。

这里显示了 webtest.py 程序的列表。

1   import requests
2   from urllib.parse import urlparse
3   
4   url=input('Web url to fetch:')
5   urlparts=urlparse(url)
6   if urlparts[0]=='':
7       url=''.join(('http://',url))
8   
9   qstring=input('Enter query string:')
10  if len(qstring)>0:
11      url='?'.join((url,qstring))
12  
13  save=input('Save downloaded page to disk [y/n]?')
14      
15  print('Requesting',url)
16  
17  try:
18      response = requests.get(url)
19      if save.lower()=='y':
20          geturl=response.url
21          urlparts=urlparse(geturl)
22          netloc=urlparts[1]
23          if len(netloc)==0:
24              fname='save.html'
25          else:
26              fname='.'.join((netloc,'html'))
27          print('saving to',fname,'...')
28          fp=open(fname,'w')
29          fp.write(response.text)
30          fp.close()
31      else:
32          print(response.text)
33  except Exception as e:
34      print(e.__class__.__name__,e)

让我们浏览一下这个程序。 2

  • 第 1 行和第 2 行导入所需的模块(请求和 urlparse)。

  • 第 4 行到第 7 行从用户那里获得一个 URL。如果用户不包括 URL 的 http://部分,程序会添加前缀。

  • 第 10 到 12 行要求用户输入一个查询字符串,并用?性格。

  • 第 14 行到第 16 行询问用户是否希望将输出保存到文件中,然后打印所请求的完整 URL。

  • 第 18 到 40 行完成了大部分工作;任何异常都被第 34 行和第 35 行捕获。

  • 第 19 行获取 URL 并保存响应。

  • 第 20 到 31 行根据网站的 URL 创建一个文件名(或使用 save.html ),并将输出保存到该文件。

  • 第 33 行将响应内容打印到屏幕上。

当我运行这个程序时,我看到的是:

D:\LeanPython\programs>python webtest.py
Web url to fetch:uktmf.com
Enter query string:q=node/5277
Save downloaded page to disk [y/n]?y
Requesting http://uktmf.com?q=node/5277
saving to uktmf.com.html ...

d:\LeanPython\programs>

下载页面的内容保存在 uktmf.com.html。

请求库非常灵活,因为您可以使用 requests.post()访问 HTTP“post”动词。

您可以为 post 命令提供数据,如下所示:

data = {'param1': 'value 1','param2': 'value 2'}
response = request.post(url,data=data)

在网站或 web 服务需要的地方,您可以提供用于身份验证的凭证,并以 JSON 数据的形式获取内容。您可以为请求提供定制的头,也可以很容易地看到响应中返回的头。

requests 模块可用于全面测试网站和 web 服务。

脚注

本章假设读者对 web 服务器、浏览器和 HTML 的操作有所了解。

是的,这是一个程序,第一个真正做一些你可能会发现有用的事情的程序。

十、搜索

搜索字符串

在字符串中搜索文本是一种常见的活动,内置的字符串函数 find()是简单搜索所需要的。它返回查找的位置(偏移量),如果没有找到则返回–1。

>>> txt="The quick brown fox jumps over the lazy dog"
>>> txt.find('jump')
20
>>> txt.find('z')
37
>>> txt.find('green')
-1

更复杂的搜索

通常情况下,搜索并不那么简单。我们需要寻找一种模式,从匹配的文本中提取我们真正想要的信息,而不是简单的字符串。例如,假设我们想要提取网页上链接中的所有 URL。这里有一些来自真实网页的 HTML 文本的例子。

1   <link rel="alternate" type="application/rss+xml" title="RSS: 40 newest packages" href="https://pypi.python.org/pypi?:action=packages_rss"/>
2   <link rel="stylesheet" media="screen" href="/static/styles/screen-switcher-default.css" type="text/css"/>
3   <li><a class="" href="/pypi?%3Aaction=browse">Browse&nbsp;packages</a></li>
4   <li><a href="http://wiki.python.org/moin/CheeseShopTutorial">PyPI Tutorial</a></li>

这篇文章中有很多内容。

  • 第 1 行引用了一个 RSS 提要。

  • 第 2 行有一个 href 属性,但是它引用了一个级联样式表(CSS)文件,而不是一个链接。

  • 第 3 行是一个真实的链接,但 URL 是相对的;它不包含 URL 的网站部分。

  • 第 4 行是到外部站点的链接。

怎么才能希望用一些软件找到自己关心的链接呢?这就是正则表达式的用武之地。

引入正则表达式 1

正则表达式 2 是一种利用模式匹配找到我们感兴趣的文本的方法。不仅模式匹配,re 模块还可以从匹配的文本中提取出我们真正想要的数据。

还可以写出更多的例子,事实上有很多关于正则表达式的书籍(例如,[16],[17])。有许多网站,但最有用的可能是 http://www . regular-expressions . info。

注意

正则表达式是包含文本和特殊字符的字符串,这些字符定义了 re 函数可以用来进行匹配的模式。

简单搜索

最简单的正则表达式是你想在另一个字符串中找到的文本字符串,如表 10-1 所示。

表 10-1。查找简单的字符串
|

正则表达式

|

字符串匹配

|
| --- | --- |
| 跳跃 | 跳跃 |
| 女王 | 女王 |
| Pqr123 | Pqr123 |

使用特殊字符

表 10-2 中列出了一些特殊字符,它们会影响比赛的进行方式。

表 10-2。使用特殊字符
|

标志

|

描述

|

例子

|
| --- | --- | --- |
| 逐字的 | 匹配文字字符串 | 跳跃 |
| re1|re2 | 匹配字符串 re1 或 re2 | 是|否 |
| 。 | 匹配任何单个字符(除了\n) | 国会议员 |
| ^ | 匹配字符串的开头 | 这 |
| $ | 匹配字符串结尾 | 井$ |
| * | 匹配前面正则表达式的 0 次或多次出现 | [阿-兹]* |
| + | 匹配前面正则表达式的一次或多次出现 | [A-Z]+ |
| ? | 匹配前面正则表达式的 0 次或 1 次出现 | 【a-z0-9】? |
| {m,n} | 匹配前面正则表达式的 m 个和 n 个匹配项(n 可选) | [0-9]{2,4} |
| [...] | 匹配字符类中的任何字符 | [aeiou] |
| [x-y] | 匹配范围内的任何字符 | [0-9],[A-Za-z] |
| [...] | 不匹配字符类中的任何字符 | [^aeiou] |

表 10-3 中列出了许多可以匹配的特殊字符。

表 10-3。使用特殊字符搜索
|

特殊字符

|

描述

|

例子

|
| --- | --- | --- |
| \d | 匹配任何十进制数字 | 英国广播公司\d |
| \w | 匹配任何字母数字字符 | 收音机\w+ |
| \s | 匹配任何空白字符 | \sBBC |

表 10-4 给出了一些正则表达式的例子以及它们匹配的字符串。

表 10-4。正则表达式和匹配字符串
|

正则表达式

|

匹配的字符串

|
| --- | --- |
| 史密斯|琼斯 | 琼斯·史密斯 |
| 一个-什么或者说 | UN 和 O 之间的任意两个字符;例如教科文组织、联合国教科文组织、联合国教科文组织 |
| 这 | 任何以 |
| 结束$ | 任何以 end 结尾的字符串 |
| c[阿伊]t | cat、cit、cot、cut |
| [dg][io][gp] | dig,dip,dop,gig,gip,gog,gop |
| [阿-德][英-意] | 2 个字符 a/b/c/d,后跟 e/f/g/h/i |

注意正则表达式可以使用文本和特殊字符的任意组合,所以它们有时看起来非常复杂。从简单开始。

在文本中查找模式

在文本中查找子字符串是没问题的,但是我们经常希望在文本中查找模式,而不是文字字符串。假设我们想从文本中提取数值、电话号码或网站 URL。我们如何做到这一点?这就是正则表达式的真正威力所在。

下面是一个正则表达式示例:

\s[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}[\s]

你能猜到它会发现什么吗?这是一个在文本中查找电子邮件地址的正则表达式。乍一看,这看起来很令人生畏,所以让我们把它分成几个组成部分。 3

这个正则表达式有六个元素:

| 1 \s2 [A-Z0-9。_%+-]+3   @4[A-Z0 9。-]+5   \.6 [A-Z]{2,4}7 [\s\] | 前导空白一个或多个字符@字符 A-Z,0-9。-点字符 2 到 4 个文本字符空白或句号 |

显然,您需要知道您所搜索的模式的规则,并且有构建电子邮件地址的特定规则。

下面是文件 remail.py。

1   import re         # The RegEx library
2   #
3   # our regular expression (to find e-mails)
4   # and text to search
5   #
6   regex = '\s[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}[\s]'
7   text="""This is some text with x@y.z embedded e-mails
8   that we'll use as@example.com
9   some lines have no email addresses
10  others@have.two valid email@addresses.com
11  The re module is awonderful@thing."""
12  print('** Search text ***\n'+text)
13  print('** Regex ***\n'+regex+'\n***')
14  #
15  #   uppercase our text
16  utext=text.upper()
17  #
18  #   perform a search (any emails found?)
19  s = re.search(regex,utext)
20  if s:
21      print('*** At least one email found "'+s.group()+'"')
22  #
23  #   now, find all matches
24  #
25  m = re.findall(regex,utext)
26  if m:
27      for match in m:
28          print('Match found',match.strip())
  • 第 1 行导入了我们需要的模块。

  • 第 6 到 13 行定义了要搜索的文本字符串和我们将使用的正则表达式,然后打印它们。

  • 第 16 行大写字母代表文本。

  • 第 19 到 21 行对第一封(任何)电子邮件执行简单的搜索,并打印结果。请注意,匹配包含前导和尾随空白。

  • 第 25 到 28 行查找文本中的所有匹配项并打印结果。

注意,正则表达式匹配电子邮件地址和空白边界。在第 21 行中,我们打印了包含尾随换行符的匹配,但是在第 28 行中,我们去掉了多余的字符。

当我们运行这段代码时会得到什么?这是结果。

D:\LeanPython\programs\Python3>python remail.py
** Search text ***
This is some text with x@y.z embedded emails
that we'll use as@example.com
some lines have no email addresses
others@have.two valid email@addresses.com
The re module is awonderful@thing.
** Regex ***
\s[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}[\s]
***
*** At least one email found " AS@EXAMPLE.COM
"
Match found AS@EXAMPLE.COM
Match found OTHERS@HAVE.TWO
Match found EMAIL@ADDRESSES.COM

捕捉括号

我们应该提到的另一个方面是圆括号的使用。可以像搜索任何其他字符一样搜索它们,但是也可以使用它们来描述匹配的子字符串,re 模块可以捕获这些子字符串,并将它们放在搜索过程返回的列表中。在下面的例子中,这些所谓的捕捉括号提供了我们想要从 HTML 页面中提取的 URL。

在 HTML 中查找链接

以下程序使用 urlliblibrary 下载单个网页。然后使用复杂的正则表达式搜索下载的 HTML 内容的文本,该表达式提取文本链接并提供 URL 和用户看到的链接文本。

这个程序叫做 regex.py。

1   import urllib.request
2   import re         # The RegEx library
3   #
4   #   this code opens a connection to the leanpy.com website
5   #
6   response = urllib.request.urlopen('http://leanpy.com')
7   data1 = str(response.read())           # put response text in data
8   #
9   #   our regular expression (to find links)
10  #
11  regex = '<a\s[^>]*href\s*=\s*\"([^\"]*)\"[^>]*>(.*?)</a>'
12  #
13  #   compile the regex and perform the match (find all)
14  #
15  pm = re.compile(regex)
16  matches = pm.findall(data1)
17  #
18  #   matches is a list
19  #   m[0] - the url of the link
20  #   m[1] - text associated with the link
21  #
22  for m in matches:
23      ms=''.join(('Link: "',m[0],'" Text: "',m[1],'"'))
24      print(ms)

此处显示了该程序的输出。

1   D:\LeanPython\programs>python re.py
2   200 OK
3   Link: "http://leanpy.com/" Text: "Lean Python            "
4   Link: "#content" Text: "Skip to content"
5   Link: "http://leanpy.com/" Text: "Home"
6   Link: "http://leanpy.com/?page_id=33" Text: "About Lean Python            "
7   Link: "http://leanpy.com/" Text: "<img src="http://leanpy.com/wp-content/uploads/2014/04/cropped-LeanPythonHeader.jpg" class="header-image" width="950" height="247" alt="" />"
8   Link: "http://leanpy.com/?p=1" Text: "The Lean Python             Pocketbook"
9   Link: "http://leanpy.com/?p=1#respond" Text: "<span class="leave-reply">Leave a reply</span>"
10  Link: "http://leanpy.com/wp-content/uploads/2014/04/OnePieceCover1-e1396444631642.jpg" Text: "<img class="wp-image-17 alignleft" alt="OnePieceCover" src="http://leanpy.com/wp-content/uploads/2014/04/OnePieceCover1-e1396444631642-633x1024.jpg" width="305" height="491" />"
11  Link: "http://leanpy.com/?cat=3" Text: "Lean Python             Book"
12  Link: "http://leanpy.com/?tag=book" Text: "Book"
13  Link: "http://leanpy.com/?p=1" Text: "<time class="entry-date" datetime="2014-04-02T12:06:06+00:00">April 2, 2014</time>"
14  Link: "http://leanpy.com/?author=1" Text: "paulg"
15  Link: "http://leanpy.com/?p=1" Text: "The Lean Python             Pocketbook"
16  Link: "http://leanpy.com/?cat=3" Text: "Lean Python             Book"
17  Link: "http://leanpy.com/wp-login.php?action=register" Text: "Register"
18  Link: "http://leanpy.com/wp-login.php" Text: "Log in"
19  Link: "http://leanpy.com/?feed=rss2" Text: "Entries <abbr title="Really Simple Syndication">RSS</abbr>"
20  Link: "http://leanpy.com/?feed=comments-rss2" Text: "Comments <abbr title="Really Simple Syndication">RSS</abbr>"
21  Link: "http://wordpress.org/" Text: "WordPress.org"
22  Link: "http://wordpress.org/" Text: "Proudly powered by WordPress"

你可以看到程序识别了所有的链接,但是还没有我们希望的那么智能。

  • 第 4 行:这个链接使用了同一个页面的书签。

  • 第 7 行:链接文本实际上是一个图像(我们需要担心这个吗?).

作为练习,也许您可以改进所使用的正则表达式。

脚注

Python re 模块的完整文档可以在 https://docs.python.org/3/library/re.html 的 ?? 找到。正则表达式是任何编程语言中的高级主题。

2 通常,正则表达式简称为 regex。

请注意,这个电子邮件查找器 regex 并不完美。它不会在字符串的开头找到地址,并且会忽略尾部元素中超过四个字符的电子邮件地址(例如。手机)。

十一、数据库

每个 1 应用都使用某种形式的(持久)存储。我们已经看过纯文本文件了。在这一章中,我们考虑 Python 程序如何访问和使用数据库,尤其是关系数据库。

Python 提供了访问所有流行数据库的标准函数。有许多开源和商业数据库产品,每个都有自己的适配器,允许 Python 连接和使用其中保存的数据。出于我们的目的,我们使用 SQLite 数据库,因为它不需要安装其他软件。

数据库

SQLite 是一个非常轻量级的无服务器工具。核心 Python 产品包括 SQLite 适配器,允许我们演示最重要的数据库特性。SQLite 的行为方式与大型系统相同,但是管理开销很低(接近于零)。这样做的结果是 SQLite 可以用于开发或原型制作,以后可以迁移到更复杂的数据库。出于我们的目的,SQLite 提供了我们需要的所有特性。

数据库功能

这些是我们将使用的关键 SQLite 数据库函数:

# open (or create) a database file and return
# the connection
conn = sqlite3                .connect(filename)

# executes a SQL statement
conn.executescript(sql)

# return a cursor
cursor = conn.cursor()

# execute the SQL query that returns rows of data
cursor.execute(sql)

# returns the data as a list of rows
rows = cursor.fetchall() 

将数据连接并加载到 SQLite 中

下面是一个示例程序,它创建一个新的数据库,一个单独的表,插入一些数据,执行一个查询,并尝试插入一个重复的行(dbcreate.py)。

1   import os
2   import sqlite3
3   
4   db_filename='mydatabase.db'
5   #
6   #   if DB exists - delete it
7   #
8   exists = os.path.exists(db_filename)
9   if exists:
10      os.unlink(db_filename)
11  #
12  #   connect to DB (create it if it doesn't exist)
13  #
14  conn = sqlite3.connect(db_filename)
15  #
16  #   create a table
17  #
18  schema="""create table person (
19    id integer primary key autoincrement not null,
20    name text not null,
21    dob  date,
22    nationality text,
23    gender text)
24   """
25  conn.executescript(schema)
26  #
27  # create some data
28  #
29  people="""insert into person (name, dob,nationality,gender)
30  values ('Fred Bloggs', '1965-12-25','British','Male');
31  insert into person (name, dob,nationality,gender)
32  values ('Santa Claus', '968-01-01','Lap','Male');
33  insert into person (name, dob,nationality,gender)
34  values ('Tooth Fairy', '1931-03-31','American','Female');
35  """
36  conn.executescript(people)
37  #
38  #   execute a query
39  #
40  cursor = conn.cursor()
41  cursor.execute("select id, name, dob,nationality,gender from person")
42  for row in cursor.fetchall():
43      id, name, dob,nationality,gender = row
44      print("%3d %15s %12s %10s %6s" % (id, name, dob,nationality,gender))
45  #
46  #   attempt to insert a person with no name
47  #
48  try:
49      dupe="insert into person (id, dob,nationality,gender) \
50      values (1,'1931-03-31','American','Female');"
51      conn.executescript(dupe)
52  except Exception as e:
53      print('Cannot insert record',e.__class__.__name__)
  • 第 1 行和第 2 行导入了我们需要的模块。

  • 第 4 行到第 10 行删除一个旧的数据库文件(注意不要将在这个程序中创建的数据库用于任何有用的用途!).

  • 第 14 行创建数据库文件。

  • 在第 18 到 25 行,模式是一组命令(一个 SQL 脚本),它将创建一个新表。

  • 第 26 行执行 SQL 脚本来创建新表。

  • 在第 29 到 36 行中,定义了一个新的脚本,其中包含在新表中插入三条记录的 SQL 命令。

  • 第 37 行执行脚本。

  • 在第 40 到 44 行,要执行一个查询,您需要创建一个游标,然后使用该游标执行查询。这将建立查询内容,但不会获取数据。cursor.fetchall()提供了一个可迭代的行列表,这些行被分配给命名变量,然后打印出来。

  • 第 48 行到第 53 行建立了一个行的插入,try…except 子句捕获插入中的错误。insert SQL 故意省略了 name 字段来触发异常。

这个程序的输出显示为 hee。

D:\LeanPython\programs>python dbcreate.py
  1     Fred Bloggs   1965-12-25    British   Male
  2     Santa Claus    968-01-01        Lap   Male
  3     Tooth Fairy   1931-03-31   American Female
Cannot insert record IntegrityError

第 52 行的 insert 语句导致的异常被触发,因为没有提供 name 字段(并且必须不为 null)。

在下面的清单(dbupdate.py)中,我们向程序传递两个参数,并在 SQL update 命令中使用它们来更改一个人的国籍。

1   import sqlite3
2   import sys
3   #
4   #   arguments from command line
5   #   use: python dbupdate.py   1  Chinese
6   #
7   db_filename = 'mydatabase.db'
8   inid = sys.argv[1]
9   innat = sys.argv[2]
10  #
11  #   execute update using command-line arguments
12  #
13  conn = sqlite3.connect(db_filename)
14  cursor = conn.cursor()
15  query = "update person set nationality = :nat where id = :id"
16  cursor.execute(query, {'id':inid, 'nat':innat})
17  #
18  #   list the persons to see changes
19  #   
20  cursor.execute("select id, name, dob,nationality,gender from person")
21  for row in cursor.fetchall():
22      id, name, dob,nationality,gender = row
23      print("%3d %15s %12s %10s %6s" % (id, name, dob,nationality,gender))
  • 第 8 行和第 9 行从命令行获取数据:inid 和 innat。

  • 第 13 行到第 16 行完成了大部分工作。第 13 和 14 行设置了光标。第 15 行和以前一样是 SQL,但是用于 SQL 中的字段(id 和 nat)的值是使用冒号符号(:id 和:nat)参数化的。第 16 行执行查询,并使用字典作为调用{'id':inid,' nat':innat}的第二个参数来提供参数的实际值。

这里显示了输出。

D:\LeanPython\programs>python dbupdate.py 1 Chinese
  1     Fred Bloggs   1965-12-25    Chinese   Male
  2     Santa Claus    968-01-01        Lap   Male
  3     Tooth Fairy   1931-03-31   American Female

冒号符号和字典可用于参数化任何 SQL 调用。

脚注

本章假定您了解关系数据库模型和简单结构化查询语言(SQL)命令。

十二、接下来呢?

在这本小书中,我介绍了我在自己的 Python 开发中使用的核心 Python 特性。如果您完成了所有的示例,使用交互式解释器进行了实验,并且使用了示例程序,那么您将会很好地掌握这种出色的编程语言的最基本的元素。

不过,Python 的其他方面可能看起来相当神秘。关于正则表达式、web 应用和 SQLite 的章节只是为了激起您学习更多内容的兴趣。

如果你像我一样,有编程缺陷,将没有什么能阻止你探索这门语言,以及你能用它做什么。如果你是一个使用另一种语言的有经验的程序员,我希望你能理解 Python 是如何工作的,以及编写代码的容易程度。您甚至可能会考虑放弃您的旧语言,转而使用 Python。你们中的一些人可能已经看够了。编程、Python 和所有那些废话可能不适合你。你以前怀疑过,至少现在你确定了。

如果你选择更进一步,这里有一些建议:

  1. 1.购买一本好的语言参考书,或者熟悉附录中给出的在线 Python 参考资料。

  2. 2.探索 PyPI 资源。无论你想用代码做什么,别人已经创建了一个库,这将使你的生活变得更加容易。利用这一点。

  3. 3.练习。就这么简单。就像口语和许多其他技能一样,如果你不使用它,你就会失去它。如果本周你不用 Python 来工作,那就用它来娱乐吧。

如果你用 Python 走得更远,我知道你会玩得很开心!

附录

参考

网页 1

  1. 1.www.artima.com/intv/pythonP.html。访 Python 的发明者吉多·范·罗苏姆。

  2. 2.en . Wikipedia . org/wiki/Python _(programming _ language。Python 语言的维基百科条目。

  3. 3.web2py.com。Web2py web 开发框架。

  4. 4.legacy.python.org/dev/peps/pep-0008/。Python 代码的(PEP 8)风格指南。

  5. 5.legacy.python.org/dev/peps/pep-0020/。Python 的禅。

  6. 6.wiki.python.org/moin/Python2orPython3。我应该用 Python 2 还是 Python 3?

  7. 7.pypi . python . org/pypi。Python 软件包索引。

  8. 8.docs.python.org/3/using/cmdline.html。使用 Python 命令行环境。

  9. 9.www.python.org。Python 语言的官方网站。

  10. 10.docs.python.org/。Python 标准文档。

  11. 11.docs.python.org/3/library/index.html。Python 标准库。

  12. 12.legacy.python.org/dev/peps/pep-0020/。Python 的禅(2014)。

  1. 13.核心 Python 编程,卫斯理·淳。

  2. 14.Python 标准库举例, Doug Hellmann。

  3. 15.Alex Martelli,David Ascher 和 Anna Martelli Ravenscroft。

  4. 16.掌握 Python 正则表达式,菲利克斯·洛佩兹和维克多·罗梅罗。

  5. 17.掌握正则表达式,杰弗里·弗里德尔。

工具

  1. 18.视觉塞斯尔:www.obelisk.me.uk/cesil/(2016)。

  2. 19.PIP 安装程序:www . pip-installer . org/(2014 年)。

  3. 20.Python 单元测试框架:docs.python.org/3.4/library/unittest.html

Python 内置的异常层次 2

在第七章中,我们描述了 Python 如何管理异常。我们在那里介绍了一些异常类型,但这里是完整的列表。

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
                   +-- ImportWarning
                   +-- UnicodeWarning
                   +-- BytesWarning

脚注

1 这些 URL 在发布时有效,但可能会发生变化。

2 摘自 https://docs.python.org/3/library/exceptions.html。

posted @ 2024-08-10 15:27  绝不原创的飞龙  阅读(2)  评论(0编辑  收藏  举报