Python语法02

73 阅读1小时+

第12章 常用模块

12.1 sys 模块

image.png image.png image.png

12.2 os 模块

os 模块以及子模块 path 中包含了大量获取各种系统信息,以及对系统进行设置的函数,本节讲解这两个模块中的一些常用函数的使用方法。

12.2.1 获取与改变工作目录

getcwd 函数用于获取当前的工作目录,如果指定文件或目录,则在不指定任何路径的情况下,系统会认为该文件或目录在当前的工作目录中。通过 chdir 函数可以改变当前的工作目录。 image.png

12.2.2 文件与目录操作

在os模块中提供了一些操作目录和文件的函数。这些函数的功能描述如下: 口 mkdir(dirname, permissions):创建目录,dirame 表示目录名,如果 dirname 指定的目录名存在,则抛出 OSError 异常。permissions 表示目录的权限。在Linux 或 Mac OSX上可以设置目录读(r)、写(w)和执行(x)权限。permissions 参数值一般是一个八进制的数值。如0o777表示最高的权限,7表示同时具有rwx权限。关于 Linux/Mac OS X权限的细节,请参阅相关的文档,这里不再详细讲解。 口 makedirs(dirname, permissions,exist_ok):与 mkdir 函数类似,用于建立目录,但 dirname 指定的目录可以是多级的,而且上一级不存在,也会建立上一级的目录。例如,dirname 参数的值是x/y/z。也就是说,需要在当前目录建立三级目录。如果使用mkdir 函数,并且x或y目录不存在,那么程序会直接抛出 OSEror 错误,但使用 makedirs 函数建立目录,会连同x和y 一起建立。makedirs 函数最后一个参数的值为 False,当目录存在时,会抛出 OSError 异常,否则即使目录存在,也不会抛出异常。 口 rmdir(dirname):删除 dirname 参数指定的目录,如果目录不为空,则抛出一个 OSError 异常。 口 removedirs(dirname):删除 dirname 参数指定的目录,dirname参数可以指定多级目录。如x/y/z,该函数会同时删除x、y和z目录。不过如果某一级目录不为空,那么该目录以及所有的父目录都不会被删除。例如,如果在y目录中除了有一个z 子目录外,还有一个 test.fxt 文件,那么在删除 x/y/z目录时,只会删除y目录中的z子目录,x和y目录都不会被删除。 remove(filename):删除 filename 参数指定的文件。 口 rename(src, dst):将 src 参数指定的文件(目录)名改成dst 参数指定的文件名。 口 renames(src,dst):与 rename函数的功能类似,只是 src 和 dst 可以是多级目录(最后一级可以是文件名)。该函数会将每一级的目录都改成对应的目录名,如可以将 x/y/z改成a/b/c。该函数会将x改成 a,y 改成b,z改成c。

12.2.3 软链接与硬链接

软链接和硬链接是Linux 和 Mac OSX的概念。软链接就像 Windows中的快捷方式,只是保存了源文件或目录的引用,而且只有固定尺寸。硬链接只能针对文件建立,这是因为硬链接是将整个文件复制一份,相当于一个副本,所以在建立硬链接时会进行限制。软链接和硬链接都是同步的。也就是说,只要修改软链接文件或硬链接文件,源文件的内容就会变,反之亦然。 不管是 Linux 还是 Mac Os X,建立软链接和硬链接的命令都是 ln,在建立软链接时,需要加“-s命令行参数。假设有一个 test.txt 文件,现在要对该文件建立一个软链接文件 slink.txt 和一个硬链接文件 link.txt,则 test.txt 文件的内容如下: Hello World 世界你好 建立软链接和硬链接的命令如下:

In -s test.txt slink.txt
In test.txt link.txt
12.3.1 集合

从Python2.3开始,集合就通过set类型成为语言的一部分,因此,在Python3 中可以直接使用集合类型(set),而不需要再引用sets模块(这个模块已经被去除了)。Python 语言中的集合和数字集合的概念非常类似。在数学中集合有如下三个特性。 口 无序性:集合中每个元素的值都是平等的,元素之间是无序的。 口 互异性:集合中任意两个元素都是不同的,即每个元素只能出现一次。 口 确定性:集合中每个元素都是确定的,对于一个值来说,要么属于该集合,要么不属于该集合 所以集合、列表和字典都不能作为集合的元素值,因为它们都是可变的。 Python 语言中的集合也同样满足这三个特性,而且同样支持集合的标准操作,如创建集合、合并集合、集合相交、求集合的差等。在 Python 语言中有很多操作同时提供了运算符和方法,例如,集合的并操作,可以使用其中一个集合的 union 方法,也可以使用按位或运算符“|”。 在创建集合类 set 的实例时,需要为 set类的构造方法提供一个列表或元组类型的值,用于建立集合的数据源。也就是说,set类可以将列表或元组转换为集合,在转换的过程中,会去除重复的值、并且列表或元组中元素的顺序可能被打乱,因为集合中的元素是无序的。

\#创建一个有 10个元素值的集合
set1 = set(range(10))
\#输出 set1的类型,运行结果:\<class'set'>
print(type(set1))
\#运行结果:(0123456789}
print(set1)
\#将字符串中的每一个字符作为元素添加进集合,因为字符串可看作字符的列表
\#会去除重复的字符,而且顺序会打乱
set2 = set('hello')
\#运算结果:{'h','o’,'1','e')
print(set2)
\#利用字符串列表建立集合,会去除重复的字符串,字符串的顺序会打乱
set3 = set(\['Bill','John','Mike','John'])
\#运行结果:{'John','Mike','Bi11')
print(set3)
\#利用元组建立一个集合
a = set((1,2,3))
\#利用列表建立一个集合
b = set(\[3,5,1,7])
\#使用 union方法合并a集合和b集合,运行结果:{12357))
print (a.union(b))
\#使用“|”运算符合并a集合和b集合,运行结果:(12357)
print(a | b)
\#使用 intersection方法求a集合和b集合的交集,运行结果:(13)
print (a.intersection(b))
\#使用“&”运算符求a集合和b集合的交集,运行结果:(13)
print (a & b)
\#使用列表创建一个集合
c = set(\[2,3])
\#判断c集合是否为a集合的子集,运行结果:True
print(c.issubset(a)):
\#判断a集合是否为c集合的子集,运行结果:False
print(a.issubset(c))
\#判断c集合是否为a集合的超集,运行结果:False
print(c.issuperset(a))
\#判断a集合是否为c集合的超集,运行结果:True
print(a.issuperset(c))
\#使用列表创建一个集合
d = set(\[1,2,3])
\#判断a集合和d集合是否相等,运行结果:True
print(a == d)
\#使用 difference 方法计算a集合与b集合的差,a和b的差值就是在a中删除在b中存在的元素
\#运行结果:{2}
print(a.difference(b))
\#使用“-”运算符计算a集合与b集合的差,运行结果:{2)
print(a - b)
\#使用 symmetric\_difference 方法计算a集合与b集合的对称差,运行结果:{257}
print(a.symmetric difference(b))
\#使用“^”运算符计算a集合与b集合的对称差,运行结果:{257}
print (a ^ b)
#对称差相当于a-bb-a的并集,运行结果:{257}
Print((a-b)|(b- a))
#使用 copy 方法将a复制一份,并将该副本赋给变量x
x= a.copy()
#判断x与a是否相同,运行结果:False
print(x is a)
#向集合x中添加一个新的元素
x.add(30)
#运行结果:{12330}
print(x)
#运行结果:{123}
print(a)
#运行结果:(123}
print (d):
#判断1是否属于集合 d,运行结果:True
print(l in d)
#判断10是否属于集合 d,运行结果:False
print (10 in d)

image.png 由于集合是可变的,所以不能作为元素值添加到集合中,也不能作为字典的 key。不过可以利用 frozenset 类型将集合变成只读的,这样就可以作为集合元素和字典的 key。

12.3.2堆

堆也是一种众所周知的数据结构,它是优先队列中的一种。使用优先队列能以任意顺序增加元素,并能快速找到最小(大)的元素值,或前n个最小或最大的元素值,这要比用于列表的 min 函数和max 函数高效得多。 与集合不同,在 Python3 中并没有独立的堆类型,只有一个包含一些堆操作函数的模块,该模块名为heapq(q是 queue 的缩写,即队列)。heapq模块中常用的函数如表 12-2 所示。 image.png

12.3.3 双端队列

双端队列不同于普通的队列。对于普通的队列来说,只能操作队列的头,而不能操作队列的尾,也就是先进先出操作。双端队列是普通队列的扩展,在队列的头和尾都可以进行队列的操作,所以对 一组值的两头进行操作,使用双端队列是非常方便的。 使用双端队列需要导入 collections 模块中的 deque 类。该类中提供了若千个方法用于操作双端队列,例如,append 方法可以将值添加到队列的尾部,而 appendleft 方法可以将值添加到队列的头部。pop方法可以弹出队列尾部的最后一个值,并返回这个值。popleft 方法可以弹出队列头部的第1个值并返回这个值。

from collections import deque
\#创建一个包含 10 个数字的双端队列
g= deque (range(10))
\#运行结果:deque(\[0123456789])
print(g)
\#将 100 追加到双端队列g的队尾
g.append(100)
\#将-100 追加到双端队列q的队尾
g.append(-100)
\#运行结果:degue(\[0123456789100,-100])
print(g)
\#将 20 追加到双端队列g的队首g.appendleft(20)
\#运行结果:deque(\[200123456789100,-100])
print(g)
\#弹出队尾的值,运行结果:-100
print(g.pop())
\#运行结果:degue(\[200123456789100])
print(g)
\#弹出队首的值,运行结果:20
print(g.popleft())
\#运行结果:deque(\[0123456789100])
print(g)
\#将双端队列中的元素向左循环移动两个位置,也就是队首的元素会移动到队尾
g.rotate(-2)
\#运行结果:degue(\[2345678910001])
print(g)
!将双端队列中的元素向右循环移动两个位置,也就是队尾的元素会移动到队首
g.rotate(4)
\#运行结果:deque(\[9100012345678])
print(g)
\#创建一个双端队列 q1
g1 = deque (\['a', 'b'])
\#将 q1 追加到q的后面
g.extend (gl)
\#运行结果:degue(\[9,100,0,1,23,45,678'a''b'])
print (g)
\#将 q1 追加到q的前面,这时 g1 会倒序排列
g.extendleft(gl)
\#运行结果: degue(\['b', 'a', 9, 100,0,1,2,3,4,5, 6, 7,8,'a','b'])
print(g)

12.4 时间、日期与日历(time 模块)

Python 程序能用很多方式处理日期和时间,转换日期格式是一个常见的功能。Python 提供的time、datetime 和 calendar 模块可以用于格式化日期和时间。时间间隔是以秒为单位的浮点数。每个间戳使用从 1970年1月1日午夜(历元)到现在经过的时间来表示。 Python 语言的 time 模块下有很多函数可以转换常见日期格式。例如,函数 time 用于获取当前时间戳,代码如下:

import time
ticks = time,time()
\#运行结果:当前时间戳为:1511675087.7990139
print("当前时间戳为:",ticks)

从前面代码的运行结果可以看出,ticks变量的值是一个浮点数,这个浮点数就是从运行程序那一刻的时间点到 1970 年1月1日午夜之间的秒数。当然,Python 语言还可以使用更多的函数来操作间和日期。本节深入介绍与时间、日期和日历相关函数的用法。

12.4.1 时间元组

在 Python 语言中,时间用一个元组表示。表示时间的元组有9个元素,这9个元素都有其对应的属性,所以时间元组中的每一个元素值既可以通过属性获得,也可以通过索引获得。时间元组中的元景仅仅要求显示4位的年。这就要求在输出日期和时间之前要先进行格式化。 格式化日期和时同需要使用 strftime 函数,该函数的第1个参数是格式化字符串,第2个参数是时间元组。Python语言支持多个日期和时间格式化符号,这些符号如表 12-4所示。 image.png Python 语言中所有用于格式化日期和时间的符号都以百分号(%)开头,如“%Y-%m-%d”会将日期格式化为如“2017-11-12”的形式。 要注意的是,如果格式化字符串中包含中文,必须用locate模块中的 setlocale 函数将日期与时间格式设置的格式设为中文的 UTF-8 格式,否则无法成功对日期和时间格式化。如果不知道当前系统有哪些Locate,则可以使用 locate -a 命令査询,或使用 locate a|grep UTF-8 只显示 UTF-8的Locate。这些会令仅仅针对 Linux 和 Mac Os X

12.4.3 时间戳的增量

通过 time 模块的 time 函数可以获取当前时间的时间戮(浮点数格式),时间戳可以加上或减去一个整数(n),这就是时间戳的增量,单位是秒。也就是说,当前时间加 10表示未来 10s,减10多示过去10s。如果想得到未来 1h 的时间戳,需要将 time 函数的返回值加 3600。

12.4.4 计算日期和时间的差值

datetime 模块中的 datetime 类允许计算两个日期的差值,可以得到任意两个日期之间的天数以及剩余的秒数。通过 datetime 模块的 timedelta 函数还可以得到一个时间增量,如 timedetla(hours=2)可省到往后延两个小时的时间增量。

12.5 随机数(random 模块

在 random 模块中封装了多个函数用于产生各种类型的随机数,这些函数有的可以产生单值随机数,有的可以产生一组随机数,还有的可以打乱列表原来的顺序,类似于洗牌。random 模块中常用函数及描述如下: □ randint(m,n):产生 mn 的随机整数,包括 m 和 n。 口 random():产生 01的随机浮点数,包括 0,但不包括1。 口 uniform(m,n):产生 m~~n的随机浮点数,m 和n可以是浮点数,包括 m和 n。 口 randrange(m,n.step):在一个递增的序列中随机选择一个整数。其中,step 是步长,如 randrange(1,6,2),该函数就会在列表[1.3.5]中随机选择一个整数。 口 choice(seq):从 seq 指定的序列中随机选择一个元素值。seq 指定的列表元素可以是任意类型的值。 口 sample(seq,k):从 seq 指定的序列中随机选取k个元素,然后生成一个新的序列 口 shuffle(seq):把 seq 指定的序列中元素的顺序打乱,该函数直接修改原有的序列

import random
\#产生 1~~100 的随机整数
print(random.randint(1,100))
\#产生 0~~1 的随机数
print(random.random())
\#从\[1,4,7,10,13,16,19]随机选一个数
print(random.randrange(1, 203))
\#产生一个从 1~~100.5的随机浮点数
Print(random.uniform(1, 100.5))
intlist = \[1,2,3,4,5,6,7,8 9,'a’,'b','c','d']
\#从 intzist 列表中随机选一个元素值
print(random.choice(intList))
\#从 intList 列表中随机选3个元素值,并生成一个新的序列 …
newList = random.sample(intList, 3)
print(newList)
\#随机排列 intList 中的元素值,该函数直接改变了 intList 列表
random.shuffle(intList)
print(intList)

image.png

12.6 数学(math 模块)

在 math 模块中封装了很多与数学有关的函数和变量,如取整、计算幂值、平方根、三角函数等。 本节介绍 math 模块中比较常用的数学函数。

import math
print('圆周率''='math.pi)
print('自然常数’,'=’,math.e)
\#取绝对值
print (math.fabs(-1.0))
\#向上取整,运行结果:2
print(math.ceil(1.3))
\#向下取整,运行结果:1
print(math.floor(1.7))
\#计算210次方,运行结果:1024.0
print(math.pow(2,10))
\#计算8的平方根,运行结果:2.8284271247461903
print(math.sqrt(8))
\#计算π/2.的正弦,运行结果:1.0
print(math.sin(math.pi /2))
\#计算"的余弦,运行结果:-1.0
print (math.cos (math.pi))
\#计算x/4的正切,运行结果:0.9999999999999999
print(math.tan(math.pi /4))

image.png

第13章 文件与流

13.1 打开文件

open 函数用于打开文件,通过该函数的第1个参数指定要打开的文件名(可以是相对路径,也可以是绝对路径)。 f= open('test.txt') f = open('./files/test.txt') 如果使用 open 函数成功打开文件,那么该函数会返回一个 TextIOWrapper 对象,该对象中的方法可用来操作这个被打开的文件。如果要打开的文件不存在,则抛出如图 13-1所示的 FileNotFoundError异常 image.png open 函数的第2个参数用于指定文件模式(用一个字符串表示)。这里的文件模式是指操作文件的方式,如只读、写入、追加等。表 13-1 描述了 Python3 支持的常用文件模式。 可以看到,在表13-1所示的文件模式中,主要涉及对文件的读写和文件格式(文本和二进制)的问题。使用 open 函数打开文件时默认是读模式,如果要想向文件中写数据,需要通过 open 函数的第 2个参数指定文件模式。 f = open('./files/test.txt','w') #以写模式打开文件 f = open('./files/test.txt', 'a') #以追加模式打开文件 image.png #以写模式打开文件 写模式和追加模式的区别是如果文件存在,写模式会覆盖原来的文件,而追加模式会在原文容的基础上添加新的内容。 在文件模式中,有一些文件模式需要和其他文件模式放到一起使用,如 open函数不指定第2个数时默认以读模式打开文本文件,也就是'rt'模式。如果要以写模式打开文本文件,需要使用'wt'模击对于文本文件来说,用文本模式(t)打开文件和用二进制模式(b)打开文件的区别不大,都是以字节为单位读写文件,只是在读写行结束符时有一定的区别。 如果使用文本模式打开纯文本文件,在读模式下,系统会将"\n'作为行结束符,对于 UNIX、MacOSX这样的系统来说,会将"\n作为行结束符,而对于 Windows 来说,会将"r\n'作为行结束符,还有的系统会将"\r"作为行结束符。对于“\r\n"和"\r"这样的行结束符,在文本读模式下,会自动转换为"\n',而在二进制读模式下,会按原样读取,不会做任何转换。在文本写模式下,系统会将行结束符转换为 0S应的行结束符,如 Windows 平台会自动用"\r\n'作为行结束符。可以使用 os 模块中的 linesep 变量来获得当前 OS 对应的行结束符。 在表 13-1 最后一项是'+’文件模式,表示读写模式,必须与其他文件模式一起使用,如'r+'、'w+ 'a+'。这三个组合文件模式都可以对文件进行读写操作,它们之间的区别如下。 口 r+:文件可读写,如果文件不存在,会抛出异常;如果文件存在,会从当前位置开始写入新内容,通过 seek 函数可以改变当前的位置,也就是文件指针。 口 w+:文件可读写,如果文件不存在,会创建一个新文件;如果文件存在,会清空整个文件 并写入新内容。 口 a+文件可读写,如果文件不存在,会创建一个新文件;如果文件存在,会将要写入的内容加到原文件的最后,也就是说,使用'a+模式打开文件,文件指针会直接跳到文件的尾部,果要使用 read 方法读取文件内容,需要使用 seek 方法改变文件指针,如果调用 seek(0)会直接将文件指针移到文件开始的位置。

13.2操作文件的基本方法

13.2.1 读文件和写文件

image.png 使用 open 函数成功打开文件后,会返回一个 TextIOWwrapper 对象,然后就可以调用该对象中的方法对文件进行操作。TextIOWwrapper 对象有如下4个非常常用的方法。 口 write(string):向文件写入内容,该方法返回写入文件的字节数。 口read(n):读取文件的内容,n 是一个整数,表示从文件指针指定的位置开始读取的n字节。如果不指定n,该方法就会读取从当前位置往后的所有的字节。该方法返回读取的数据。 口 seek(n):重新设置文件指针,也就是改变文件的当前位置。使用 write 方法向文件写入内容后 需要调用 seek(0)才能读取刚才写入的内容。 口 close():关闭文件,对文件进行读写操作后,关闭文件是一个好习惯。

【例 13.1】 本例分别使用?、'w、r+、'w+等文件模式打开文件,并读写文件的内容,可以从中学习到不同文件模式操作文件的差别。
\#以写模式打开 test1.txt 文件
f= open('./files/testl.txt','w')
\#向test1.txt 文件写入“I love ”,运行结果:7
print(f.write('I love '))
\#向test1.txt 文件写入“python”,运行结果:6
print(f.write('python'))
\#关闭 test1.txt 文件
f.close()
\#以读模式打开 test1.txt 文件
f = open('./files/testl.txt','r')
\#从 test1.txt 文件中读取7字节的数据,运行结果:I 1ove
print(f.read(7))
\#从 test1.txt 文件的当前位置开始读取6字节的数据,运行结果:python
print(f.read(6))
\#关闭 test1.txt 文件
f.close()
try:
\#如果 test2.txt 文件不存在,会抛出异常
f = open('./files/test2.txt','r+')
except Exception as e:
print(e)
\#用追加可读写模式打开 test2.txt 文件
f = open('./files/test2.txt', 'a+')
\#向 test2.txt 文件写入“hello”
print(f.write('hello'))
\#关闭 test2.txt 文件
f.close()
\#用追加可读写模式打开 test2.txt 文件
f = open('./files/test2.txt', 'a+')
\#读取 test2.txt 文件的内容,由于目前文件指针已经在文件的结尾,所以什么都不会读出来
print(f.read())
\#将文件指针设置到文件开始的位置
f.seek(0)
\#读取文件的全部内容,运行结果:hello
print (f.read())#关闭 test2.txt 文件
f.close()
\#用写入可读写的方式打开 test2.txt 文件,该文件的内容会清空
try:
f = open('./files/test2.txt','w+')
\#读取文件的全部内容,什么都没读出来
print(f.read())

# 向文件写入“How are you?”

    f.write('How are you?')
    #重置文件指针到文件的开始位置
    f.seek(0)
    #读取文件的全部内容,运行结果:How are you?
     print(f.read())

finally:
\#关闭 test2.txt 文件,建议在 finally 中关闭文件
f.close()

image.png

print (f.read())#关闭 test2.txt 文件
f.close()
\#用写入可读写的方式打开 test2.txt 文件,该文件的内容会清空
try:
f = open('./files/test2.txt','w+')
\#读取文件的全部内容,什么都没读出来
print(f.read())

# 向文件写入“How are you?”

f.write('How are you?')
\#重置文件指针到文件的开始位置
f.seek(0)
\#读取文件的全部内容,运行结果:How are you?
print(f.read())
finally:
\#关闭 test2.txt 文件,建议在 finally 中关闭文件
f.close()

尽管一个文件对象在退出程序后(也可能在退出前)会自动关闭,尽管是否关闭不重要,但在文件完成相关操作后关闭文件也没什么坏处,而且还可以避免浪费操作系统中打开文件的配额。因建议在对文件进行读写操作后,使用 close 方法关闭文件,而且最好在 finally 子句中关闭文件,这做可以保证文件一定会被关闭

13.2.2 管道输出

在 Linux、UNIX、Mac Osx等系统的 Shell 中,可以在一个命令后面写另外一个命令,前一个命令的执行结果将作为后一个命令的输入数据,这种命令书写方式被称为管道,多个命令之间要使用"|"符号分隔。下面是一个使用管道命令的例子。 ps aux l grep mysql 在上面的管道命令中先后执行了两个命令,首先执行 ps aux命令查看当前系统的进程以及相关信息,然后将查询到的数据作为数据源提供给 grep 命令,grep mysql命令表示查询进程信息中所有包含mysql 字样的进程。图 13-3 所示是一种可能的输出结果。 image.png 在 Python 程序中,可以通过标准输入来读取从管道传进来的数据,所以 python 命令也可以使用在管道命令中。 image.png

13.2.3 读行和写行

读写一整行是纯文本文件最常用的操作,尽管可以使用read 和 write 方法加上行结束符来读写文件中的整行,但比较麻烦。因此,如果要读写一行或多行文本,建议使用 readline 方法、readlines方法. 法和 writelines 方法。注意,并没有 writelime 方法,写一行文本需要直接使用 write 方法。 readline 方法用于从文件指针当前位置读取一整行文本,也就是说,遇到行结束符停止读取文,但读取的内容包括了行结束符。readines方法从文件指针当前的位置读取后面所有的数据,并将这些数据按行结束符分隔后,放到列表中返回。writelines 方法需要通过参数指定一个字符串类型的列表。该方法会将列表中的每一个元素值作为单独的一行写入文件。 【例 13.3】本例通过 readlime 方法、readlines 方法和 writelines 方法对 urls.txt 文件进行读写行作,并将读文件后的结果输出到控制台。

实例位置:PythonSampleslsrechapter13\demo13.02.py
import os
#以读写模式打开 urls.txt 文件
f = open('./files/urls.txt','r+')
#保存当前读上来的文本
url = ‘’
while True:
     #从 urls.txt 文件读一行文本
      url = f.readline()
      #将最后的行结束符去掉
       url = url.rstrip()
       #当读上来的是空串,结束循环
       if url == '':
           break;
        else:
            #输出读上来的行文本
            print (url)
print('-----------')
#将文件指针重新设为 0
f.seek(0)
#读 urls.txt 文件中的所有行
print(f.readlines())
#向 urls.txt 文件中添加一个新行
f.write('https://jiketiku.com' + os.linesep)
#关闭文件
f.close()
#使用'a+'模式再次打开 urls.txt 文件
f = open('./files/urls.txt','a+')
#定义一个要写入 urls.txt 文件的列表
urllist=['https://geekori.com'+ os.linesep, "https://www.google.com’ + os.linesep]
#将 urlList 写入 urls.txt 文件
f.writelines(urllist)
f.close()

在运行上面的程序之前,先要在当前目录中建立一个 fles子目录,并在该目录下建立一个 urls.txt文件,并输入下面3行内容。 files/urls.txt geekori.com geekori.com/que.php edu.geekori.com

13.3使用 FileInput 对象读取文件

如果需要读取一个非常大的文件,使用 readlines 函数会占用太多内存,因为该函数会一次性将文 件所有的内容都读到列表中,列表中的数据都需要放到内存中,所以非常占内存。为了解决这个问题可以使用 for 循环和 readline 方法逐行读取,也可以使用 fleinput 模块中的 input 函数读取指定的文件。 input方法返回一个 Filelnput对象,通过FileInput对象的相应方法可以对指定文件进行读取,FileInput 对象使用的缓存机制,并不会一次性读取文件的所有内容,所以比 readlines 函数更节省内存资源。

import fileinput
#使用 input 方法打开 urls.txt 文件
fileobj = fileinput,input(',/files/urls.txt')
#输出 fileobj 的类型
print(type(fileobj))
#读取 urls.txt 文件第1行
print(fileobj.readline() .rstrip())
# 通过 for 循环输出 urls.txt 文件的其他行
for line in fileobj:
     line = line.rstrip()
      #如果 file 不等于空串,输出当前行号和内容
     if line != ' ':
         print (fileobj.lineno (),':' line)
       else:
             #输出当前正在操作的文件名
               print (fileobj.filename()) #必须在第1行读取后再调用,否则返回 None

image.png 要注意的是,flename 方法必须在第1次读取文件内容后调用,否则返回 None。

第14章 数据存储

image.png

14.1.2 字典转换为 XML 字符串

image.png

image.png

14.1.3 XML 字符串转换为字典

image.png

14.2 处理 JSON 格式的数据

image.png

image.png JSON格式的数据同样被广泛使用在各种应用中,JSON 格式要比 XML 格式更轻量,所以现在很多数据都选择使用 JSON 格式保存,尤其是需要通过网络传输数据时,这对于移动应用更有优势,因为保存同样的数据,使用 JSON 格式要比使用 XML 格式的数据尺寸更小,所以传输速度更快,也更节省流量,因此,在移动 App 中通过网络传输的数据,几乎都采用了 JSON 格式。 JSON格式的数据可以保存数组和对象,JSON 数组用一对中括号将数据括起来,JSON 对象用一对大括号将数据括起来。下面就是一个典型的 JSON 格式的字符串。在这个 JSON 格式字符串中定义了一有两个元素的数组,每一个元素的类型都是一个对象。对象的 key 和 value 之间要用冒号(:)分隔,key-value 对之间用逗号(,)分隔。注意,key 和字符串类型的值要用双引号括起来,不能使用单引号。 [ { "itemi":"valuel","item2": 30,"item3":10}, {"item1":"value2", "item2": 30, "item3":20} ]

14.2.1 JSON 字符串与字典互相转换

将字典转换为 JSON 字符串需要使用 json 模块的 dumps 函数,该函数需要将字典通过参数传入,后返回与字典对应的 JSON 字符串。将 JSON 字符串转换为字典可以使用下面两种方法。 (1)使用 json 模块的 loads 函数,该函数通过参数传入 JSON 字符串,然后返回与该 JSON 字符 对应的字典。 (2)使用 eval 函数将 JSON 格式字符串当作普通的 Python 代码执行,eval 函数会直接返回与JSON格式字符串对应的字典。 尽管eval 函数与 loads 函数都可以将 JSON 字符串转换为字典,但建议使用 loads 函数进行转换,8为eval函数可以执行任何 Python 代码,如果 JSON 字符串中包含了有害的 python 代码,执行 JSON符串可能会带来风险。

14.2.2 将 JSON 字符串转换为类实例

image.png

image.png loads 函数不仅可以将 JSON 字符串转换为字典,还可以将 JSON 字符串转换为类实例。转换原理 是通过loads 函数的 object_hook 关键字参数指定一个类或一个回调函数。具体处理方式如下: 口指定类:loads 函数会自动创建指定类的实例,并将由 JSON 字符串转换成的字典通过类的构造方法传入类实例,也就是说,指定的类必须有一个可以接收字典的构造方法。 口 指定回调函数:loads 函数会调用回调函数返回类实例,并将由 JSON 字符串转换成的字典传入回调函数,也就是说,回调函数也必须有一个参数可以接收字典。 从前面的描述可以看出,不管指定的是类还是回调函数,都会由 loads 函数传入由 JSON 字符串成的字典,也就是说,loads 函数将 JSON 字符串转换为类实例的本质是先将 JSON 字符串转换为字典,然后再将字典转换为对象。区别是指定类时,创建类实例的任务由 loads 函数完成,而指定回调函数时,创建类实例的任务需要在回调函数中完成,前者更方便,后者更灵活。 image.png

14.2.3 将类实例转换为 JSON 字符串

dumps 函数不仅可以将字典转换为 JSON 字符串,还可以将类实例转换为 JSON 字符串。dumps函数需要通过 default 关键字参数指定一个回调函数,在转换的过程中,dumps 函数会向这个回调函传入类实例(通过 dumps 函数第1个参数传入),而回调函数的任务是将传入的对象转换为字典,然后 dumps 函数再将由回调函数返回的字典转换为JSON 字符串。也就是说,dumps 函数的本质还是将字典转换为 JSON 字符串,只是如果将类实例也转换为 JSON 字符串,需要先将类实例转换为字典,然后再将字典转换为JSON 字符申,而将类实例转換为字典的任务就是通过 default 关键字参数指定回调函数完成的。 image.png

14.2.4 类实例列表与 JSON 字符串互相转换

image.png

image.png 前面讲的类实例和 JSON 字符串直接地互相转换,转换的只是单个对象,如果 JSON 字符串是一个类实例数组或一个类实例的列表,也可以互相转换。

14.4 SQLite数据库

image.png

image.png

14.4.2 用 Python 操作 SQLite 数据库

通过 sqlite3 模块中提供的函数可以操作 SQLite 数据库,sqlite3 模块是 Python 语言内置的,不需要安装,直接导入即可。 sqlite3 模块中提供了丰富的函数可以对 SQLite 数据库进行各种操作,但是,在对数据进行增、删、改、查以及其他操作之前,要先使用 connect 函数打开 SQLite 数据库,通过该函数的参数指定 SOLite数据库的文件名即可。打开数据库后,通过 cursor 方法获取 sqlite3.Cursor 对象,然后通过 sqlite3.Cursor时象的 execute 方法执行各种 SQL 语句,如创建表、创建视图、删除记录、插入记录、查询记录等。如果执行的是查询 SOL 语句(SELECT 语句),那么 execute 方法会返回 sqlite3.Cursor 对象,需要对该对象进行迭代,才能获取查询结果的值。 image.png image.png

14.5 MySQL 数据库

image.png

image.png

image.png MySQL 是非常常用的关系型数据库,现在很多互联网应用都使用了 MySQL 数据库,在Python语言中需要使用 pymysql 模块来操作 MySQL 数据库。如果使用的是 Anaconda 的 Python 环境,则需要使用下面的命令安装 pymysql模块: conda install pymysql 如果使用的是标准的 Python 环境,需要使用 pip 命令安装 pymysq1模块: pip install pymysql pymysql模块中提供的 AP与 sqlite3 模块中提供的 API类似,因为它们都遵循 Python DB API 2.0标准。下面的页面是该标准的完整描述: www.python.org/dev/peps/pe… 其实也不必详细研究 Python DB API规范,只需要记住几个函数和方法,绝大多数的数据库的操作就可以搞定。 口 connect 函数:连接数据库,根据连接的数据库类型不同,该函数的参数也不同。connect函数 返回 Connection 对象。 口 cursor 方法:获取操作数据库的 Cursor 对象。cursor 方法属于 Connection 对象。 □ execute 方法:用于执行 SOL语句,该方法属于 Cursor 对象。 口 commit 方法:在修改数据库后,需要调用该方法提交对数据库的修改,commit 方法属于 Cursor 对象。 口 rollback 方法:如果修改数据库失败,一般需要调用该方法进行数据库回滚,也就是将数据恢复成修改之前的样子。

from pymysql import *
import json
#打开MySQL数据库,其中127.0.0.1是MySQL服务器的IP,root 是用户名,12345678 是密码
#test是数据库名
def connectDB():
    db=connect ("127.0.0.1","root","12345678","test",charset='utf8')
     return db
db = connectDB()
#创建persons 表
def createTable(db):
    #获取 Cursor 对象
    cursor=db.cursor()
    sql='''CREATE TABLE persons
        (id INT PRIMARY KEY       NOT ULL,
          name   TEXT     NOT NUL,
            age INT      NOT NULL,
          address   CHAR(50),
          salary  REAL);'''
try:
#执行创建表的 SQL 语句

image.png image.png image.png 从前面的代码和输出结果可以看出,操作 MySOL 和 SQLite 的 API基本是一样的,只是有如下点区别: 口 用 Cursor.execute 方法査询 SOLite 数据库时会直接返回查询结果,而使用该方法查询 MySQL数据库时返回了 None,需要调用 Cursor.fetchall 方法才能返回查询结果。 口 Cursor.execute 方法返回的查询结果和 Cursor.fetchall 方法返回的查询结果的样式是不同的,这一点从输出结果就可以看出来。如果想让 MySOL 的查询结果与 SOLite 的查询结果相同,需要使用 zip 函数和 dict 函数进行转换。关于这两个函数的用法,请参阅 6.2节的内容。

14.6 ORM

在前面讲的用 Python 语言操作 SQLite 和 MySQL 的过程中,使用的都是 SOL 语句,尽管 SQL语句比较方便,但缺点是必须要求程序员了解 SQL 语句,而且不同数据库在实现同样功能的 SQL语句时可能有差异,因此,直接在程序中嵌入 SQL 语句,会增加程序对数据库的耦合度,如果要更换数据库,可能还需要修改程序中的 SQL 语句。为了解决这个问题,出现了 ORM(Object Relational Mapping对象关系映射)技术,可以将纯 SQL 抽象化,或者说将 SQL 语句直接映射成 Python 对象,程序员只需要使用 Python 对象,就可以操作各种类型的数据库,ORM 系统会根据不同类型的数据库,以及相应的操作,自动生成 SQL 语句。因此,使用 ORM编写的应用在更换数据库(如 MySQL 换成 SQL Server,Oracle)时,不需要修改程序源代码。 绝大多数编程语言都支持ORM,有的是直接内置的,有的是通过第三方实现的。在 Python 语部中使用 ORM 有多种选择,都是通过模块支持的。比较著名的有 SQLAlchemy 和 SQLObject,

14.7 非关系型数据库

14.7.2 MongoDB 数据库

image.png MongoDB 是非常著名的文档数据库,所有的数据以文档形式存储

14.7.3 pymongo 模块

image.png 在 python 语言中使用 MongoDB 数据库需要先导入 pymongo 模块,如果使用了 Anaconda Python开发环境,pymongo 模块已经被集成到 Anaconda:如果使用的是标准的 Python 开发环境,需要使下面的命令安装 pymongo 模块: pip install pymongo

image.png

14.8 小结

本章讲解了 Python 语言中大多数场景需要使用到的各种数据存储技术,主要包括文本格式文件(XML和 JSON)、关系型数据库(SOLite 和 MySQL)和非关系型数据库(MongoDB),以及操作关系型数据库的两个 ORM 模块(SOLAlchemy 和 SQLObject)。可能很多读者会有这样的疑问,讲了这么多数据存储方案,那么在实际的应用中,应该使用哪种数据存储方案呢?其实在大多数应用中,存储方案都是多元化的,因为任何一种数据存储方案都不能适用于所有的场景。例如,存储配置信息一般会使用 XML 或 JSON,在网络上传输数据会使用JSON,在本地存储较多的数据,而且希望可以快速检索,可以考虑使用 SOLite 数据库,对于互联网应用,可以考虑使用 MySQL 数据库,但对于数据关系比较复杂的情况,可以采用 MySOL 和 MongoDB 混合的方式。总之,适合的才是最好的。

第15章 TCP与UDP编程

15.1 套接字

套接字(Socket)是用于网络通信的数据结构。在任何类型的通信开始之前,都必须创建 Socket可以将它们比作电话插孔,没有它就无法进行通信。 Socket 主要分为面向连接的 Socket 和无连接 Socket。面向连接的 Socket 使用的主要协议是传输控制协议,也就是常说的 TCP,TCP的 Socket 名称是 SOCK_STREAM。无连接 Socket 的主要协议是用户数据报协议,也就是常说的 UDP,UDP Socket 的名字是 SOCK_DGRAM。本节详细介绍如何使用socket模块进行面向连接的通信(TCP)以及无连接的通信(UDP)。

15.1.1 建立 TCP 服务端

Socket 分为客户端和服务端。客户端 Socket 用于建立与服务端 Socket 的连接,服务端 Socket 用 等待客户端 Socket 的连接。因此,在使用客户端 Socket 建立连接之前,必须建立服务端 Socket。 服务端 Socket 除了要指定网络类型(IPv4 和 IPv6)和通信协议(TCP 和 UDP)外,还必须要持个端口号。所有建立在 TCP/UDP 之上的通信协议都有默认的端口号。例如,HTTP 协议的默认端口号是 80,HTTPS 协议的默认端口号是 443,FTP 协议的默认端口号是21。这些都是应用层协议,建在TCP 协议之上,这些内容会在本章稍后的部分讲解。在Python 语言中创建 Socket 服务端程序,需要使用 socket 模块中的 socket类。创建 Socket 服务端程序的步骤如下: (1)创建 Socket对象。 (2)绑定端口号。 (3)监听端口号。 (4)等待客户端 Socket 的连接。 (5)读取从客户端发送过来的数据。 (6)向客户端发送数据。 (7)关闭客户端 Socket 连接。 (8)关闭服务端 Socket连接。 上面的某些步骤可能会执行多次,例如,第4步等待客户端 Socket连接,可以放在一个循环山 当处理完一个客户端请求后,再继续等待另外一个客户端的请求。这些步骤的伪代码描述如下、

#创建 Socket 对象
tcpServerSocket = socket(...)
#绑定 Socket 服务端端口号
tcpServerSocket.bind(..)
#监听端口号
tcpServerSocket.listen(..)
#等待客户端的连接
tcpClientSocket = tcpServerSocket.accept()
#读取服务端发送过来的数据
data = tcpClientSocket.recv (..)
#向客户端发送数据
tcpClientSocket.send(...)
#关闭客户端 Socket 连接
tcpClientSocket.close()
#关闭服务端 Socket 连接
tcpServerSocket.close()
【例 15.1】 本例使用 socket 模块中的相关 API建立一个 Socket 服务端,端口号是 9876,可以使用浏览器、telnet 等客户端软件测试这个 Socket 服务。
实例位置:PythonSamples\src\chapter15\demo15.01.py
#导入 socket 模块中的所有 API
from socket import *
#定义一个空的主机名,在建立服务端 Socket 时一般不需要使用 host
host=' '
#用于接收客户端数据时的缓冲区尺寸,也就是每次接收的最大数据量(单位:字节)
bufferSize=1024
#服务端 Socket 的端口号
port = 9876
#将 host 和 port 封装成一个元组
addr =(host,port)
#创建 Socket 对象,AF INET表示 IPV4,AF INET6 表示 IPv6,SOCK_STREAM 表示 TCP
tcpServerSocket = socket(AF_INET, SOCK_STREAM)
#使用 bind 方法绑定端口号
tcpServerSocket.bind(addr)
#监听端口号
tcpServerSocket.listen()
print('server port:9876,)
print('正在等待客户端连接)
#等待客户端号Socket的连接、这里程序会被阻塞,直到接收到客户端的连接请求,才会往下执行
#接收到客户端请求后,同时返回了客户端 Socket 和客户端端口号

tcpClientsocket,addr = tcpServerSocket.accept()
print('客户端已经连接','addr’', '=', addr)
#开始读取客户端发送过来的数据,每次最多会接收不超过buffersize字节的数据
#如果客户端发送过来的数据量大于 bufferSize 所指定的字节数,那么recv万法只会返回buffersize 个
字节,剩下的数据会等待recv 方法的下一次读取
data = tcpclientSocket.recv(buffersize)
# recv方法返回了字节形式的数据,如果要使用字符串,需要将其进行解码,本例使用 utf8 格式解码
print (data.decode ('utfg'))
#向客户端以 utf-8 格式发送数据
tcpClientSocket.send("你好,I love you.\n'.encode (encoding='utf 8'))
#关闭客户端 Socket
tcpClientSocket.close()
#关闭服务端 Socket
tcpServerSocket.close()

image.png 尽管从表面上看,只有服务端 Socket 需要绑定端口号,其实客户端 Socket 在与服务端 Socket连接时也需要一个端口号,这个客户端 Socket 的端口号一般是自动产生和绑定的,这个端口号由 Socke对象的 accept方法返回。图 15-4 中的 53051 就是客户端 Socket 的端口号,每一个客户端 Socket的口号一般都是不同的。 除了使用 telnet 测试 Socket 服务端程序外,也可以使用浏览器进行测试,本例选择了 Google 的Chrome 浏览器。首先启动Socket 服务端程序,然后在 Chrome 浏览器地址栏中输入如下的 Url:http://localhost:9876/geekori 其中,“/geekori”是 Url 的路径(Path),由于 Socket 服务端程序并不是 HTTP 服务器,所以这个路径可以任意指定,浏览器只会使用 localhost 和后面的端口号(9876)连接 Socket 服务端。 输入上面的 Ul,并按 Enter 键后,在浏览器中会显示“该网页无法正常运作”或类似的信息,这个无关紧要,因为 Socket 服务端程序并没有返回 HTTP 响应头®和相关信息。出现这个信息也说明Chrome 浏览器成功连接到了 Socket 服务端程序。 在服务端,会在 Console 中输出如图 15-5 所示的信息。 在 Console 中显示的都是 Chrome 浏览器发送给 Socket 服务端程序的 HTTP 请求头信息,如果服务端是 HTTP 服务器,那么应该给浏览器返回 HTTP 响应头信息,而目前服务端程序只给浏览器返回了“你好,Ilove python.”,所以浏览器会认为返回的信息错误,即没有正常显示返回内容。 image.png 服务端的 Console 之所以能成功显示 Chrome 浏览器发送过来的所有 HTTP 请求头信息,是因为bufferSize 设置得足够大(1024 字节),所以一次就可以获得浏览器发送给服务端的所有数据,如果bufferSize 设置得比较小,那么就只会获得客户端发送过来的一部分数据。例如,如果将 bufferSize 设为10,那么获取的 HTTP 请求头信息如图 15-6所示。 image.png 很明显,在 Console 中只输出了 HTTP 请求头的前 10字节的内容(GET/geeko),要想获取所有客户端发送过来的数据,需要使用循环不断调用 recv 方法,最多每次获取 10 字节的数据,详细的实现过程见 15.1.2 节。

15.1.2 服务端接收数据的缓冲区

如果客户端传给服务端的数据过多,则需要分多次读取,每次最多读取缓冲区尺寸的数据,也就是 15.1.1 节例子中设置的 bufferSize 变量的值。如果要分多次读取,则根据当前读取的字节数是否小于缓冲区的尺寸来判断是否后面还有其他未读的数据,如果没有,则终止循环。 image.png image.png

15.1.3 服务端的请求队列

通常服务端程序不会只为一个客户端服务,当 accept方法在接收到一个客户端请求后,除非再次调用 accept 方法,否则将不会再等待下一个客户端请求。当然,可以在 accept方法接收到一个客户端请求后启动一个线程(见 15.1.4 节)来处理当前客户端的请求,从而让 accept方法尽可能快地再次被调用(一般会将调用 accept 方法的代码放在一个循环中,见 15.1.4 节的例子),但就算 accept 方法很快被下一次调用,也是有时间间隔的(如两次调用 accept 方法的时间间隔是 100ms),如果在这期间又有客户端请求,该如何处理呢? 在服务端 Socket 中有一个请求队列。如果服务端暂时无法处理客户端请求,会先将客户端请求放到这个队列中,而每次调用 accept方法,都会从这个队列中取一个客户端请求进行处理。不过这个请求队列也不能无限制地存储客户端请求,请求队列的存储上限与当前操作系统有关,例如,有的 Linux系统的请求队列存储上限是 128 个。请求队列的存储上限也可以进行设置,只要通过 listen 方法监听端口号时指定请求队列上限即可(这个值也被称为 backlog),如果这个指定的上限超过了操作系统限 制的最大值(如 128),那么会直接使用这个最大值。 image.png image.png

5.1.4 TCP时间戳服务端

本节会利用 Socket 实现一个可以将时间返回给客户端的时间戳服务端。当客户端向服务端发送数据后,服务端会将这些数据原样返回,并附带上服务端当前的时间

15.1.5 用 Socket 实现 HTTP 服务器

本节会使用 Socket 实现一个 HTTP 服务器。如果要实现 HTTP 服务器,服务端在接收数据,以及 向客户端发送数据时,必须遵循 HTTP 协议。也就是说,服务端在接收数据时,要解析 HTTP 请求头向客户端发送数据时,要在发送的数据前面加上 HTTP请求头。关于 HTTP 的详细信息请访问如下的URL: tools.ietf.org/html/rfc261… 其实读者也不需要深入了解 HTTP 协议,因为 HTTP 协议是 Web 中经常使用的协议,已经有很多实现了,通常并不需要自己去实现完整的HTTP 协议。本节之所以要自己实现 HTTP 协议,只是为了演示 Socket 的功能,而且并没有实现完整的 HTTP 协议,只是通过 HTTP 协议实现了一个最基本的HTTP 服务器。 下面解释一下 HTTP 服务器的实现原理。首先应该了解一下 HTTP 请求头的格式。HTTP 请求头是纯文本形式,除了第1行,其他行都是 key-value 形式。例如,下面是一个典型的 HTTP 请求头。 GET /main/index.html HTTP/1.1 Host:geekori.com Accept:/ Pragma: no-cache Cache-Control:no-cache Referer:download.microtool.de/ User-Agent:Mozilla/4.04en Range:bytes=554554- 大家不必理会上面的大多数内容,因为浏览器会自动发送这些内容,服务端也会自动处理。但必须了解第1行,因为在第1行中包含了请求路径。第1行分为如下三个部分,中间用空格分隔。 (1)方法(GET、POST等)。 (2)请求路径,需要将其映射成服务端对应的本地文件路径 (3)HTTP 版本,目前一般是 1.1。由于本节要实现的是一个基本的 HTTP 服务器,所以只考虑第1行。HTTP 响应头与 HTTP 请求头类似,下面是一个 HTTP 响应头的例子。 HTTP/1.1 200 OK Date:Mon,31Dec2012 04:25:57GMT Server:Apache/1.5(UNIX) Content-type:text/html Content-length:1234 HTTP 响应头只有两行是必须指定的,即第1行和 Content-length 字段。第1行描述了 HTTP 版本。 返回状态码等信息,其中 200和 OK 描述了访问成功。如果要描述页面没找到,可以返回 404。不过本节的例子不管是否找到服务端的页面,都返回了 200,只是在没找到页面时固定返回了“File Not Found”信息。读者可以自己修改这个例子,做更有趣的实验。 还有一点要注意,HTTP 响应头在返回时,一定与后面的要返回的内容之间有一个空行,浏览器会依赖这个空行区分 HTTP 响应头到哪里结束。

image.png

image.png

image.png 现在运行程序,然后在当前目录建立一个 static 子目录,并在该目录中建立两个文件:test.txt 和index.html。分别输入如下的内容:

test.txt
hello world
index.html

<h1>Main Page</h1>

接下来在当前路径建立一个 response_headers.txt 文件,并输入如下内容。在该文件中使用了“%d’作为格式化符号,在读取该文件时需要将“%d”格式化为发送到客户端数据的长度,单位是字节。

HTTP/1.1 OK
Server:custom
Content-type:text/html
Content-length:%d

现在打开浏览器,在浏览器地址栏中输入如下的 URL: http://localhost:9876

会在浏览器中显示如图 15-13 所示的内容。 image.png 如果在浏览器地址栏中输入 http://ocalhost:9876/test.txt,那么会输出如图 15-14 所示的内容 image.png

15.1.6 客户端 Socket

image.png 在前面的部分一直使用 telnet 和浏览器作为客户端测试 Socket 服务端,其实 socket 类同样可以作为客户端连接服务器。socket 类连接服务端的方式与创建 Socket 服务端类似,只是这时 host (IP 或域名)就有用了,因为客户端 Socket 在连接服务端时,必须指定服务器的IP 或命名。当然,端口号也是必需的。在浏览器中使用 http/https 访问 Web 页面时,之所以没有指定端口号,是因为使用了默认的端口号,http 的默认端口号是 80,https 默认的端口号是 443。 客户端 Socket 成功连接服务端后,可以使用 send 方法向服务端发送数据,也可以使用 recv 方法接收从服务端返回的数据,使用方法与服务端 Socket 相同。

from socket import *
#服务器的名称(可以是 IP 或域名)
host ='localhost'
#服务器的端口号
port= 9876
#客户端 Socket 接收数据的缓冲区
buffersize=1024
addr =(host,port)
tcpClientSocket = socket(AF_INET, SOCK_STREAM)
#开始连接时间戳服务端
tcpClientSocket.connect (addr)
while True:
  #从终端采集用户输入信息
   data = input('>')
   #如果什么都未输入,退出循环
    if not data:.
        break
     #将用户输入的字符串按 utf-8 格式编码成字节序列
     data = data.encode('utf-8')
     #向服务端发送字节形式的数据
      tcpClientsocket.send(data)
      #从服务端接收数据
      data = tcpClientSocket.recv(buffersize)
      #输出从服务端接收到的数据
      print(data.decode('utf-8'))
#关闭客户端 Socket
tcpClientSocket.close()

image.png

15.1.7 UDP 时间戳服务端

image.png

image.png UDP 与TCP 的一个显著差异就是前者不是面向连接的,也就是说,UDPSocket 是无连接的,TCESocket是有连接的。那么什么是无连接?什么是有连接呢?有连接的网络传输协议(如 TCP)是指在网络数据传输的过程中客户端与服务端的网络连接会一直存在,而且面向连接的网络传输协议会通过某些机制保证数据传输的可达性,如果用比较科幻的说法就是通过面向连接的网络协议在客户端和服务端建立一个稳定的虫洞,可以放心大胆地在虫洞中传递数据。面向无连接的网络协议(如 UDP)相当于将一東光射向远方,对于发射光源的一方只负责开启光源,至于射出的这束光能不能到达目的地那就不管了。当然,这束光有可能会到达目的地,也有可能发生意外,如碰到某个障碍物或被散射。因此,通过像 UDP 这类无连接的网络协议传输的数据不能保证 100%到达目的地,但操作更简单,没有像 TCP 这类有连接的网络协议需要那么多设置。 本节会利用 UDP 服务实现一个与 15.1.4 节实现的时间戳服务端功能完全相同的服务端程序。当 然,一般都是在本地测试(使用 localhost 或 127.0.0.1),所以几乎不会出现传输的数据不会到达目的地的情况,但在复杂的网络中运行本节的例子,就有可能会出现数据无法传输到目的地的情况。

实例位置:PythonSamples\src\chapter15\demo15.07.py
from socket import *
from time import ctime
host= ' '
port = 9876
buffersize = 1024
addr =(host, port)
# SOCK DGRAM 表示 UDP
udpServerSocket = socket(AF_INET, SOCK_DGRAM)
udpServerSocket.bind(addr)
while True:
     print('正在等待消息.......')
     #接收从客户端发过来的数据
      data, addr = udpServerSocket.recvfrom(buffersize)
       #向客户端发送服务端时间和客户端发送过来的字符串
       udpServerSocket.sendto(ctime().encode (encoding='utf-8') + b' '+ data,addr)
       print('客户端地址:',addr)
udpserverSocket.close()

要注意的是,使用 UDP Socket发送和接收数据的方法与 TCP Soeke 不同。UDp Socket接收数据的方法是recvfrom,发送数据的方法是 sendto.

15.2 socketserver 模块

socketserver 是标准库中的一个高级模块,该模块的目的是让 Socket 编程更简单。在 socketserver模块中提供了很多样板代码,这些样板代码是创建网络客户端和服务端所必需的代码。本节会利用socketserver 模块中的 API重新实现时间戳客户端和服务端,从中会看到,使用 socketserver 模块实现的时间戳服务端的代码更简洁,也更容易维护。

15.2.1 实现 socketserver TCP 时间戳服务端

socketserver 模块中提供了一个 TCPServer 类,用于实现 TCP 服务端。TCPServer 类的构造方法有两个参数:第1个参数需要传入 host 和 port(元组形式);第2个参数需要传入一个回调类,该类必须是 StreamRequestHandler 类的子类。在 StreamRequestHandler 类中需要实现一个 handle 方法,如果接收到客户端的响应,那么系统就会调用 handle 方法进行处理,通过 handle 方法的 self参数中的响应API 可以与客户端进行交互。

#将 TCPServer 类重命名为 TCP,将 streamRequestHandler 类重命名为 SRH
from socketserver import (TCPServer as TCP,StreamRequestHandler as SRH)
from time import ctime
host = ' '
port = 9876
addr =(host,port)
#定义回调类,该类必须从 streamRequestHandler 类(已经重命名为 SRH)继承
class MyRequestHandler(SRH):
     #处理客户端请求的方法
     def handle (self):
     #获取并输出客户端 IP和端口号
     print('客户端已经连接,地址:"self.client address)
     #向客户端发送服务端的时间,以及按原样返回客户端发过来的字符串
     self.wfile.write(ctime().encode(encoding='utf-8')+ b' '+ self.rfile.readline())
#创建 TCPServer 类(已经重命名为 TCP)的实例
tcpServer = TCP(addr, MyRequestHandler)

image.png 从上面的代码可以看出,在 handle 方法中通过 self.rfle.readline 方法从客户端读取数据,通过self.wfle.write 方法向客户端发送数据。由于读取客户端数据使用了 readline 方法,该方法读取客户端发送过来的数据的第 1行,所以客户端发送过来的数据至少要有一个行结束符(“\r\n”或“\n"),否则服务端在读取客户端发送过来的数据时会一直处于阻塞状态,直到超时才结束读取。

15.2.2实现 socketserver TCP 时间戳客户端

socketserver TCP 客户端与前面实现的 TCP Socket 客户端没什么区别,只是在向 socketserver TCP时间戳服务端发送文本数据时要加一个行结束符。 image.png image.png

第16章 网络高级编程

16.1 urllib3 模块

urllib3 是一个功能强大,条理清晰,用于编写 HTTP 客户端的 Python 库,许多 Python 的原生系统已经开始使用 urllib3。urllib3 提供了很多 Python 标准库里所没有的重要特性,这些特性包括: 口线程安全 口 连接池 口 客户端 SSL/TLS 验证 口 使用 multipart 编码上传文件 口 协助处理重复请求和 HTTP 重定位 口 支持压缩编码 口 支持 HTTP 和 SOCKS 代理口 100%测试覆盖率 wli3 并不是pmben 语言的标准模块,因此,使用 urib3 之前需要使用 pip 命令或 conda 命今寸装 urllib3: pip install urllib3 或 conda install urllib3

16.1.1 发送 HTTP GET 请求

image.png

image.png 使用 urllib3 中的 API 向服务端发送 HTTP 请求的过程:首先需要引用 urllib3 模块;然后创PoolManager 类的实例,该类用于管理连接池;最后通过 request 方法发送 GET 请求,request 方法的 返回值就是服务端的响应结果,通过 data属性直接可以获得服务端的响应数据。 当向服务端发送 HTTP GET 请求时,如果请求字段值包含中文、空格等字符,需要对其进行编码如果在 urllib.parse 模块中有一个 urlencode 函数,则可以将一个字典形式的请求值对作为参数传入urlencode 函数,该函数返回编码结果。

#使用 urlencode 函数将"极客起源"转换为 URL编码形式print(urlencode({'wd':'极客起源'}))
执行上面的代码,会输出如下的内容:
wd=号E6号9E号81号E5号AE号A2号E8号B5号B7号E6号BA号90
使用 request 方法发送 HTTP GET 请求时,可以使用 urlencode 函数对 GET 字段进行编码,也可以直接使用 fields 关键字参数指定字典形式的 GET 请求字段。使用这种方式,request 方法会自动对 fields关键字参数指定的 GET 请求字段进行编码。
# http 是 PoolManager 类的实例变量http.reguest('GET', url,fields={'wd':'极客起源'})
16.1.2 发送 HTTP POST 请求

image.png 向服务端发送比较复杂的数据时,通过 HTTP GET 请求不太合适,因为 HTTP GET 请求将要发送数据都放到 URL 中。因此,当向服务端发送复杂数据时,建议使用 HTTP POST 请求。 HTTP POST 请求与 HTTP GET 请求的使用方法类似,只是在向服务端发送数据时,传递数据会跟在HTTP 请求头后面,因此,可以使用 HTTP POST 请求发送任何类型的数据,包括二进制形式的文件(一般会将这样的文件使用 Base64 或其他编码格式进行编码)。为了能更好地理解 HTTP POST请求,本节首先编写一个专门接收 HTTP POST 请求的服务端。可以使用第 15 章介绍的服务端 Socket编写,不过手工处理HTTP请求太麻烦了,所以本节使用一个基于Python语言的轻量级 web框架Flask,只需要几行代码就可以轻松编写一个处理 HTTP POST 请求的服务端程序。 Flask 属于 Python 语言的第三方模块,需要单独安装,不过如果使用的是 Anaconda Python 开发环境,就不需要安装 Flask 模块,因为 Anaconda 已将Flask模块集成到里面了。如果使用的是标准的Pythor开发环境,可以使用 pip install flask 命令安装 Flask 模块。本节只是利用 Flask 模块编写一个简单的可以处理 HTTP POST 请求的服务端程序。如果对某些代码不理解也不要紧,在后面的章节会详细介绍Flask 模块的使用方法。

image.png

16.1.3 HTTP 请求头

image.png

大多数服务端应用都会检测某些 HTTP 请求头,例如,为了阻止网络爬虫或其他的目的,通常会检测 HTTP 请求头的 user-agent 字段,该字段指定了用户代理,也就是用什么应用访问的服务端程序,如果是浏览器,如 Chrome,会包含 Mozilla/5.0 或其他类似的内容,如果 HTTP 请求头不包含这个字段,或该字段的值不符合要求,那么服务端程序就会拒绝访问。还有一些服务端应用要求只有处于登录状态

16.1.4 HTTP 响应头

image.png 使用 HTTPResponse.info 方法可以非常容易地获取 HTTP 响应头的信息。其中,HTTPResponse 对象是 request 方法的返回值。

16.1.5 上传文件

image.png

image.png 客户端浏览器向服务端发送 HTTP 请求时有一类特殊的请求,就是上传文件,为什么特殊呢?因发发送其他值时,可能是以字节为单位的,而上传文件时,可能是以KB或 MB 为单位的,发送的文尺寸通常比较大,所以上传的文件内容会用 multipart/form-data 格式进行编码,然后再上传。urllib3对件上传支持得非常好,只需要像设置普通的 HTTP 请求头一样在 request 方法中使用 fields 关键字多指定一个描述上传文件的 HTTP 请求头字段,然后再通过元组指定相关属性即可,例如,上传文件名、文件类型等。

# http是 PoolManager 类的实例
#上传任意类型的文件(未指定上传文件的类型)
http.request('posr',url,fields={'file':(filename,fileData)})
#上传文本格式的文件
http.request ('posr',url,fields=('file':(filename, fileData,'text/plain')})
#上传 jpeg 格式的文件
http.request ('posr',url,fields={'file':(filename,fileData,'image/jpeg')))
16.1.6 超时

image.png 由于 HTTP 底层是基于 Socket 实现的,所以连接的过程中也可能会超时。Socket 超时分为连接超时和读超时。连接超时是指在连接的过程中由于服务端的问题或域名(IP 地址)弄错了而导致的无法连接服务器的情况,当客户端 Socket 尝试连接服务器超过给定时间后,还没有成功连接服务器,那么会自动中断连接,通常会抛出超时异常。读超时是指在从服务器读取数据时由于服务器的问题,导致长时间无法正常读取数据而导致的异常。 使用 urllib3 模块中的 API 设置超时时间非常方便,只需要通过 request 方法的 timeout 关键字参数指定超时时间即可(单位是秒)。如果连接超时与读超时相同,可以直接将 timeout 关键字参数值设为一个浮点数,表示超时时间。如果连接超时与读超时不相同,需要使用 Timeout 对象分别设置。

#http是 PoolManager 类的实例
#连接超时与读超时都是5s
http.reguest('GET', urll,timeout=5.0)
#连接超时是2s,读超时是 4s
http.reguest('GET', urll,timeout=rimeout(connect=2.0,read=4.0))
如果让所有网络操作的超时都相同,可以通过 PoolManager 类构造方法的 timeout 关键字参数设置连接超时和读超时。
http = PoolManager(timeout=rimeout(connect=2.0,read=2.0))
如果在 request 方法中仍然设置了 timeout 关键字参数,那么将覆盖通过 PoolManager 类构造方法设置的超时。

image.png

16.2 twisted 框架

image.png

image.png

image.png

image.png

image.png twisted 是一个完整的事件驱动的网络框架,利用这个框架可以开发出完整的异步网络应用程序,有很多著名的 Python 模块是基于 twisted 框架的,例如,后面要讲的网络爬虫框架 Scrapy 就是使用twisted 框架编写的。 twisted 并不是 Python 的标准模块,所以在使用之前需要使用 pip install twisted 安装 twisted 模块如果使用的是 Anaconda Python 开发环境,也可以使用 conda install -c anaconda twisted 安装 twisted模块。 口 twisted 的 Reactor 模式必须通过 run 函数启动。 口 Reactor 循环是在开始的进程中运行的,也就是运行在主进程中。 口 一旦启动 Reactor,就会一直运行下去。Reactor会在程序的控制之下。 □ Reactor 循环并不会消耗任何 CPU 资源。 口 并不需要显式创建 Reactor 循环,只要导入 reactor 模块即可。也就是说,Reactor 是 Singleto (单件)模式,即在一个程序中只能有一个 Reactor。 twisted 可以使用不同的 Reactor,但需要在导入 twisted.internet.reactor 之前安装它。例如,引用pollreactor的代码如下: from twisted.internet import pollreactor pollreactor.install() 如果在导入 twisted.intermet.reactor 之前没有安装任何特殊的 Reactor,那么 twisted 会安装selectreactor。正因为如此,习惯性做法是不要在顶层的模块内引入 Reactor 以避免安装默认的 Reactor而是要使用 Reactor 的区域内安装。 下面的代码安装了 pollreactor,然后导入和运行 Reactor。

from twisted.internet import pollreactor
#安装 pollreactor
pollreactor.install()
from twisted.internet import reactor
reactor.run()

其实上面的这段代码还是没做任何事情,只是使用了pollreactor 作为当前的 Reactor. 下面这段代码在 Reactor 循环开始后向终端输出一条消息。

def hello():
   print('Hello,How are you?')
from twisted.internet import reactor
#执行回调函数
reactor.callWhenRunning(hello)
print('Starting the reactor.")
reactor.run()

image.png 在上面的代码中,hello 函数是在 Reactor 启动后被调用的,这就意味着 twisted 调用了 hello 函数。 通过调用 Reactor 的 callWhenRunning 函数,让 Reactor 启动后回调 calWhenRunning 函数指定的回调数。 这段代码可以在 basic-twisted/hello.py 中找到。 关于函数回调需要了解以下几点: 口 Reactor 模式是单线程的。 口 像 twisted 这种交互式模型已经实现了 Reactor 循环,这就意味着无须我们亲自去实现它口 仍然需要框架调用自己的代码来完成业务逻辑。 口因为在单线程中运行,所以要想运行自己的代码,必须在 Reactor 循环中调用它们。 口 Reactor 事先并不知道调用代码中的哪个函数。 回调并不仅仅是一个可选项,而是游戏规则的一部分。图 16-16所示为回调过程。

image.png

很明显,用于回调的代码是传递给 twisted 的。

16.2.4 用 twisted 实现时间戳客户端

image.png twisted 框架的异步机制是整个框架的基础,可以在这个基础上实现很多基于异步编程模型的应用,本节会利用 twisted 框架的相关 API 实现一个时间戳客户端,该程序与 15.1.6 节实现的案例在功 能上完全相同。

连接服务端 Socket,需要调用 connectTCP 函数,并且通过 giant 函数的参数指定 host 和 port,以及一个工厂对象,该工程对象对应的类必须是 ClientFactory 的子类,并且设置了 protocol 等属性。protocol属性的类型是 Protocol 对象,Protocol相当于一个回调类,Protocol类的子类实现的很多父类 的方法都会被回调。

16.2.5 用 twisted 实现时间戳服务端

image.png

16.2.6 用 twisted 获取 Email 邮箱日录列表

twisted 框架还有很多与网络有关的功能,例如,操作 Email 就是其中最重要的功能之一。本节会使用其中的 imap 模块读取 Email 中指定邮箱的目录列表,这个程序相对比较复杂,请直接看代码。

第17章 多线程

17.1 线程与进程

17.1.1 进程
17.1.2 线程

17.2 Python线程

image.png

17.2.1 使用单线程执行程序
17.2.2 使用多线程执行程序

image.png

17.2.3 为线程函数传递参数

image.png

17.2.4 线程与锁

image.png

17.3 高级线程模块

17.3.1 Thread类与线程函数

image.png

17.3.2 Thread类与线程对象

image.png

image.png

17.3.3 从Thread类继承

image.png

17.4 线程同步

image.png

17.4.1 线程锁

image.png

image.png

17.4.2 信号量

image.png

image.png

image.png

image.png

17.5 生产者-消费者问题与queue模块

image.png

image.png

image.png

第18章 GUI库:tkinter

image.png

18.4 控件

本节会介绍 tkinter 中的常用控件。由于 tkinter 依托于强大的 Python 语言,所以学习 tkinter其实主要就是学习布局和控件,然后就可以利用 Python 语言海量的原生模块和第三方模块编写拥有强大功能的 GUI 程序。

18.4.1 Label 控件和 Button 控件

Label 控件在前面的内容中已经多次使用过了,这个控件是整个 tkinter 中最简单的控件。本节先会回顾一下 Label控件,并介绍一些以前没接触到的内容。 Label类的构造方法需要传入一些必要的参数值,例如,第 1个参数通常是窗口对象,然后通过text参数设置要显示的文本,通过fg属性设置文本的演示,通过 width 和 height 参数设置 Label控件的宽度和高度。


Label(window, text='Hello world', fg-'blue', bg='green', width=20, height=2)
除了上述的这些参数外,还可以通过fonl 参数设置 [abel 控件中显示文本的字体和字号。
Label(waacow, text-"nello world', fg='blue’, bg=iareen', font=('arial', 12)width=20, height=2)
var = StringVar()
Label(window, textvariable=var)
#设置 iabe1 控件中的文本
var.set('Hello world')
#获取并输出 Labe1 控件中的文本print(var.get())

Button 是tkinter 中另外一个非常常用的控件,主要用来与用户交互,用户会通过单击按钮通知程完成一些任务,而程序一般在完成任务后,会给用户一些反馈,例如,会在Console 中输出消息,会弹出一个对话框等。

Button 控件与 Label 控件在使用上几乎是一样的,只是 Button 控件还需要一个处理单击事件的回调函数,这个回调函数需要通过 Button 类的 command 关键字参数指定。

实例位置:PythonSamples\src\chapter18\demo18.12.py
import tkinter as tk
import random
window = tk.Tk()

window.title('Label 控件和 Button 控件')
window['background']='blue'
window.geometry("300x200+30+30")
# 创建Label 控件,设置文字字体为 aria1,字号是 12
labe11 = tk.Label(window,
   text='Hello world',
   bg='green', font=('Arial',12), width=20, height-2)
label1.pack()
#创建一个用于绑定 abel 控件的变量
var = tk.StringVar()
# 初始化变量
var.set('Hello world')
#创建labe1 控件,并与 var 变量绑定
label2 = tk.Label(window,.
     textvariable=var,fg = 'blue',
     bg='yellow', font=('arial',12), width=15, height-2)
#使用 pack 布局摆放 Label 控件,并设置 Label 控件的垂直外边距为 20
label2.pack(pady = 20)
onHit = False
第1个按钮的单击回调函数
def hitMe():
    global onHit
    if onHit == false:
       onHit = True
       var.set('世界你好')
     else:
       onHit = False
       var.set('Hello world')
#创建第1个 Button元
button1 = tk.Button (window,
    text='单击我'
    command=hitMe)
buttonl.pack()
#第2个按钮的单击回调函数
def getLabelText():
   #输出 label 控件的文本
    print (var.get())
#创建第2个 Button 控件
button2 = tk,Button (window,
   text='获取 Label 控件的文本!
   command=getLabelText)
#使用 pack 布局摆放 Button 控件,并设置 abel 控件的垂直外边距为 20
button2.pack(pady = 20)
window.mainloop()

image.png

18.4.2 Entry 控件与 Text 控件

Entry控件与Text控件都是用来输入文本的。Entry是单行文本输入控件,而 Text是多行文本输入控件,而且支持图像、富文本等格式。

Entry 控件与 Text 控件的基本使用方法与前面介绍的 Label 控件、Buton 控件类似,只是多了一些特殊的属性、例如,可以使用 Entry类构造万法的show关键字参数指定录入文木时回显某个字符

实例位置:PythonSampleslsrelchapter18\demo18.13.py
import tkinter as tk
window = tk.Tk()
window.title('Entry 控件与 rext 控件')
window['background']='blue'
window.geometry("600x500+30+30")
#该变量绑定了第1个 Entry 控件
entryvarl = tk.Stringvar()
#在第1个 Entry 控件中输入文本时回调的函数
def callback():
   #更新第2个 Entry控件中的文本
   entryVar2.set(entryvarl.get())
#将第1个 Entry控件与entryVar1 绑定,w表示当写入时调用 callback,其中a、b、c是Lambda 表达式#要求传入的3个参数,在本例用不着这3个参数,但必须要指定,否则会抛出异常
entryVar1.trace("w", lambda a,b,c: callback())
#创建第1个 Entry 控件
entryl = tk.Entry(window,textvariable=entryVar1,show='*')
#对第1个 Entry 控件使用 pack 布局,垂直外边距为 10
entry1.pack(pady=10)
#该变量绑定了第2个 Entry控件
entryVar2 = tk.StringVar()
#创建第2个 Entry 控件
entry2 = tk.Entry(window,textvariable=entryVar2)
#对第2个 Entry 控件使用 pack 布局,垂直外边距为 10
entry2.pack(pady=10)
#创建 Text 控件
text = tk.Text(window)
#对 Text 控件使用 pack布局,垂直外边距为 10
text.pack(pady = 10)
#由于 Text 控件只支持少数几种图像格式(gif、bmp 等),不支持 jpg、png,所以要插入这些不支持格式的图
#需要用 PIL处理下
from PIL import Image, ImageTk
#装载 pic.png
pic = Image.open('pic.png")
photol=ImageTk.PhotoImage(pic)
#在 Text 控件的结尾插入图像
text.image create(tk.END, image=photo1)
#进行字体、字号等设置,需要通过 big 引用
text.tag configure('big', font=('Arial', 25, 'bold'))
#在 rext 控件的结尾插入文本,并使用 big 指定的字体属性
text.insert(tk.END,"臭美",'big')
ha = Image.open('ha.jpg')
photo2=Imagerk.PhotoImage(ha)
在 rext 控件的结尾插入图像
text.image create(tk.END, image=photo2)
Window,mainloop()

第19章 GUI库:PyQt5

image.png

19.2 安装PyQt5

image.png image.png

image.png

image.png

image.png

19.3 编写第一个 PyQt5 程序

image.png 在这一节来编写第一个基于 PyQ15 的程序,尽管在 19.2.3 节已经编写了一个比较复杂的 PyQt5 程序,不过大多数代码是通过 PyUIC生成的。为了更深入了解编写PyQt5 程序的方法,本章仍然主要采用手工编写代码的方式学习 PyQt5。当然,如果对这些技术已经比较精通,可以直接使用 QTDesigner来设计UI。

编写一个 PyQt5 程序必须使用两个类:QApplication 和 QWidget。这两个类都在 PyQt5.QtWidgets模块中,所以首先要导入这个模块。

QApplication类的实例表示整个应用程序。该类的构造方法需要传入 Python 程序的命令行参数(需要导入 sys 模块),因此,基于 PyOt5 的程序也能在终端中执行,并传入命令行参数。

QWidget 类的实例相当于一个窗口,在 19.22节中图 19-5所示的控件都可以放到这个窗口上。可以通过 QWidget 实例中的方法控制这个窗口,例如,通过 resize 方法改变窗口的尺寸,通过 move 方法移动窗口,通过 setWindowTitle 方法设置窗口的标题。最后,还需要调用 show 方法显示窗口。要注意的是,调用 show 方法显示窗口后,程序并不会处于阻塞状态,会继续往下执行,通常需要在程序的最后调用 app.exec_方法进入程序的主循环,在主循环中会不断检测窗口中发生的事件,如单击按翻事件,当窗口关闭后,主循环就会结束,一般会通过 sys.exit 函数确保主循环安全结束。

实例位置:PythonSamples\src\chapter19\demo19.01.py
import sys
#导入 Qapplication 类和 Qwidget 类
from Pyot5.OtWidgets import QApplication, Qwidget
if __name__ == '__main__':
 #创建 Qapplication 类的实例,并传入命令行参数
 app = QApplication(sys.argv)
 #创建 Qwidget 类的实例,相当于创建一个窗口
 w= OWidget()
 #将窗口的宽设为 250,高设为 150
 w.resize(250,150)
 #移动窗口
 w.move(300,300)
 #设置窗口的标题
 w.setwindowritle('第一个 PyQt5 应用')
 #显示窗口
 w.show()
 #进入程序的主循环,并通过 exit 函数确保主循环安全结束
 sys.exit(app.exec ())

image.png

19.4 窗口的基本功能

image.png

19.4.4 消息盒子

消息盒子 (MessageBox)其实就是各种类型的消息对话框,如信息对话框、警告对话框、询问对话框等。这些对话框的区别主要是对话框的图标以及按钮的个数。QMessageBox类提供了若干个静态方法可以显示各种类型的对话框。例如,information 方法用于显示信息对话框,warning 方法用于显示警告对话框,question方法用于显示询问对话框。这些方法的使用方式类似。

第20章 测试

第三篇 Python Web开发

第21章 Python Web框架:Flask

21.1 Flask 基础知识

image.png 本书将介绍一些Fask的基础知识,如路由、Request、Response、Cookie、Sesion等。如果使用的是Anaconda Python开发环境,Flask 框架已经集成到 Anaconda 中,所以并不需要单独安装Flask。 如果使用的是标准的 Python 开发环境,可以使用下面的命令安装 Flask: pip install Flask

21.1.1 使用8行代码搞定 Web 应用

Flask 框架的一大特色就是简单。只需要很少的代码,就可以编写一个可以运行的 Web 应用。下面就看一下使用 Flask 框架开发 Web 应用的基本步骤。 (1)导入Flask 模块:与Flask 相关的 API 都在 Flask模块中,所以在使用 Flask 框架之前,必复导入 Flask 模块。 (2)创建Flask对象:一个 Flask 对象表示一个 Flask 应用。 (3)编写路由:要想在浏览器中通过 URL 访问 Web 应用,必须至少编写一个路由。这里的路由其实就是客户端请求的 URL 与服务端处理这个 URL 的程序的一个映射。Flask 中一个路由就是一个Python 函数。 (4)调用 Flask对象的 run 方法启动 Web 应用:要想长久处理客户端的请求,Web 应用必须永久 运行。调用 run方法后,Web 应用就会一直处于运行状态,以便等待客户端的请求。

image.png

image.png

21.1.2 静态路由和动态路由

image.png 路由分为静态和动态两种,静态路由就是在上一节使用的路由,直接使用@app.route 定义,route的参数值就是路由,也就是在浏览器地址栏中输入 URL 的路径。例如,@app.route(/greet/abe)表示访该路由的URL 是https://localhost:5000/abc。 尽管静态路由可以解决大多数问题,但如果有多个类似的路由要使用同一个路由函数处理,或想通过URL的路径传递一些参数,就要用到动态路由。先看下面几个 URL。 http://localhost:5000/greet/xyz http://localhost:5000/greet/abc http://localhost:5000/greet/what http://localhost:5000/greet/test http://localhost:5000/greet/geekori

image.png

image.png

21.1.3 获取 HTTP 请求数据

客产端通过URL访问服务端程序,会发送给服务端两类信息,一类是 HTTP 请求头,另外一类就是请求数据,一般HTTP请求会通过GET方法和POST方法向服务端提交数据。因此,服务端程序需要获得客户端的这些请求数据,然后会做进一步的处理。例如,如果服务端要想对客户端的类型(使用什么浏览器)做一下统计,就需要获取 HTTP 请求头中的User-Agent 字段的值。如果要得到客户端表单提交的数据,就要在服务端获取 GET 请求或 POST 请求的数据。

读取POST请求在本章后面的部分会详细介绍,本节先看一个如何读取 HTTP 请求头和GET请求的数据,在Flask中读取HTTP请求头和GET请求的数据需要导入 flask模块中的一个全局变量request,然后使用 reguest.headers.get(…)读取 HTTP 请求头数据。get方法的参数就是 HTTP 请求头字段的名称。使用 request.args.get(…)读取 GET 请求中的某个字段的值。get 方法的参数值就是 GET 请求的字段名称

image.png

image.png

21.1.4 Response与Cookie

我们已经知道,路由函数的返回值会作为 HTTP 响应信息返回给客户端。不过如果要对 HTTP 响应信息做更复杂的操作,如设置 HTTP 响应头,就需要获得 HTTP 响应对象,也就是 Response 对象。

获取 Response 对象需要导入 Flask 模块的 make_response 函数,该函数用于返回一个flask.wrappers.Response 对象,然后路由函数直接返回这个 Response 对象即可。

Response 对象有很多常用的场景,例如,可以通过 Response 对象向客户端写入 Cookie。相信编写过Web 应用的读者应该对 Cookie 很了解。Cookie 其实就是服务端向客户端浏览器写入的一段文本信息(最大是 4KB),那么服务端是怎么通知客户端要写入什么的?其实就是通过 HTTP 响应头向客户览器发送要写入的 Cookie 信息。也就是说,在服务端写入 Cookie 的操作就是设置 HTTP 响应头,这就要用到 Response 对象中的 set_cookie 方法。该方法需要传入三个参数:第1个参数是 Cookie 的key,第2个参数是 Cookie 的值,第3个参数是 Cookie 的过期时间。


#向客户端写入Cookie,有效期是20s。20s后,cookie自动失效
response.set cookie('name','lining',max age=20);

Cookie 的主要目的是跟踪客户端浏览器。当某个浏览器访问了服务端,服务端就会向客户端浏览号入一个或多个 Cookie。当该浏览器再次访问服务端时,服务端就会知道这个浏览器曾经访问过服线,那么这是如何做到的呢?这就涉及浏览器读取 Cookie,并将其通过 HTTP 请求发送给服务端的过程,浏览器读取 Cookie 是自动的,不需要干涉。但对于服务端程序来说,需要读取从客户端浏览器发过来的Cookie,这就要使用到前面介绍的 request 变量。

#从客户端读取名为name 的 cookie 的值,并将读取结果赋给 value 变量
value = reguest.cookies.get('name');
21.1.5 会话

Cookie 是存储在客户端的,而会话(Session)是存储在服务端的,也可以将 Session 称为服务端的Cookie。因为 Cookie 和 Session 的使用方式都和字典一样,通过 key 存储和获取值。

服务端会为每一个客户端浏览器创建一个 Session 对象。也就是说,客户端浏览器不仅仅可以在本地将数据保存在 Cookie 中,还可以将敏感或大量的数据存储到服务端的 Session 对象中。

在Flask 中使用 Session 需要如下几步: (1)导入 flask 模块中的 session 变量。 (2)在设置 Session 时将 session.permanent 属性设为 True。 (3)设置 app.secret_key,用于对保存到客户端的 Session-Cookie-id 加密. (4)通过 app.permanent_session_lifetime 属性设置 Session 的有效期。

上面步骤中涉及一个 Session-Cookie-id,这是什么东西呢?其实这就要涉及 Session 内部实现的原理,前面已经讲了,Session 对象与客户端浏览器是一一对应的。也就是说,使用浏览器 A 访问服务和使用浏览器B访问服务端,使用的是两个 Session 对象。当然,用浏览器A多次访问服务端,仍会使用同一个 Session 对象。那么服务端如何知道是哪一个客户端浏览器访问的服务端呢?其实在医务端设置 Session 时,会向客户端自动写入一个 Cookie,这个 Cookie 是一个 ID。这个 ID 也用来在务端寻找 Session 对象。因为在服务端可能会为多个客户端创建多个 Session 对象,通过每个 Session的ID来定位相应的 Session 对象。也就是说,在服务端会有一个大的字典,字典的 Key 就是 Session ID,过这个Key,可以找到对应的 Session 对象。每一个 Session 对象也相当于一个小的字典,Key 是保存在Session 中的字段名,Value 是字段值。由于 Session ID 是通过 Cookie 写入客户端的,所以也可以称这个ID为 Session-Cookie-ID。当浏览器再次访问服务端时,就会将 Session-Cookie-ID 连同其他的(odkièe数据一起发送给服务端,服务端获得这个 Session-Cookie-ID 后,就会找到对应的 Session 对象。

image.png

21.1.6 静态文件和重定向

Web 应用有一类重要资源:静态文件。例如,html、js、css、图像文件等都属于静态文件。在客户端访问服务端的静态资源一般有两种方式:第1种方式是转发,第2种方式是直接访问。转发一般路由与静态文件做一个映射,通过 Flask 对象的 send_static_file 方法进行转发。直接访问是在浏览器中直接输入静态资源的 URL,或通过路由进行重定向。重定向可以通过 redirect 函数完成。静态资源默认要放在static 目录中。所以在放置静态资源之前,先在 Python 源文件所在的目录建立一个 static子目录,然后在 static 目录放置一些静态资源,如 p.png、test1.txt等。

image.png

21.2 Jinja2 模板

虽然通过路由函数的返回值可以向客户端发送 HTML 代码,但如果代码非常复杂,就不适合将HTML,JS等进行硬编码。当然,可以将这些代码保存在文件中,然后通过 open 函数或其他 API 读取文件的内容,再使用路由函数返回这些代码。首先可以肯定,这是一个非常好的处理方式,但不用这么麻烦,因为Flask已经提供了类似的功能,这就是Jinja2模板。

21.2.1 第一个基于 Jinja2 模板的 Web 应用

image.png

image.png

21.2.2 在 Jinja2 模板中使用复杂数据

image.png 在上一节向 Jinja2 模板文件传递了一个字符串类型的值,但在实际应用中,还需要传递一些复杂类型的数据,例如,列表、字典、对象等。向模板文件传递这些复杂类型数据的方式与内建类型(整数、字符串等)类似。但在模板文件中,要引用变量内部的值。如传递一个列表 mylist,需要在模板文件中使用 mylist[0]、mylist[1]引用列表中的元素。传递一个字典 mydict,需要在模板文件中使用 mydict["key']引用key 对应的 value。 image.png

21.2.6 宏操作

在编写 Python 程序时,代码会越来越多,而且还有很多地方调用同样或类似的代码。在这种情况下,通常会将这些被多处重复调用的代码放到函数或类中,只需要访问函数或类的实例就可以实现代码复用。解决代码过多问题的方案是将代码分散在多个 Python 脚本文件中,然后通过 import导入相关的模块(Python 脚本文件)。

以上是编写 Python 程序时可能遇到的问题,其实在使用 Jinja2 模板时也会遇到这种情况。在Jinja2模板中使用宏来防止代码冗余。例如,要利用循环控制指令生成一个表格,循环控制指令需要对一个对象列表进行迭代。每一个列表元素都是一个对象,都包含 id、name、age 三个属性。而读取这三个属性值并生成单元格的代码还会在另外一个循环控制指令中用到。如果按常规的写法,两个循环控制指令中的代码会完全一样,为了不让代码冗余,可以将这段代码提炼出来,放到一个宏中,这样在每-个循环控制指令中就都可以通过调用宏来实现。这里的宏相当于 Python 语言中的函数。 Jimja2模板中的宏要放到{%…%}中,使用 macro 修饰,支持参数,并且使用{%endmacro%}结束宏。下面是一个定义宏的例子。 {% macro myMacro(item)%} {{item.id}}-{{item.name}} {%endmacro %} 调用宏和调用 Pyhon 函数类似,下面的代码调用了两次 myMacro 宏。

{% for item in items1 %} {{ myMacro(item)}} {% endfor %}

{% for item in items2 %} {{ myMacro (item)}} {% endfor %} 其实宏调用就是在调用处插入宏的代码,并用传入的参数值替换宏参数。上面代码调用myMacro 宏后,相当于下面的代码。

image.png

image.png

image.png

21.2.7 include 指令

有时在一个模板中引用另外一个模板的目的并不是调用里面定义的宏,而是直接将整个模板的代码导入当前模板,在这种情况下,就需要使用 include 指令。加上有一个模板文件 item.txt,在当前模板中引入这个模板文件的代码如下: {% include "item.txt' %}

image.png

image.png

21.2.8 模板继承

jinja2模板还有另外一种代码重用技术,那就是模板继承。与Python 语言的类一样,当一个模板从另外模板继承后,就可以通过{{super()}}访问父模板的资源。在一个模板中维承另外一个模板,需要使用 extends 指令。例如,child.txt 模板文件从 parent.txt 继承的代码如下: {% extends 'parent.txt’%} child.bxt 模板从 parent.txt 模板继承后,会自动使用 parent.txt 中的所有代码,但放在{% block xxx……%}...{% endblock %}中的代码需要 child.txt 中使用{{super()}}引用。其中,xxxx 是块(block)的名字【例21.14】本例创建了两个模板文件:child.txt 和 parent.txt,其中,child.txt 模板文件从 parent.txt模板文件继承,并且使用了parent.txt模板文件中的一些资源。通过render_template 函数会向 child.txt模板文件传入一个 text 参数,然后在 child.txt 模板文件中会组合 parent.txt 模板文件中的资源和 text 参数值作为页面的 Title。

image.png

image.png

21.2.9 使用 flask-bootstrap 模块集成 Twitter Bootstrap

利用 Jinja2 模板的继承可以使用很多第三方的模板。例如,本节介绍的 flask-bootstrap 就是其中之一。flask-bootstrap 是一个第三方的模块,需要使用下面的命令安装:

pip install flask-bootstrap

flask-bootstrap 模板可以将 Bootstrap 框架集成进自己的 Web 应用。Bootstrap 是 Twitter 推出的一个开源的 Web 前端框架,主要用于制作 Web 页面。对于 web 前端页面来说,主要就是 CSS,所以Bootstrap 框架的核心就是提供了一大堆现成的 CSS。 使用 flask-bootsurap 模板时,除了要在 Python 代码中导入 flask-bootstrap 模板外,还要在模板中使用下面的代码从 bootstrap 模板继承。

{% extends "bootstrap/base.html" %}

image.png

image.png

21.2.10 自定义错误页面

image.png

image.png

21.3 Web 表单与 Flask-WTF 扩展

Flask-WTF 扩展是 Flask 的一个模块,用于处理 Web 表单。Web 表单用于通过 HTTPGET 或HTTP POST请求向服务端提交数据。Flask-WTF 扩展的主要功能如下; 口生成表单组件的 HTML 代码。

21.3.1 表单类

image.png

image.png

image.png

21.3.2 简单的表单组件

image.png

image.png

21.3.3 单选和多选组件

image.png

21.3.4 表单校验器

image.png

image.png

image.png

image.png

21.3.5 获取和设置表单组件中的数据

image.png

image.png

第22章 Python Web框架:Django

image.png

22.2 Django 基础知识

image.png 本节会介绍一些 Django 的基础知识,包括如何手工建立一个 Django 工程,如何使用 PyCharm 开发 Django 程序,以及获取用户请求信息,Cookie、Session 等内容。

22.2.1 建立第一个 Django 工程

本节将遵循学习新技术的标准做法,从 Hello world 开始学习 Django。为了让读者更有信心,本节首先要做的是让第一个 Django 程序能运行起来,而且不需要编写一行代码。 如果成功安装了 Diango,会有一个名为 django-admin.py 的脚本文件,如果使用的是 Anaconda:Python 开发环境,那么这个脚本文件就在<Anaconda 安装目录>/bin 目录中,建议将这个目录添加到PATH 环境变量中,这样在任何目录都可以执行 diango-admin.py 脚本文件。

现在进入终端(Windows是控制台)输入如下的命令,会在当前目录建立一个 HelloWorld 子目录该目录就是 Django 工程目录。 django-admin.py startproject HelloWorld 现在进入 HelloWorld 目录,然后执行下面的命令运行程序: python manage.py runserver 运行这行命令后,如果出现如图 22-1所示的信息,表示某些资源未被初始化现在按 Ctrl+C 键终止程序,然后执行下面的命令进行初始化: python manage.py migrate

image.png

然后再执行 python manage.py runserver,就会正常运行程序,这个程序其实是 Django 内建的 Web服务器,直接可以处理 HTTP 请求。现在打开浏览器,在浏览器地址栏中输入如下的 URL: http://127.0.0.1:8000 如果在浏览器中显示如图 22-3所示的内容,表示已经成功创建并运行了第一个基于 Django 的 Web应用。

访问 http://ocalhost:8000,也可以得到图22-3 所示的效果。但通过远程访问的方式却显示“无法问此网站”。例如,假设本机的P 地址是 192.168.31.3,访问 http:/192.168.31.3:8000 是无法得到22-3 所示的页面的。要知道为什么会出现这个问题,以及如何解决这个问题,请参看下一节的内容。

image.png

image.png

22.2.2 Django工程结构分析

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

22.2.9 读写 Session

Sesion 与Cookie 有些类似,都是通过字典管理 key-value 对。只不过 Cookie 是保存在客户端的字典,而Session是保存在服务端的字典。Session可以在服务端使用多种存在方式,默认一般存储在内存中,一旦Web服务重启,所有保存在内存中的 Sesnion就会消失,为了让 sesion 即使在 Web服务重启后仍然能够存在,也可以将 Session 保存到文件或数据库中。不管如何保存 Session,操作上都是一样的。

Session 的另外一个重要作用是跟踪客户端。也就是说,当一个客户端浏览器访问 Web 服务后,关闭浏览器,再次启动浏览器,再次访问 web 服务。这时 Web 服务就会知道这个浏览器已经访问两次 Web 服务。这就是通过 Session 跟踪的。每一个客户端访问 Web 服务时都会创建一个单独的Session,同时为这个 Session生成一个ID,这里就叫它 Session-ID。这个 Session-ID 会利用 Cookie的方式保存在客户端,如果客户端再次访问 web 服务时,这个 Session-ID 也会随着 HTTP 请求发送结Web 服务,Web 服务会通过这个 Session-ID 寻找属于这个客户端的 Session。也就是说,如果客户端不支持 Cookie,那么 Session 是无法跟踪客户端的。当然,也可以用其他方式保存这个 Session-ID,但这个不在本章的讨论范围,这里只讨论 Session 和 Cookie 的关系。

读写 Session 都需要使用路由函数的request 参数,WSGIRequest 对象有一个 session 属性,这是一个字典类型的属性,所以可以用操作字典的方式读写 Session 中的 key-value。

image.png

22.2.10 用户登录

image.png 本节会利用 Session 实现一个用户登录的例子,这也是最典型的 Session 案例。实现的基本原理是当登录成功后,会将用户名以及其他相关信息写入 Session。如果用户再用同一个浏览器访问该 Web应用,就会从与客户端对应的 Session 中重新获取用户名和其他相关信息,这也表明用户处于登录状态,所以当用户第二次访问该 Web 应用时,除非 Session 过期,否则就无须登录了。

image.png

image.png

image.png

22.3 Django模版

image.png

image.png

image.png

image.png

image.png

image.png

image.png

第四篇 Python科学计算与数据分析

第23章 科学计算库:NumPy

23.3.2 获取数组值和数组的分片

NumPy 数组也指出与 python 列表相同的操作,例如,通过索引获得数组值,分片等。

image.png

image.png

第24章 数据可视化库:Matplolib

image.png

24.1 Matplotlib 开发环境搭建

如果使用的是 Anaconda Python 开发环境,那么 Matplotlib 已经被集成进 Anaconda,并不需要单独安装。如果使用的是标准的Python 开发环境,可以使用下面的命令安装 Matplotlib: pip install matplotlib 如果要了解 Matplotlib 更详细的情况,请访问官方网站。网址如下: matplotlib.org 安装完 Matplotlib 后,可以测试一下 Matplotlib 是否安装成功。可以进入 Python 的 REPL 环境然后使用下面的语句导入 matplotlib.pyplot 模块,如果不出错,就说明 Matplotlib 已经安装成功了。import matplotlib.pyplot

第25章 数据分析库:Pandas

image.png

image.png

第五篇 Python Web爬虫技术

第26章 网络爬虫与Beautiful Soup

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

第27章 网络爬虫框架:Scrapy

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png image.png

image.png

image.png

image.png

第六篇 Python项目实战

第28章 Web项目实战:基于Flask的美团网

image.png

image.png

image.png

image.png

第29章 Web项目实战:基于Django的58同城

第30章 Web项目实战:天气预报服务API

第31章 Web项目实战:胸罩销售数据分析

第32章 GUI项目实战:动态修改域名指向的IP

image.png

image.png

image.png

image.png

image.png

第33章 游戏项目实战:俄罗斯方块

网络爬虫

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png image.png