Open xuzhengfu opened 4 years ago
前面我们深入学习了函数的方方面面,就像打开了一个琳琅满目的工具箱,但如果没有用武之地,再强大的工具也会变得无趣,来自现实世界里的各种数据,提供了函数们施展拳脚的空间。
我们编写程序是为了让计算机帮助我们解决现实世界的问题,我们一般会对这些问题建立一个数据模型,用计算机的数据结构来表达这个模型,然后编写合适的算法(函数)来处理这个模型,最终得到我们要的结果。计算机程序千变万化,但不离其宗。
从这一章开始,我们就要进入 “数据” 的主题,来看看程序中常用的 数据模型 和 结构 有哪些,又该如何使用它们。
你已经学习了 Python 中最基本的数据类型:整数、浮点数、布尔、字符和字符串,这些类型能很好的解决现实世界里的这几种基础数据: 整数、小数; 逻辑真值和假值; 一般的文本。
你已经学习了 Python 中最基本的数据类型:整数、浮点数、布尔、字符和字符串,这些类型能很好的解决现实世界里的这几种基础数据:
在接下去的几章,你将学到更多现实世界常见数据的表示和处理方法,比如: 日期时间; 电话号码、Email 等格式化文本; 容纳一组数据,方便我们批量处理的数据容器(data container); 如何定制更复杂的数据结构来解决问题,比如树型图(tree graph)。 同时,你也会学习其他非常重要的知识,比如迭代器(iterator)模型 —— 这是 Python 中所有数据容器的基础。
在接下去的几章,你将学到更多现实世界常见数据的表示和处理方法,比如:
同时,你也会学习其他非常重要的知识,比如迭代器(iterator)模型 —— 这是 Python 中所有数据容器的基础。
在这一章先来看看如何用字符串表示各种数据。
你在之前专门用了一章来学习 Python 的字符串,因为字符串实在是太重要了,和整数、浮点数、布尔等数据类型相比,字符串的应用要广泛得多也灵活得多。任何人类触及的数据,进入人眼,都可以视为一串文本,我们在看的书是文本,网页是文本,对话可以录下来再转换成文本,数据报表是文本,歌曲的乐谱和歌词都是文本,只要我们愿意,我们可以用字符串表达任何数据。 要和别人交换信息时,写一个邮件或者打印一张报告,本质都是文本;当某个程序要和别的程序交换数据时,一串约定好格式的字符串是最容易处理、兼容性最好的方法。 所以字符串在编程中有非常独特的地位,我们经常需要做的,就是把复杂的数据变成一个字符串,保存下来,传给别人或者别的程序;再把收到的字符串,按照约定好的格式解析出来,还原成各种数据。
你在之前专门用了一章来学习 Python 的字符串,因为字符串实在是太重要了,和整数、浮点数、布尔等数据类型相比,字符串的应用要广泛得多也灵活得多。任何人类触及的数据,进入人眼,都可以视为一串文本,我们在看的书是文本,网页是文本,对话可以录下来再转换成文本,数据报表是文本,歌曲的乐谱和歌词都是文本,只要我们愿意,我们可以用字符串表达任何数据。
要和别人交换信息时,写一个邮件或者打印一张报告,本质都是文本;当某个程序要和别的程序交换数据时,一串约定好格式的字符串是最容易处理、兼容性最好的方法。
所以字符串在编程中有非常独特的地位,我们经常需要做的,就是把复杂的数据变成一个字符串,保存下来,传给别人或者别的程序;再把收到的字符串,按照约定好的格式解析出来,还原成各种数据。
下面我们就来看看如何用字符串表示一些常见数据,数字、日期时间,还有电话号码、Email 等。
把 数值类型 转换为 字符串 很简单,用我们讲的 f-string 就可以轻易做到,比如:
n = 440312 f = 3.1415926 s1 = f"{n}" s2 = f"{f}"
要把 包含数值类型数据的字符串 转换成 对应的类型 则借助内置函数 int() 和 float():
int()
float()
int(s1) float(s1) # 字符串里包含的如果是整数,则不仅可以转为整数,也可以转为等值的浮点数
# 字符串里包含的如果是浮点数,则可以转为浮点数,但不能直接转为整数 float(s2) # int(s2) 会导致 运行时错误:ValueError: invalid literal for int() with base 10,因为 '3.1415926' 无法转换为一个整数。
但下面这句是可以的,内层函数 float(s2) 返回是浮点数 3.1415926,外层函数相当于 int(3.1415926),效果是取整,结果是整数 3: int(float(s2))
但下面这句是可以的,内层函数 float(s2) 返回是浮点数 3.1415926,外层函数相当于 int(3.1415926),效果是取整,结果是整数 3:
float(s2)
int(3.1415926)
int(float(s2))
s3 = "440abc312" s4 = "3.l4l5g26" 对上面两个字符串,如果我们尝试调用 int(s3) int(s4) float(s3) float(s4),也会出 ValueError 型的运行时错误,因为 s3 和 s4 的内容根本不是合法的数值。正如我们讲异常处理时说的:编写程序时并不知道自己可能会处理的数据是什么,有些输入如我们所想,有些则未必,当我们收到一个字符串而认为它 “应该是” 某种东西时,要格外注意,如果它不是会怎样,一个好的程序应该能正确处理那些即使 “不正常” 的输入,得到一个合理的(reasonable)结果。 我们把一个字符串转换为数值,比较安全的做法是这样的: def is_float(s): try: float(s) return True except ValueError: return False def is_int(s): try: int(s) return True except ValueError: return False def parse_str(s): if is_int(s): print(f"输入是整数,其值为 {int(s)}。") elif is_float(s): print(f"输入是浮点数,其值为 {float(s)}。") else: print(f"输入“{s}”不是数值类型。") 在上面我们先定义了两个辅助函数 is_float(s) 和 is_int(s),来判断输入的字符串是不是浮点数或者整数,或者更准确的说,输入是不是可以转换为浮点数和整数,判断的方法是利用 Python 提供的 “异常处理” 机制,就是尝试用 float() 或者 int() 函数来把字符串转换为浮点数或者整数,如果有 ValueError 异常抛出就说明输入的字符串不能转换。 随后在 parse_str() 函数中就用上述辅助函数先判明输入的情况,然后再做处理,这样就安全多了。
s3 = "440abc312" s4 = "3.l4l5g26"
对上面两个字符串,如果我们尝试调用 int(s3) int(s4) float(s3) float(s4),也会出 ValueError 型的运行时错误,因为 s3 和 s4 的内容根本不是合法的数值。正如我们讲异常处理时说的:编写程序时并不知道自己可能会处理的数据是什么,有些输入如我们所想,有些则未必,当我们收到一个字符串而认为它 “应该是” 某种东西时,要格外注意,如果它不是会怎样,一个好的程序应该能正确处理那些即使 “不正常” 的输入,得到一个合理的(reasonable)结果。
int(s3)
int(s4)
float(s3)
float(s4)
ValueError
我们把一个字符串转换为数值,比较安全的做法是这样的:
def is_float(s): try: float(s) return True except ValueError: return False def is_int(s): try: int(s) return True except ValueError: return False def parse_str(s): if is_int(s): print(f"输入是整数,其值为 {int(s)}。") elif is_float(s): print(f"输入是浮点数,其值为 {float(s)}。") else: print(f"输入“{s}”不是数值类型。")
在上面我们先定义了两个辅助函数 is_float(s) 和 is_int(s),来判断输入的字符串是不是浮点数或者整数,或者更准确的说,输入是不是可以转换为浮点数和整数,判断的方法是利用 Python 提供的 “异常处理” 机制,就是尝试用 float() 或者 int() 函数来把字符串转换为浮点数或者整数,如果有 ValueError 异常抛出就说明输入的字符串不能转换。
is_float(s)
is_int(s)
随后在 parse_str() 函数中就用上述辅助函数先判明输入的情况,然后再做处理,这样就安全多了。
parse_str()
将字符串解读然后转换成某种特定类型的数据,通常我们用一个词 parse,我们在这个文档里讲的就是把字符串 parse 成各种类型数据。 Parsing a string is difficult. 所以我们一般情况下都会用现成的、对各种情况都有周到处理的第三方模块来做,比如把字符串 parse 成数值可以考虑 fastnumber 这个模块,有比内置函数更好的性能和更清晰易用的接口设计。 在命令行用 pip3 install fastnumbers 来安装这个模块。 $ pip3 install fastnumbers Collecting fastnumbers Downloading fastnumbers-3.0.0-cp37-cp37m-macosx_10_6_intel.whl (43 kB) |████████████████████████████████| 43 kB 61 kB/s Installing collected packages: fastnumbers Successfully installed fastnumbers-3.0.0 这个 fastnumbers 甚至可以转换各种奇怪的数字字符,随便写写的肯定做不到这种程度。如果我们必须自己写这样的代码,那就是对我们的思维严谨和周密性的考验了。有兴趣的话可以看一看 Python int() 方法的 源代码,或者 fastnumber 库的 源代码,不过这些源代码都是用 C 语言写的(Python 的官方解释器就是用 C 写的,所以叫 CPython)。
将字符串解读然后转换成某种特定类型的数据,通常我们用一个词 parse,我们在这个文档里讲的就是把字符串 parse 成各种类型数据。
Parsing a string is difficult. 所以我们一般情况下都会用现成的、对各种情况都有周到处理的第三方模块来做,比如把字符串 parse 成数值可以考虑 fastnumber 这个模块,有比内置函数更好的性能和更清晰易用的接口设计。
在命令行用 pip3 install fastnumbers 来安装这个模块。
pip3 install fastnumbers
$ pip3 install fastnumbers Collecting fastnumbers Downloading fastnumbers-3.0.0-cp37-cp37m-macosx_10_6_intel.whl (43 kB) |████████████████████████████████| 43 kB 61 kB/s Installing collected packages: fastnumbers Successfully installed fastnumbers-3.0.0
这个 fastnumbers 甚至可以转换各种奇怪的数字字符,随便写写的肯定做不到这种程度。如果我们必须自己写这样的代码,那就是对我们的思维严谨和周密性的考验了。有兴趣的话可以看一看 Python int() 方法的 源代码,或者 fastnumber 库的 源代码,不过这些源代码都是用 C 语言写的(Python 的官方解释器就是用 C 写的,所以叫 CPython)。
fastnumber
语言定义的日期时间 数据类型,通常包含年、月、日、时、分、秒等属性,不同的编程语言的实现可能有所不同,有的语言还支持 Unix 式时间戳(Unix timestamp),也就是用 1970 年 1 月 1 日零时整开始流逝的秒数来表示的时间; 用字符串表示的日期时间,比如 “2019-07-16 18:05:33”,不同国家地区对日期的表示格式是不一样的,比如我们中国习惯于“年-月-日”,美国习惯“月/日/年”,而世界上大部分其他国家都是“日/月/年”;时间的表示则有 12 小时和 24 小时制的区别,AM/PM 的不同写法等问题;还有遗留 数据用两位数字表示年份带来的臭名昭著的 “千年虫” 问题;所以在处理 字符串表示的日期时间 时要非常小心地约定好表示格式; 与日期时间关联的时区,如果不指明时区,时间的表示就无意义,但很多软件系统里的时间是不管时区的,一旦需要跨时区使用就会带来一大堆麻烦,比如你带着你的手机出国,手机自动切换到目的地时区,然后你拍的照片、发出的邮件都是目的地的时间,如果不做处理,当你回到常居地这些照片和邮件的时间就错了; 和日历有关的一系列处理,比如今天星期几?去年第二个月有多少天、最后一天是星期几?从现在开始加上 10000 天是什么日子?通常会有一个专门的日历模块来处理这类问题。
语言定义的日期时间 数据类型,通常包含年、月、日、时、分、秒等属性,不同的编程语言的实现可能有所不同,有的语言还支持 Unix 式时间戳(Unix timestamp),也就是用 1970 年 1 月 1 日零时整开始流逝的秒数来表示的时间;
用字符串表示的日期时间,比如 “2019-07-16 18:05:33”,不同国家地区对日期的表示格式是不一样的,比如我们中国习惯于“年-月-日”,美国习惯“月/日/年”,而世界上大部分其他国家都是“日/月/年”;时间的表示则有 12 小时和 24 小时制的区别,AM/PM 的不同写法等问题;还有遗留 数据用两位数字表示年份带来的臭名昭著的 “千年虫” 问题;所以在处理 字符串表示的日期时间 时要非常小心地约定好表示格式;
与日期时间关联的时区,如果不指明时区,时间的表示就无意义,但很多软件系统里的时间是不管时区的,一旦需要跨时区使用就会带来一大堆麻烦,比如你带着你的手机出国,手机自动切换到目的地时区,然后你拍的照片、发出的邮件都是目的地的时间,如果不做处理,当你回到常居地这些照片和邮件的时间就错了;
和日历有关的一系列处理,比如今天星期几?去年第二个月有多少天、最后一天是星期几?从现在开始加上 10000 天是什么日子?通常会有一个专门的日历模块来处理这类问题。
由于历史原因,日期时间的处理也有很多的坑(和梗),不过目前比较新的编程语言都提供了很完备的解决方案,我们以后会陆陆续续的碰到和学会,这里我们先重点介绍下 字符串表示的日期时间 数据和相关的处理。
# datetime 类型在 datetime 包中,使用前需要先引入 from datetime import datetime # 使用 datetime 类型的 now() 方法来获取当前时间 t = datetime.now() 现在 datetime 类型的变量 t 里面保存了上述代码运行时的时间信息,包括年、月、日、时、分、秒、微秒和时区,我们可以方便的获取这些分量: print(t) print(t.year) print(t.month) print(t.day) print(t.hour) print(t.minute) print(t.second) print(t.microsecond) print(t.tzinfo) 可以看到这里时区信息(tzinfo)输出为 None,因为创建 t 时我们没有提供时区,我们可以在调用 now() 的时候传入一个时区参数,也可以不带参数调用 astimezone() 方法来给时间加上操作系统设定的本地时区: t = datetime.now().astimezone() print(t) print(t.tzinfo) datetime 模块里不只有 datetime 这个类型,如果我们只关心日期可以用 date 类型,如果只关心时间可以使用 time 类型。官方有一篇详细的文档可以参考。
# datetime 类型在 datetime 包中,使用前需要先引入 from datetime import datetime # 使用 datetime 类型的 now() 方法来获取当前时间 t = datetime.now()
现在 datetime 类型的变量 t 里面保存了上述代码运行时的时间信息,包括年、月、日、时、分、秒、微秒和时区,我们可以方便的获取这些分量:
print(t) print(t.year) print(t.month) print(t.day) print(t.hour) print(t.minute) print(t.second) print(t.microsecond) print(t.tzinfo)
可以看到这里时区信息(tzinfo)输出为 None,因为创建 t 时我们没有提供时区,我们可以在调用 now() 的时候传入一个时区参数,也可以不带参数调用 astimezone() 方法来给时间加上操作系统设定的本地时区:
now()
astimezone()
t = datetime.now().astimezone() print(t) print(t.tzinfo)
datetime 模块里不只有 datetime 这个类型,如果我们只关心日期可以用 date 类型,如果只关心时间可以使用 time 类型。官方有一篇详细的文档可以参考。
有。就是 datetime 等类型提供的 strftime() 方法。这个方法让我们可以用指定格式把日期时间数据转换为字符串输出。 strftime() 需要一个参数,这个参数指定输出字符串的 “时间格式”,这个格式里是各种 % 打头的标志,每个标志代表一个 日期时间分量 及其格式。时间格式里除了 % 打头的标志以外都会原样输出,所以可以用我们喜欢的任何字符。 如果 strftime() 输出的字符串只是用来展示,那么格式相对自由,规范美观就好;如果用来保存数据(比如存到数据库里),那么格式就要非常严谨,确保以后读出来的时候还能正确解析。
有。就是 datetime 等类型提供的 strftime() 方法。这个方法让我们可以用指定格式把日期时间数据转换为字符串输出。
strftime()
strftime() 需要一个参数,这个参数指定输出字符串的 “时间格式”,这个格式里是各种 % 打头的标志,每个标志代表一个 日期时间分量 及其格式。时间格式里除了 % 打头的标志以外都会原样输出,所以可以用我们喜欢的任何字符。
%
如果 strftime() 输出的字符串只是用来展示,那么格式相对自由,规范美观就好;如果用来保存数据(比如存到数据库里),那么格式就要非常严谨,确保以后读出来的时候还能正确解析。
Python 提供许多方法来构造一个日期时间类型的变量,上面看到的 now() 是第一类,即获取当前时间;第二类是用前面介绍过的 Unix timestamp 来构造一个时间,这主要是为了兼容使用 Unix timestamp 的系统和库;第三类就是读取和解析一个表示日期时间的字符串,我们下面介绍主要方法 strptime(),strptime() 可以看做 strftime() 的反向操作,使用一样的时间格式描述。
strptime()
在绝大多数情况下,strptime() 都能很好的根据时间格式描述去 “套” 输入的字符串,然后把里面对应的年月日时分秒取出来,然后构造出一个对应的 datetime 类型变量。为了确保 用字符串表示的日期时间数据 能够在各种数据库和程序中都被正确理解和处理,专门有一个 ISO 标准规定了大家 “最好” 都采用的时间格式是怎样的,这就是 ISO 8601 标准,这一标准采用的 日期时间格式描述 大致是这样子的: 2019-07-17T12:38:24.091911+08:00 从左到右分三段: 日期:YYYY-MM-DD,年月日之间用短横线隔开; 时间:HH:MM:SS.ffffff,最多到微秒;日期和时间之间用一个大写的 'T' 作为分隔符; 时区:通过与标准时的偏移量来表示时区,比如 +08:00 就是东八区,也就是我国采用的时区。 除了日期,后面的部分都是可选的,可以有也可以没有。 Python 通过 isoformat() 和 fromisoformat() 两个方法来支持这个标准。 t = datetime.now().astimezone() # 生成一个 datetime 类型的数据 iso_str = t.isoformat() # 使用 isoformat() 方法把该数据 转成 ISO 标准的字符串 t_from_iso_str = datetime.fromisoformat(iso_str) # 通过 datetime.fromisoformat() 方法将 ISO 标准的字符串 转变成 datetime 类型的数据 ISO 8601 标准避免了各自定义不一样的 时间格式描述,如果我们处理的 日期时间字符串 是用于数据保存和数据交换,采用这个标准是最简单和保险的方式。
在绝大多数情况下,strptime() 都能很好的根据时间格式描述去 “套” 输入的字符串,然后把里面对应的年月日时分秒取出来,然后构造出一个对应的 datetime 类型变量。为了确保 用字符串表示的日期时间数据 能够在各种数据库和程序中都被正确理解和处理,专门有一个 ISO 标准规定了大家 “最好” 都采用的时间格式是怎样的,这就是 ISO 8601 标准,这一标准采用的 日期时间格式描述 大致是这样子的:
2019-07-17T12:38:24.091911+08:00
从左到右分三段:
除了日期,后面的部分都是可选的,可以有也可以没有。
Python 通过 isoformat() 和 fromisoformat() 两个方法来支持这个标准。
isoformat()
fromisoformat()
t = datetime.now().astimezone() # 生成一个 datetime 类型的数据 iso_str = t.isoformat() # 使用 isoformat() 方法把该数据 转成 ISO 标准的字符串 t_from_iso_str = datetime.fromisoformat(iso_str) # 通过 datetime.fromisoformat() 方法将 ISO 标准的字符串 转变成 datetime 类型的数据
ISO 8601 标准避免了各自定义不一样的 时间格式描述,如果我们处理的 日期时间字符串 是用于数据保存和数据交换,采用这个标准是最简单和保险的方式。
除了数值、日期时间等通用基础数据类型,我们还经常用字符串来处理和记录一些 自定义的格式化数据,比如身份证号码、电话号码、Email 地址等。
这些数据的特点是: 由字母、数字和一些特定的符号组成,长度不会太长; 有明确的格式要求; 一般会在输入时检验其是否符合格式要求。 所以,对这类数据我们经常做的是:格式校验、搜索和替换。
这些数据的特点是:
所以,对这类数据我们经常做的是:格式校验、搜索和替换。
在处理这类数据时有个绝佳的工具 “正则表达式(regular expression)”,正则表达式是处理文本数据的利器,学习起来有一定的门槛,所以经常吓到很多人(其中包括一些工作多年的程序员),但其实只要方法得当,学会用正则表达式处理一些常见情况并没有那么难,也是越早学会越早受益的典范。我们在附录中有一篇 正则表达式入门 可作为学习的起点。 希望系统学习 正则表达式 的话可以使用余晟老师编写的《正则指引》一书。另外 Python 官方文档中的 正则表达式指引 也不错。
在处理这类数据时有个绝佳的工具 “正则表达式(regular expression)”,正则表达式是处理文本数据的利器,学习起来有一定的门槛,所以经常吓到很多人(其中包括一些工作多年的程序员),但其实只要方法得当,学会用正则表达式处理一些常见情况并没有那么难,也是越早学会越早受益的典范。我们在附录中有一篇 正则表达式入门 可作为学习的起点。
希望系统学习 正则表达式 的话可以使用余晟老师编写的《正则指引》一书。另外 Python 官方文档中的 正则表达式指引 也不错。
手机号码是我们经常需要处理的数据,为了简单起见,我们限定为中国的手机号,暂不考虑国际区号,那么手机号码应该是一个 3 或 4 位的运营商段码(绝大部分是 3 位),再加 8 位号码,一共 11 或 12 位数字。 我们如果不考虑新加入的两个四位的号段,也不做特别严谨的检查,手机号码的正则规则大概是这样的: import re def is_valid_cellphone(s): pattern = re.compile(r'^[1]([3-9])[0-9]{9}$') if pattern.match(s): return True else: return False 上面的代码非常好懂:首先引入 Python 的正则表达式库,然后编译一个我们写好的 正则规则生成 pattern,然后用这个 pattern 去匹配输入的字符串,如果匹配成功返回 True,否则返回 False。 这个正则规则也很好懂: ^ 表示开始,$ 表示结束; [1] 表示第一个字符必须是 1; ([3-9]) 表示接下来是 3-9 这些数字中的一个,用小括号括起来表示这是一个 group,选出来我们以后可以用(比如通过号段判断运营商); [0-9]{9} 表示接下去是 0-9 的数字中的一个,重复 9 次。
手机号码是我们经常需要处理的数据,为了简单起见,我们限定为中国的手机号,暂不考虑国际区号,那么手机号码应该是一个 3 或 4 位的运营商段码(绝大部分是 3 位),再加 8 位号码,一共 11 或 12 位数字。
我们如果不考虑新加入的两个四位的号段,也不做特别严谨的检查,手机号码的正则规则大概是这样的:
import re def is_valid_cellphone(s): pattern = re.compile(r'^[1]([3-9])[0-9]{9}$') if pattern.match(s): return True else: return False
上面的代码非常好懂:首先引入 Python 的正则表达式库,然后编译一个我们写好的 正则规则生成 pattern,然后用这个 pattern 去匹配输入的字符串,如果匹配成功返回 True,否则返回 False。
True
False
这个正则规则也很好懂:
^
$
学习正则最好的办法就是看别人写好的规则,尝试去理解,如果理解不了,这里有个秘笈:有不少正则规则可视化工具,可以对输入的正则规则给出可视化的解析,比如 Regexper,还有 Debuggex,都可以。 如果要非常精确地匹配我国现有的号段规则,这个正则规则可能就有点长了,大致是这个样子的: ^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}$ 如果把它贴到 Regexper 里,可以看到结构其实也不复杂,主要都在罗列号段 1xx 里的各种情况,这些情况都用 () 分组,这样匹配完了之后我们可以知道匹配到了哪种情况,方便我们根据号段来识别运营商。 手机号码的号段是个会时不时变化的规则,我们如果要编写和维护一个函数,检查手机号码是不是合法、是哪个运营商的,就要时不时更新规则。如果要处理其他国家的手机,那就更加复杂,我们把这些留给大家自己去练习。
学习正则最好的办法就是看别人写好的规则,尝试去理解,如果理解不了,这里有个秘笈:有不少正则规则可视化工具,可以对输入的正则规则给出可视化的解析,比如 Regexper,还有 Debuggex,都可以。
如果要非常精确地匹配我国现有的号段规则,这个正则规则可能就有点长了,大致是这个样子的:
^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}$
如果把它贴到 Regexper 里,可以看到结构其实也不复杂,主要都在罗列号段 1xx 里的各种情况,这些情况都用 () 分组,这样匹配完了之后我们可以知道匹配到了哪种情况,方便我们根据号段来识别运营商。
手机号码的号段是个会时不时变化的规则,我们如果要编写和维护一个函数,检查手机号码是不是合法、是哪个运营商的,就要时不时更新规则。如果要处理其他国家的手机,那就更加复杂,我们把这些留给大家自己去练习。
我们每个人都有电子邮件地址,基本上就是 someone@any.com 这个样子,与直觉相反,用正则表达式来检查 Email 地址是不是合法可不简单。 Email 服务相关的规范相当古老,由 IETF 制订的一系列标准定义,由于历史悠久又涉及到域名规则,我们很难写出一个完美的正则规则来检查 Email 地址,好在大部分时候我们不需要那么完美,一般实际应用中只要保证基本合规就可以了,实际向某个地址发送邮件之后还是要做 “如果这个地址收不到信怎么办” 的处理。 幸运的是,由于 Email 校验这个问题太经典也太经常被提出来了,有人甚至专门做了个网站,叫 Email Address Regular Expression That 99.99% Works,根据这个网站提供的正则规则,我们可以写出下面的这个函数: def is_valid_email(s): pattern = re.compile(r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)') if pattern.match(s): return True else: return False 利用这个函数我们可以方便地检查用户输入的是不是一个合法的 Email 地址。 while True: email = input('Please enter your Email: ') if is_valid_email(email): break else: print('It\'s not a valid Email address. Please try again.') print(f'Your Email address is \'{email}\'.')
我们每个人都有电子邮件地址,基本上就是 someone@any.com 这个样子,与直觉相反,用正则表达式来检查 Email 地址是不是合法可不简单。
Email 服务相关的规范相当古老,由 IETF 制订的一系列标准定义,由于历史悠久又涉及到域名规则,我们很难写出一个完美的正则规则来检查 Email 地址,好在大部分时候我们不需要那么完美,一般实际应用中只要保证基本合规就可以了,实际向某个地址发送邮件之后还是要做 “如果这个地址收不到信怎么办” 的处理。
幸运的是,由于 Email 校验这个问题太经典也太经常被提出来了,有人甚至专门做了个网站,叫 Email Address Regular Expression That 99.99% Works,根据这个网站提供的正则规则,我们可以写出下面的这个函数:
def is_valid_email(s): pattern = re.compile(r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)') if pattern.match(s): return True else: return False
利用这个函数我们可以方便地检查用户输入的是不是一个合法的 Email 地址。
while True: email = input('Please enter your Email: ') if is_valid_email(email): break else: print('It\'s not a valid Email address. Please try again.') print(f'Your Email address is \'{email}\'.')
字符串是用来表示各种数据的利器,重点在于约定好表示的格式,可以把数据按格式组合成字符串,也可以按照格式 parse 字符串得到里面的数据; Parse 字符串时处理各种意外情况是一个关键; 了解 Python 中数值类型和日期时间类型,及其与字符串之间来回转换的方法; 了解 用正则表达式处理特定格式字符串的 基本方法。
2020-03-15 01:05:57 中间傻逼地断学了 2020-03-05 17:40:48 initialize
1. 引子:进入数据的世界
2. 一切都是字符串
3. 字符串 <=> 数值
4. 字符串 <=> 日期时间
4.1 日期时间 => 字符串
4.2 字符串 => 日期时间
5. 字符串与自定义格式化数据
5.1 手机号码
5.2 Email
小结
Logging
2020-03-15 01:05:57 中间傻逼地断学了 2020-03-05 17:40:48 initialize