Python基础知识入门PartII

本教程详细介绍了Python的基础知识,包括常用模块如sys、文件与目录操作、数据存储(XML、JSON、SQLite、MySQL、MongoDB)、TCP/UDP编程、网络高级技术(HTTP、FTP、邮件发送、邮件接收)以及多线程和GUI库tkinter的使用。通过学习,读者将掌握Python进行系统交互、数据管理和网络通信的基本技能。
摘要由CSDN通过智能技术生成

本文档整理自李宁老师发布在B站的视频,主要介绍python相关的基础入门知识,相关代码和本文档(md格式)提取码:pyth

Part I 1-11章

12-18章

12 常用模块

12.1 常用模块:sys

import sys
# 将模块所在的路径添加到sys.path列表中
sys.path.append('./test')
print(sys.path)
import my
my.greet('Bill')

print(sys.modules['my'])
print(type(sys.modules['my']))

print(sys.platform)

# 命令行参数
# 使用cmd切换到当前文件的路径,输入:python demo12.1.py abc,'abc'就是传入的命令行参数
# 或者在Pycharm中调出下方的Terminal,输入python demo12.1.py abc
# 输出当前脚本文件的完整路径
print(sys.argv[0])
if len(sys.argv) == 2:
    print(sys.argv[1])
    my.greet(sys.argv[1])
print('------------------------')

# 标准的输入输出流
s = sys.stdin.read(6)  # 只读取输入的前6个字符
print(s)  # 输出读取的字符
sys.stdout.writelines('hello world')
sys.stderr.writelines('error')  # 用红色标记错误输出,而且该行输出的位置是不确定的,可以多运行几次程序观察结果

# 当本脚本被调用时,会输出123,可以运行invoke.py查看运行结果
sys.exit(123)
# 下面是从pycharm控制台运行demo12.1.py的结果
PS D:\Projects\Python\test\demo12_Modules> python demo12.1.py abc
['D:\\Projects\\Python\\test\\demo12_Modules','test']  # 该列表还包含其他路径,这里不作展示,新添加的路径在列表的末尾,即'test'
hello Bill
<module 'my' from 'D:\\Projects\\Python\\test\\demo12_Modules\\./test\\my.py'>
<class 'module'>
win32
demo12.1.py
abc
hello abc
------------------------
请输入一段长度大于6的字符串: asigyhas
asigyh
hello worlderror

invoke.py文件中的代码

import subprocess
output = subprocess.getstatusoutput('python demo12.1.py Harry')
import pprint
pp = pprint.PrettyPrinter(indent=1)
pp.pprint(output)
# 先输出123,再输出demo12.1.py的调用结果
print(output[0])

12.2 获取和改变工作目录

import os
print(f"当前工作目录:{os.getcwd()}")
# 获取当前工作目录中的文件名和目录名
print(os.listdir(os.getcwd()))
os.chdir('../')
print(f"改变后的工作目录:{os.getcwd()}")
print(os.listdir(os.getcwd()))
当前工作目录:D:\Projects\Java\py2Md\target
['bill', 'classes', 'generated-sources', 'lib', 'maven-archiver', 'maven-status', 'Py2Md-1.0-SNAPSHOT.jar', 'Py2Md.exe', 'yourdir']
改变后的工作目录:D:\Projects\Java\py2Md
['.git', '.gitignore', '.idea', 'pom.xml', 'README.md', 'src', 'target']

12.3 文件与目录操作

'''
1、mkdir(dirname, permissions)
    r w x
    如果dirname存在,会抛出OSError
2、makedirs(dirname, permissions, exist_ok)
    (1)创建多级目录 mkdir('a') mkdir('a/b')(如果上级目录a不存在,下级目录b也不会创建)
        makedirs('a/b/c') 可以连续创建这三个目录
    (2) exist_ok == False,如果目录存在,会抛出OSError
        exist_ok == True,如果目录存在,不会进行任何操作
3、rmdir(dirname):删除目录
    rmdir('a') 如果目录a不为空,会抛出OSError
4、removedirs(dirname):删除目录,可以指定多级目录
    removedirs('a/b/c') 如果几层目录均为空,会全部删除
5、remove(filename)删除filename指定的文件
6、rename(src, dst) 将src参数指定的文件改名为dst指定的文件名
7、renames(src, dst)
    a/b/c --> x/y/z  可以同时修改多层目录的名称
'''
# 建议跟着视频一步一步运行该文件
import os
if not os.path.exists('newdir1'):
    os.mkdir('newdir1')
os.makedirs('x/y/z', 0o733, True)
try:
    os.rmdir('newdir1')
except OSError as e:
    print(e)
# os.removedirs('x/y/z')

if not os.path.exists('mydir'):
    os.mkdir('mydir')
    os.rename('mydir', 'yourdir')
if os.path.exists('x/y/z'):
    os.renames('x/y/z', 'bill/mike/john')
if os.path.exists('newdir1/a.txt'):
    os.remove('newdir1/a.txt')

12.4 软链接与硬链接

'''
软链接(符号链接):相当于Windows的快捷方式
硬链接:是文件的副本
'''
import os
# windows系统要实现软链接需要开启特权SeCreateSymbolicLinkPrivilege
# if os.path.exists('data.txt') and not os.path.exists('slink_data.txt'):
#     # 建立软链接文件
#     os.symlink('data.txt', 'slink_data.txt')
if os.path.exists('data.txt') and not os.path.exists('link_data.txt'):
    # 建立硬链接文件
    os.link('data.txt', 'link_data.txt')

12.5 用于获取和设置系统信息的函数和变量

'''
1、sep变量:返回当前OS的路径分隔符
2、pathsep变量:返回环境变量中的路径之间的分隔符
3、name变量:返回当前OS的名称
4、environ变量:以字典的形式返回系统中所有环境变量的值
5、getenv函数:获取指定的环境变量的值,通过参数可以指定环境变量名
6、putenv函数:设置指定环境变量的值,通过参数指定环境变量名和环境变量值
7、system函数:执行命令,通过参数指定要执行的命令
'''
import os
import subprocess
print('路径分隔符', os.sep)
print('环境变量路径之间的分隔符', os.pathsep)
print('操作系统名', os.name)
print(os.environ)
print('PATH=', os.environ['PATH'])
print('PATH=', os.getenv('PATH'))
路径分隔符 \
环境变量路径之间的分隔符 ;

12.6 集合

'''
集合满足:
1、无序性:集合中各个元素的值是平等的
2、互异性:集合中任意两个元素都是不同的,每个元素只能出现一次
3、确定性:集合中每个元素都是确定的,对于某个值来说,要么属于该集合,要么不属于该集合。
列表、字典都不能作为集合的元素值,因为它们都是可变的
'''
set1 = set(range(10))
print(set1)

set2 = set('helloabc')  # 自动去除重复字符
print(set2)

set3 = set(['Bill', 'John', 'Mike', 'John'])
print(set3)
print('--------------------------')
# 求a和b的并集
a = set((1, 2, 3))
b = set([3, 5, 1, 6])
print(a.union(b))
print(a | b)
# 求a和b的交集
print(a.intersection(b))
print(a & b)

c = set([2, 3])
print(c.issubset(a))
print(a.issubset(c))
print(a.issuperset(c))

d = set([1, 2, 3])
print(a == d)

# 计算集合的差
print(a.difference(b))
print(a - b)

# 计算集合的对称差:只在其中一个集合出现的元素组成的集合
print(a.symmetric_difference(b))
print(a ^ b)
print((a - b) | (b - a))

x = a.copy()
y = a
print(y is a)
print(x is a)

print(1 in a)
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{'h', 'c', 'a', 'l', 'o', 'b', 'e'}
{'Mike', 'John', 'Bill'}
--------------------------
{1, 2, 3, 5, 6}
{1, 2, 3, 5, 6}
{1, 3}
{1, 3}
True
False
True
True
{2}
{2}
{2, 5, 6}
{2, 5, 6}
{2, 5, 6}
True
False
True

12.7 将集合作为集合的元素

'''
集合的元素和字典中的key都不允许是可变的对象,如集合、列表和字典
元组既可以做集合的元素,也可以做字典的key
'''
aSet = set([1, 2])
bSet = set([10, 20])
aSet.add(4)
print(aSet)
# a.add(b)  # 直接将集合作为元素添加到另一个集合,会报错
aSet.add(frozenset(bSet))  # frozenset()只读取集合
print(aSet)

dict = {'Bill': 30, 'Mike': 34}
# d[a] = 10  # 不能直接将集合作为key输入字典中
dict[frozenset(aSet)] = 10
print(dict)

# bSet.add(dict)  # 集合不能添加可变对象,如字典

t = (1,2,3,4)
bSet.add(t)
print(bSet)
{1, 2, 4}
{1, 2, 4, frozenset({10, 20})}
{'Bill': 30, 'Mike': 34, frozenset({1, 2, 4, frozenset({10, 20})}): 10}
{(1, 2, 3, 4), 10, 20}

12.8 堆Heap

from heapq import *
from random import *

data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
heap = []
for n in data:
    value = choice(data)  # 从data中随机选取一个元素
    # 将值添加到堆
    heappush(heap, value)
print(heap)
heappush(heap, 30)
print(heap)
print(heappop(heap))  # 弹出最小值

data1 = [6,3,1,12,9]
heapify(data1)
print(data1)
print(heapreplace(data1, 123))  # 弹出最小值,并替换为123
print(data1)

print(nlargest(2, data1))  # 返回data1中的最大的n个值
print(nsmallest(3,data1))

print(list(merge([1,3,456,7],[0,1,-5,6],[1,7,4],[],[67])))

print(list(merge(['dog','horse'],['cat','fish','kangaroo'],key=len)))
[1, 2, 2, 4, 5, 5, 5, 9, 6]
[1, 2, 2, 4, 5, 5, 5, 9, 6, 30]
1
[1, 3, 6, 12, 9]
1
[3, 9, 6, 12, 123]
[123, 12]
[3, 6, 9]
[0, 1, 1, -5, 1, 3, 6, 7, 4, 67, 456, 7]
['dog', 'cat', 'fish', 'horse', 'kangaroo']

12.9 双端队列

from collections import deque
q = deque(range(10))
print(q)
# 在两端添加元素
q.append(123)
q.append(-32)
print(q)
q.appendleft(30)
print(q)
# 在两端弹出元素
print(q.pop())
print(q)
print(q.popleft())
print(q)
# 将q向左循环移动2个位置
q.rotate(-2)
print(q)
q.rotate(4)
print(q)

q1 = deque(['a','b'])
q.extend(q1)
print(q)
q.extendleft(q1)
print(q)
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 123, -32])
deque([30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 123, -32])
-32
deque([30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 123])
30
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 123])
deque([2, 3, 4, 5, 6, 7, 8, 9, 123, 0, 1])
deque([9, 123, 0, 1, 2, 3, 4, 5, 6, 7, 8])
deque([9, 123, 0, 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b'])
deque(['b', 'a', 9, 123, 0, 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b'])

12.10 时间元组

import time
localtime = time.localtime(time.time())
print(f"当前时间是:{localtime}")
print(type(localtime))  # localtime是一个对象
st = time.struct_time((1,2,3,4,5,6,7,8,9))  # 参数需要是含有9个元素的元组
print(st)

print(f"年 = {localtime.tm_year}")
print(f"月 = {localtime.tm_mon}")
print(f"日 = {localtime.tm_mday}")
print(f"一年的第{localtime[7]}天")

localtime = time.asctime(localtime)
print(localtime)
当前时间是:time.struct_time(tm_year=2021, tm_mon=11, tm_mday=10, tm_hour=21, tm_min=27, tm_sec=52, tm_wday=2, tm_yday=314, tm_isdst=0)
<class 'time.struct_time'>
time.struct_time(tm_year=1, tm_mon=2, tm_mday=3, tm_hour=4, tm_min=5, tm_sec=6, tm_wday=7, tm_yday=8, tm_isdst=9)= 2021= 11= 10
一年的第314天
Wed Nov 10 21:27:52 2021

12.11 格式化日期和时间

'''
strftime: 参数1:格式化字符串  参数2:时间元祖
'''
import time
import locale
locale.setlocale(locale.LC_ALL, 'zh_CN.UTF-8')

print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
print(time.strftime('%Y年%m月%d日 %H时%M分%S秒', time.localtime()))

# 输出星期的完整名称
print(time.strftime("今天是%A",time.localtime()))
2021-11-10 21:27:52
20211110212752秒
今天是星期三

12.12 时间戳的增量

import time
time1 = time.time()
time2 = time1 + 60
print(time2)
time3 = time1 + 60*60
time3 = time.localtime(time3)
print(time3)
print(time.strftime('%Y-%m-%d %H:%M:%S', time3))
1636550932.8739896
time.struct_time(tm_year=2021, tm_mon=11, tm_mday=10, tm_hour=22, tm_min=27, tm_sec=52, tm_wday=2, tm_yday=314, tm_isdst=0)
2021-11-10 22:27:52

12.13 计算日期和时间的差

import datetime
d1 = datetime.datetime(2017,4,12)
d2 = datetime.datetime(2018,12,25)
print((d2-d1).days)

d1 = datetime.datetime(2017,4,12,10,10,10)
d2 = datetime.datetime(2018,12,25,10,10,40)
print(d2-d1)
print((d2-d1).seconds)

d1 = datetime.datetime.now()
d2 = d1 + datetime.timedelta(hours = 10)
print(d2)
d2 = d1 + datetime.timedelta(hours = -10)
print(d2)
622
622 days, 0:00:30
30
2021-11-11 07:27:52.988998
2021-11-10 11:27:52.988998

12.14 获取某月和某年的日历

import calendar
import locale
cal = calendar.month(2021,1)
print(cal)

print(calendar.calendar(2021))
    January 2021
Mo Tu We Th Fr Sa Su
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

                                  2021

      January                   February                   March
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
             1  2  3       1  2  3  4  5  6  7       1  2  3  4  5  6  7
 4  5  6  7  8  9 10       8  9 10 11 12 13 14       8  9 10 11 12 13 14
11 12 13 14 15 16 17      15 16 17 18 19 20 21      15 16 17 18 19 20 21
18 19 20 21 22 23 24      22 23 24 25 26 27 28      22 23 24 25 26 27 28
25 26 27 28 29 30 31                                29 30 31

       April                      May                       June
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
          1  2  3  4                      1  2          1  2  3  4  5  6
 5  6  7  8  9 10 11       3  4  5  6  7  8  9       7  8  9 10 11 12 13
12 13 14 15 16 17 18      10 11 12 13 14 15 16      14 15 16 17 18 19 20
19 20 21 22 23 24 25      17 18 19 20 21 22 23      21 22 23 24 25 26 27
26 27 28 29 30            24 25 26 27 28 29 30      28 29 30
                          31

        July                     August                  September
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
          1  2  3  4                         1             1  2  3  4  5
 5  6  7  8  9 10 11       2  3  4  5  6  7  8       6  7  8  9 10 11 12
12 13 14 15 16 17 18       9 10 11 12 13 14 15      13 14 15 16 17 18 19
19 20 21 22 23 24 25      16 17 18 19 20 21 22      20 21 22 23 24 25 26
26 27 28 29 30 31         23 24 25 26 27 28 29      27 28 29 30
                          30 31

      October                   November                  December
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
             1  2  3       1  2  3  4  5  6  7             1  2  3  4  5
 4  5  6  7  8  9 10       8  9 10 11 12 13 14       6  7  8  9 10 11 12
11 12 13 14 15 16 17      15 16 17 18 19 20 21      13 14 15 16 17 18 19
18 19 20 21 22 23 24      22 23 24 25 26 27 28      20 21 22 23 24 25 26
25 26 27 28 29 30 31      29 30                     27 28 29 30 31

12.15 随机数

'''
randint(m,n):用于产生m到n之间的随机整数,包括m和n
random():用于产生0到1之间的随机浮点数,包括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
print(random.randint(1,100))
print(random.random())
print(random.randrange(1,20,3))  # 从[1,4,7,10,13,16,19]中选取
print(random.uniform(1,100.05))

intList = [1,2,5,7,39,40]
print(random.choice(intList))

newList = random.sample(intList, 3)
print(newList)

random.shuffle(intList)
print(intList)
58
0.44714554718797783
10
81.96900990017868
5
[40, 39, 2]
[40, 7, 39, 1, 2, 5]

12.16 数学函数

import math
print('圆周率 =',math.pi)
print('自然常数 =', math.e)

print(math.fabs(-1.2))
# 向上取整
print(math.ceil(1.3))
# 向下取整
print(math.floor(1.8))

print(math.pow(2, 10))

print(math.sqrt(8))

print(math.sin(math.pi/2))
print(math.cos(math.pi))
print(math.tan(math.pi/4))
圆周率 = 3.141592653589793
自然常数 = 2.718281828459045
1.2
2
1
1024.0
2.8284271247461903
1.0
-1.0
0.9999999999999999

13 文件和流

13.1 文件和流

'''
1、读文件和写文件
2、管道输出
3、读行和写行
4、使用FileInput对象读取文件
'''
'''
r:读  w:写
r+:文件可读写,如果文件不存在,会抛出异常。如果文件存在,会从当前位置开始写入新内容
    通过seek函数可以改变当前的位置,即改变文件的指针
w+:文件可读写,如果文件不存在,会创建一个新文件,如果文件存在,会清空原文件,并写入新内容
a+:文件可读写,如果文件不存在,会创建一个新文件,如果文件存在,会将新内容添加到原文件的最后
    使用a+打开文件,文件指针会在文本的最后
'''
'''
write(string):向文件写入内容,会返回写入文件的字节数
read(n):读取文件内容,n是一个整数,表示从文件指针指定位置开始读取的n个字节
    不指定n,会读取从当前位置往后的所有字节。该函数会返回读取的内容
seek(n):重新设置文件指针,也就是改变文件的当前位置。如果使用write函数写入内容后,
    需要使用seek(0)重置文件指针
close():关闭文件,对文件进行读写操作后,关闭文件是一个好习惯
'''

f = open('./files/test1.txt','w')
print(f.write('I love '))
print(f.write('python'))
f.close()

f = open('./files/test1.txt', 'r')
print(f.read(7))
print(f.read(6))
f.close()

try:
    f = open('./files/test2.txt', 'r+')  # 尝试打开不存在的文件
except Exception as e:
    print(e)

f = open('./files/test2.txt', 'a+')
print(f.write('hello'))
f.close()

print('-------------1------------')
f = open('./files/test2.txt', 'a+')
print(f.read())
f.seek(0)
print('-------------2-------------')
print(f.read())
f.close()

try:
    f = open('./files/test2.txt', 'w+')
    print(f.read())
    f.write('How are you?')
    f.seek(0)
    print(f.read())
finally:
    f.close()
7
6
I love 
python
[Errno 2] No such file or directory: './files/test2.txt'
5
-------------1------------

-------------2-------------
hello

How are you?

13.2 读行和写行

f = open('./files/urls.txt', 'r+')
url = ''
while True:
    url = f.readline()
    url = url.rstrip()  # 去掉每一行末尾的换行符
    if url == '':
        break
    else:
        print(url)
print('---------------------------')
f.seek(0)

# windows系统下,调用os.linesep会添加两个换行符
print(f.readlines())
f.write('http://baidu.com'+'\n')
f.close()

f = open('./files/urls.txt', 'a+')
urlList = ['https://google.com'+'\n','https://jd.com']
f.writelines(urlList)
f.close()
https://geekori.com
https://geekori.com/que.php
http://ningedu.com
---------------------------
['https://geekori.com\n', 'https://geekori.com/que.php\n', 'http://ningedu.com\n']

13.3 使用FileInput对象读取文件

import fileinput
fileobj = fileinput.input('./files/urls.txt')
print(type(fileobj))

print(fileobj.readline().rstrip())

for line in fileobj:
    line = line.rstrip()
    if line != '':
        print(fileobj.lineno(), ':', line)  # 输出行号
    else:
        print(fileobj.filename())
<class 'fileinput.FileInput'>
https://geekori.com
2 : https://geekori.com/que.php
3 : http://ningedu.com

13.4 练习题I

'''
编写一个程序,从控制台输入一个奇数
然后生成奇数行的星号(*)菱形,并将该菱形保存到当前目录下的stars.txt文件
'''
line = input('请输入行数(必须是奇数):')
line = int(line)  # 这里的行数同时也是星号最多的一行中星号的个数

# 在当前目录下的stars.txt文件中查看输出结果
if line % 2 != 0:
    f = open('stars.txt', 'w')
    spaceNum = line // 2  # 第一行的星号左侧或右侧的空格个数
    i = 1
    lineSpaceNum = spaceNum
    # 生成上三角形
    while lineSpaceNum >= 0:
        f.write(' ' * lineSpaceNum)
        f.write('*' * (2*i-1))
        f.write(' ' * lineSpaceNum + '\n')
        lineSpaceNum -= 1
        i += 1
    i -= 2
    lineSpaceNum += 2
    # 生成下三角形
    while lineSpaceNum <= spaceNum:
        f.write(' ' * lineSpaceNum)
        f.write('*' * (2 * i - 1))
        f.write(' ' * lineSpaceNum + '\n')
        lineSpaceNum += 1
        i -= 1
    f.close()
else:
    print('必须输入奇数!')
请输入行数(必须是奇数):11
# 在star.txt中可查看
     *     
    ***    
   *****   
  *******  
 ********* 
***********
 ********* 
  *******  
   *****   
    ***    
     *     

13.5 练习题II

'''
编写程序,从当前目录的文本文件words.txt中读取所有内容(全都是英文单词),并统计其中每个英文单词出现的次数。
    单词之间用逗号、分号或空格分隔,也可能是这3个分隔符一起分隔单词。将统计结果保存到字典中,并输出统计结果。
'''
import re
f = open('words.txt', 'r')
words = f.read()
wordList = re.split('[ ,;]+', words)
print(wordList)
countDict = {}
for word in wordList:
    if countDict.get(word) == None:
        countDict[word] = 1
    else:
        countDict[word] += 1
for (key, value) in countDict.items():
    print(key, '=', value)
f.close()
['test', 'star', 'test', 'star', 'star', 'bus', 'test', 'bill', 'new', 'yeah', 'bill', 'book', 'bike', 'God', 'start', 'python', 'what']
test = 3
star = 3
bus = 1
bill = 2
new = 1
yeah = 1
book = 1
bike = 1
God = 1
start = 1
python = 1
what = 1

14 数据存储

14.1 数据存储

'''
1. XML
2. JSON
3. SQLite
4. MySQL
5. ORM(SQLAlchemy SQLObject)
6. 非关系型数据库(NoSQL) MongoDB
'''
# 读取与检索XML文件
from xml.etree.ElementTree import parse
doc = parse('files/products.xml')
for item in doc.iterfind('products/product'):
    # 读取id、name、price节点的值
    id = item.findtext('id')
    name = item.findtext('name')
    price = item.findtext('price')

    uuid = item.get('uuid')
    print('uudi =', uuid)
    print('id =', id)
    print('name =', name)
    print('price =', price)
    print('---------------------')
uudi = 1234
id = 10000
name = iPhone9
price = 9999
---------------------
uudi = 4321
id = 20000
name = 特斯拉
price = 1000000
---------------------
uudi = 5678
id = 30000
name = Mac Pro
price = 40000
---------------------

14.2 将字典转换为XML字符串

# pip install dicttoxml
import os

import dicttoxml
from xml.dom.minidom import parseString
d = [20,'names',{'name':'Bill','age':30,'salary':20000},
                {'name':'李宁','age':123,'salary':30000},
                {'name':'John','age':29,'salary':8000}
     ]
# 将字典转换为XML格式(bytes形式)
bxml = dicttoxml.dicttoxml(d, custom_root = 'persons')
# print(bxml)  # python对中文字符进行了二进制编码
xml = bxml.decode('utf-8')
print(xml)

# 解析xml字符串
dom = parseString(xml)
prettyxml = dom.toprettyxml(indent = '    ')
print(prettyxml)

os.makedirs('files', exist_ok= True)
f = open('files/persons.xml', 'w', encoding= 'utf-8')
f.write(prettyxml)
f.close()
<?xml version="1.0" encoding="UTF-8" ?><persons><item type="int">20</item><item type="str">names</item><item type="dict"><name type="str">Bill</name><age type="int">30</age><salary type="int">20000</salary></item><item type="dict"><name type="str">李宁</name><age type="int">123</age><salary type="int">30000</salary></item><item type="dict"><name type="str">John</name><age type="int">29</age><salary type="int">8000</salary></item></persons>
<?xml version="1.0" ?>
<persons>
    <item type="int">20</item>
    <item type="str">names</item>
    <item type="dict">
        <name type="str">Bill</name>
        <age type="int">30</age>
        <salary type="int">20000</salary>
    </item>
    <item type="dict">
        <name type="str">李宁</name>
        <age type="int">123</age>
        <salary type="int">30000</salary>
    </item>
    <item type="dict">
        <name type="str">John</name>
        <age type="int">29</age>
        <salary type="int">8000</salary>
    </item>
</persons>

14.3 XML字符串转换为字典

import xmltodict
f = open('files/products.xml', 'rt', encoding='utf-8')
xml = f.read()
d = xmltodict.parse(xml)  # 转换为一个嵌套的字典
# print(d)
for v in d['root']['products']['product']:
    print('uuid :', v['@uuid'])
    print('id :', v['id'])  # 注意:id/name/price前不用加@
    print('name :', v['name'])
    print('price :', v['price'])
    print('---------------')
print(d['root']['products']['product'][0]['@uuid'])

import pprint
pp = pprint.PrettyPrinter(indent=1)
pp.pprint(d)
uuid : 1234
id : 10000
name : iPhone9
price : 9999
---------------
uuid : 4321
id : 20000
name : 特斯拉
price : 1000000
---------------
uuid : 5678
id : 30000
name : Mac Pro
price : 40000
---------------
1234
OrderedDict([('root',
              OrderedDict([('products',
                            OrderedDict([('product',
                                          [OrderedDict([('@uuid', '1234'),
                                                        ('id', '10000'),
                                                        ('name', 'iPhone9'),
                                                        ('price', '9999')]),
                                           OrderedDict([('@uuid', '4321'),
                                                        ('id', '20000'),
                                                        ('name', '特斯拉'),
                                                        ('price', '1000000')]),
                                           OrderedDict([('@uuid', '5678'),
                                                        ('id', '30000'),
                                                        ('name', 'Mac Pro'),
                                                        ('price',
                                                         '40000')])])]))]))])

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

'''
1、使用JSON模块的loads函数,该函数通过参数传入json字符串,然后返回与该字符串对应的字典
2、使用eval函数将json格式字符串当做普通的python代码执行,eval函数会直接返回与json格式字符串对应的字典
'''
import json
data = {
    'name':'Bill',
    'company':'Microsoft',
    'age':34
}
# 将字典转换为json字符串
jsonstr = json.dumps(data)
print(type(jsonstr))
print(jsonstr)

# 将json字符串转换为字典
data = json.loads(jsonstr)
print(type(data))
print(data)

# 定义一个json字符串,要注意缩进,然后使用eval函数转换为字典
s = '''
{
    'name':'Bill',
    'company':'Microsoft',
    'age':34
}    
'''
data = eval(s)  # 使用eval函数转换时,字符串可以使用单引号
print(type(data))
print(data)

f = open('files/products.json','r',encoding='utf-8')
jsonStr = f.read()
json1 = eval(jsonStr)
json2 = json.loads(jsonStr)
# json文件中字符串要使用双引号,使用单引号调用json.loads函数会报错
# json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes
print(json1)
print(json2)
<class 'str'>
{"name": "Bill", "company": "Microsoft", "age": 34}
<class 'dict'>
{'name': 'Bill', 'company': 'Microsoft', 'age': 34}
<class 'dict'>
{'name': 'Bill', 'company': 'Microsoft', 'age': 34}
[{'name': 'iPhone9', 'price': 9999, 'count': 2000}, {'name': '特斯拉', 'price': 1000000, 'count': 123}]
[{'name': 'iPhone9', 'price': 9999, 'count': 2000}, {'name': '特斯拉', 'price': 1000000, 'count': 123}]

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

'''
loads
loads函数的object_hook关键字指定一个类或一个回调函数
1、指定类:loads函数会自动创建指定的类的实例,并将有json字符串转换成的字典通过类的构造方法传入类实例
    指定的类必须有一个可以接受字典的构造方法
2、指定为回调函数:loads函数会调用回调函数返回类的实例,并将由json字符串转换为的字典传入回调函数
    回调函数必须有一个参数可以接受字典
'''
import json
class Product:
    def __init__(self, d):
        self.__dict__ = d

f = open('files/product.json', 'r')
jsonStr = f.read()

my1 = json.loads(jsonStr, object_hook=Product)
print(type(my1))
print('name = ',my1.name)
print('price = ',my1.price)
print('count = ',my1.count)
print('-----------------------')

# jsonStr -> Dict -> Product_object
def json2Product(d):
    return Product(d)
my2 = json.loads(jsonStr, object_hook=json2Product)
print(my2)
print('name = ',my2.name)
print('price = ',my2.price)
print('count = ',my2.count)
f.close()
<class '__main__.Product'>
name =  iPhone
price =  9999
count =  3000
-----------------------
<__main__.Product object at 0x00000238660EF0D0>
name =  iPhone
price =  9999
count =  3000

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

'''
dumps:将字典转换为JSON字符串
default关键字参数指定一个回调函数,该回调函数会接受一个类实例
回调函数需要返回一个字典,最后,dumps函数将这个字典转换为JSON字符串
object -> dict -> JSON
'''
import json
class Product:
    def __init__(self,name,price,count):
        self.name = name
        self.price = price
        self.count = count
def product2Dict(obj):
    return {
        'name':obj.name,
        'price':obj.price,
        'count':obj.count
    }
product = Product('特斯拉', 1000000, 20)
jsonStr = json.dumps(product, default= product2Dict, ensure_ascii=False)
print(jsonStr)
{"name": "特斯拉", "price": 1000000, "count": 20}

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

import json
class Product:
    def __init__(self,d):
        self.__dict__ = d
# JSON字符串转换为类实例
f = open('files/products.json','r',encoding='utf-8')
jsonStr = f.read()
products = json.loads(jsonStr, object_hook=Product)
print(type(products))
print(products)  # products是一个列表,其元素是两个Product类对象
for product in products:
    print('name =', product.name)
    print('price =',product.price)
    print('count =',product.count)
# 类实例对象转换为JSON字符串
def product2Dict(product):
    return {
        'name':product.name,
        'price':product.price,
        'count':product.count
    }
jsonStr = json.dumps(products, default=product2Dict,ensure_ascii=False)
print(jsonStr)

f.close()
<class 'list'>
[<__main__.Product object at 0x000001CC7307FE80>, <__main__.Product object at 0x000001CC7307F7C0>]
name = iPhone9
price = 9999
count = 2000
name = 特斯拉
price = 1000000
count = 123
[{"name": "iPhone9", "price": 9999, "count": 2000}, {"name": "特斯拉", "price": 1000000, "count": 123}]

14.8 将JSON字符串转换为XML字符串

# json -> dict -> xml
import json
import dicttoxml
f = open('files/products.json','r',encoding='utf-8')
jsonStr = f.read()
d = json.loads(jsonStr)
print(d)
xmlStr = dicttoxml.dicttoxml(d).decode('utf-8')
print(xmlStr)
f.close()
[{'name': 'iPhone9', 'price': 9999, 'count': 2000}, {'name': '特斯拉', 'price': 1000000, 'count': 123}]
<?xml version="1.0" encoding="UTF-8" ?><root><item type="dict"><name type="str">iPhone9</name><price type="int">9999</price><count type="int">2000</count></item><item type="dict"><name type="str">特斯拉</name><price type="int">1000000</price><count type="int">123</count></item></root>

14.9 用python操作SQLite数据库

'''
安装SQLite:https://sqlite.org/download.html
    安装教程参考:https://www.runoob.com/sqlite/sqlite-installation.html
下载DB Browser for SQLite:http://www.sqlitebrowser.org/?template=VERTICAL_LINES&tdfs=1&s_token=1636608497.0397460000&uuid=1636608497.0397460000&term=SQL%20Server%20Database%20Performance%20Monitoring%20Tools&term=ETL%20Tools%20Database%20Browser&term=SQL%20Database%20Programming&searchbox=0&showDomain=0&backfill=0
'''
import sqlite3
import os
dbPath = 'data.sqlite'
if not os.path.exists(dbPath):
    conn = sqlite3.connect(dbPath)
    c = conn.cursor()
    c.execute('''CREATE TABLE persons
                (id INT PRIMARY KEY NOT NULL,
                name TEXT NOT NULL,
                age INT NOT NULL,
                address CHAR(50),
                salary REAL);
                ''')
    conn.commit()  # 在当前目录下可查看新建的数据库
conn = sqlite3.connect(dbPath)
c = conn.cursor()
c.execute('delete from persons')
c.execute("insert into persons(id,name,age,address,salary)\
          values(1,'Bill',32,'California',20000.50)")
c.execute("insert into persons(id,name,age,address,salary)\
          values(2,'Mary',26,'Texas',1234)")
c.execute("insert into persons(id,name,age,address,salary)\
          values(3,'Harry',29,'Norway',5865)")
c.execute("insert into persons(id,name,age,address,salary)\
          values(4,'John',30,'Vegas',8353)")
conn.commit()
print('数据插入成功')
persons = c.execute('select name,age,address,salary from persons order by age')
print(type(persons))
result = []
for person in persons:
    value = {}
    value['name'] = person[0]
    value['age'] = person[1]
    value['address'] = person[2]
    result.append(value)
conn.close()
print(result)
数据插入成功
<class 'sqlite3.Cursor'>
[{'name': 'Mary', 'age': 26, 'address': 'Texas'}, {'name': 'Harry', 'age': 29, 'address': 'Norway'}, {'name': 'John', 'age': 30, 'address': 'Vegas'}, {'name': 'Bill', 'age': 32, 'address': 'California'}]

14.10 用python操作MySQL数据库

'''
pip install pymysql
conda install pymysql
Document for Python DB API v2.0:https://www.python.org/dev/peps/pep-0249/
MySQL:https://dev.mysql.com/downloads/mysql/
    安装教程:https://www.php.cn/mysql-tutorials-454993.html
'''
'''
connect函数:连接数据库,返回Connection对象
cursor方法:获取操作数据库的Cursor对象,属于Connection对象
execute方法:用于执行SQL语句,属于Cursor对象
commit方法:在修改数据库后,需要调用该方法提交对数据库的修改,属于Cursor对象
rollback方法:如果修改数据库失败,可以调用该方法进行数据库回滚
'''
from pymysql import *
import json
# 请确保MySQL服务已开启
def connectDB():
    Password = input('请输入MySQL账号密码:')
    db = connect(host='127.0.0.1',user='root',password=Password,database='test',charset='utf8')
    return db
db = connectDB()
# 创建persons表
def createTable(db):
    cursor = db.cursor()
    sql = '''CREATE TABLE persons
                (id INT PRIMARY KEY NOT NULL,
                name TEXT NOT NULL,
                age INT NOT NULL,
                address CHAR(50),
                salary REAL);'''
    try:
        cursor.execute(sql)
        db.commit()
        return True
    except:
        db.rollback()
    return False
# 向persons表插入4条记录
def insertRecords(db):
    c = db.cursor()
    try:
        c.execute('delete from persons')
        c.execute("insert into persons(id,name,age,address,salary)\
                  values(1,'Bill',32,'California',20000.50)")
        c.execute("insert into persons(id,name,age,address,salary)\
                  values(2,'Mary',26,'Texas',1234)")
        c.execute("insert into persons(id,name,age,address,salary)\
                  values(3,'Harry',29,'Norway',5865)")
        c.execute("insert into persons(id,name,age,address,salary)\
                  values(4,'John',30,'Vegas',8353)")
        db.commit()
        return True
    except:
        db.rollback()
    return False
def selectRecords(db):
    cursor = db.cursor()
    sql = 'select name,age,address,salary from persons order by age'
    cursor.execute(sql)
    results = cursor.fetchall()
    print(results)
    fields = {'name', 'age', 'salary'}
    records = []
    for row in results:
        records.append(dict(zip(fields,row)))
    return json.dumps(records)

if createTable(db):
    print('成功创建persons表')
else:
    print('persons表已经存在')
if insertRecords(db):
    print('成功插入记录')
else:
    print('插入记录失败')
print(selectRecords(db))
db.close()
请输入MySQL账号密码:12345678
persons表已经存在
成功插入记录
(('Mary', 26, 'Texas', 1234.0), ('Harry', 29, 'Norway', 5865.0), ('John', 30, 'Vegas', 8353.0), ('Bill', 32, 'California', 20000.5))
[{"age": "Mary", "name": 26, "salary": "Texas"}, {"age": "Harry", "name": 29, "salary": "Norway"}, {"age": "John", "name": 30, "salary": "Vegas"}, {"age": "Bill", "name": 32, "salary": "California"}]

14.11 ORM(SQLAlchemy)

'''
ORM:Object Relational Mapping抽象SQL,或者说将SQL语句直接映射成python对象
SQLAlchemy,SQLObject
'''
from sqlalchemy import create_engine,MetaData,Table,Column,Integer,String,Float,exc,orm
from sqlalchemy.ext.declarative import declarative_base
mysql = 'mysql+pymysql://root:12345678@localhost:3306/test?charset=utf8'
# 需要在本地先创建一个名为test的数据库(安装MySQL时可以同时安装workbench,使用workbench进行数据库操作)
tableName = 'persons1'
engine = create_engine(mysql,encoding='utf-8')
# 连接MySQL数据库
engine.connect()
metadata = MetaData(engine)
person = Table(tableName,metadata,
               Column('id',Integer,primary_key=True),
               Column('name',String(30)),
               Column('age',Integer),
               Column('address',String(100)),
               Column('salary',Float))
metadata.create_all(engine)
Base = declarative_base()
class Person(Base):
    __tablename__ = tableName
    id = Column(Integer,primary_key= True)
    name = Column(String(30))
    age = Column(Integer)
    address = Column(String(100))
    salary = Column(Float)
Session = orm.sessionmaker(bind = engine)
session = Session()
# 先删除persons1表中的所有记录
session.query(Person).delete()
session.commit()
# 插入新的记录
person1 = Person(id = 10,name='Bill',age=30,address='地球',salary=30000)
person2 = Person(id =20,name='Mike',age=34,address='火星',salary=8978)
person3 = Person(id=30, name='John',age=24,address='月球',salary=7865)
session.add(person1)
session.add(person2)
session.add(person3)
session.commit()
print('成功插入记录')
# 使用query进行条件查询
session.query(Person).filter(Person.name=='Mike').update({'address':'千星之城'})
query = session.query(Person).filter(Person.name=='John')
print(query)
person = query.scalar()  # 调用scalar函数,要确保query的查询结果只有一条,否则会报错
print(person.name)
# 修改数据库记录
person.age = 55
person.salary = 5646
session.commit()
print('成功更新记录')
# 使用组合条件查询persons1表中的记录
persons = session.query(Person).filter((Person.age>=10)&(Person.salary>=2000))
for person in persons:
    print('name =',person.name,end=' ')
    print('age =',person.age,end=' ')
    print()
print(persons.first().name)
print(persons.offset(2).scalar().name)  # offset(2)隐藏掉persons中的前两个对象,仅剩最后一个对象
session.delete(person2)
session.commit()
session.close()
成功插入记录
SELECT persons1.id AS persons1_id, persons1.name AS persons1_name, persons1.age AS persons1_age, persons1.address AS persons1_address, persons1.salary AS persons1_salary 
FROM persons1 
WHERE persons1.name = %(name_1)s
John
成功更新记录
name = Bill age = 30 
name = Mike age = 34 
name = John age = 55 
Bill
John

14.12 SQLObject

'''
使用SQLObject不需要调用commit就能对数据库进行操作
'''
from sqlobject import *
from sqlobject.mysql import builder
mysql = 'mysql://root:12345678@localhost:3306/test?charset=utf8'
sqlhub.processConnection = connectionForURI(mysql,driver='pymysql')

class Person(SQLObject):
    class sqlmeta:
        table = 'persons'
    name = StringCol(length = 30)
    age = IntCol()
    address = StringCol(length = 30)
    salary = FloatCol()
try:
    Person.dropTable()  # 删除persons表
except:
    pass
Person.createTable()  # 重新创建persons表
print('成功创建persons表')
person1 = Person(name='Bill',age=30,address='地球',salary=30000)
person2 = Person(name='Mike',age=34,address='火星',salary=8978)
person3 = Person(name='John',age=24,address='月球',salary=7865)

person2.name='李宁'
person2.address = '月球'
persons = Person.selectBy(name='Bill')
print(type(persons))
print(persons[0])
print(persons.count())
print(persons[0].id,persons[0].name,persons[0].address,persons[0].salary,end=' ')

def person2Dict(obj):
    return {
        'id':obj.id,
        'name':obj.name,
        'address':obj.address,
        'salary':obj.salary
    }
import json
jsonStr = json.dumps(persons[0],default=person2Dict,ensure_ascii=False)
print(jsonStr)
persons[0].destroySelf()
成功创建persons表
<class 'sqlobject.sresults.SelectResults'>
<Person 1 name='Bill' age=30 address='地球' salary=30000.0>
1
1 Bill 地球 30000.0 {"id": 1, "name": "Bill", "address": "地球", "salary": 30000.0}

14.13 非关系型数据库:MongoDB

'''
关系型数据库:SQLite、MySQL
    关系型数据库将数据保存到二维表中
NoSQL类型:对象数据库、键值数据库、文档数据库、图形数据库、表格数据库
MongoDB:文档数据库 https://www.mongodb.com/try/download/community
    MongoDB图形界面 https://www.mongodb.com/try/download/compass
'''
# 使用MongoDB可以将博文t_blogs与博文下的评论t_comments都保存到一个文档中
from pymongo import *
# 命令行窗口调用mongod打开MongoDB服务,然后连接MongoDB数据库
Client = MongoClient()
# 打开或创建名为hello的collection,collection相当于关系型数据库中的数据库
db = Client.hello
person1 = {'name':'Bill','age':27,"address":'地球','salary':8435}
person2 = {'name':'Mary','age':23,"address":'火星','salary':3446}
# 创建或打开一个名为persons的文档,persons相当于关系型数据库中的表
persons = db.persons
# $gt >   $lt <
persons.delete_many({'age':{'$gt':0}})
personId1 = persons.insert_one(person1).inserted_id
personId2 = persons.insert_one(person2).inserted_id
print(personId2)
'''
personList = [person1, person2]
result = persons.insert_many(personList)
print(result.inserted_ids)
'''
print(persons.find_one())  # 查找输入的第一个文档
print(persons.find_one()['name'])
# 搜索所有的数据
for person in persons.find():
    print(person)
print('---------------------------------')
# 更新数据
persons.update_one({'age':{'$lt':25}},{'$set':{'name':'超人'}})
for person in persons.find():
    print(person)
print('------------------------------')
# 删除数据
persons.delete_one({'age':{'$gt':25}})
for person in persons.find({'age':{'$lt':30}}):
    print(person)

print('总数 = ',persons.estimated_document_count())
618e2050fc9bc01d8090efb0
{'_id': ObjectId('618e2050fc9bc01d8090efaf'), 'name': 'Bill', 'age': 27, 'address': '地球', 'salary': 8435}
Bill
{'_id': ObjectId('618e2050fc9bc01d8090efaf'), 'name': 'Bill', 'age': 27, 'address': '地球', 'salary': 8435}
{'_id': ObjectId('618e2050fc9bc01d8090efb0'), 'name': 'Mary', 'age': 23, 'address': '火星', 'salary': 3446}
---------------------------------
{'_id': ObjectId('618e2050fc9bc01d8090efaf'), 'name': 'Bill', 'age': 27, 'address': '地球', 'salary': 8435}
{'_id': ObjectId('618e2050fc9bc01d8090efb0'), 'name': '超人', 'age': 23, 'address': '火星', 'salary': 3446}
------------------------------
{'_id': ObjectId('618e2050fc9bc01d8090efb0'), 'name': '超人', 'age': 23, 'address': '火星', 'salary': 3446}
总数 =  1

15 TCP与UDP编程

15.1 TCP与UDP编程

'''
TCP:传输控制协议,面向连接的,保证数据的可达性
UDP:数据报协议,无连接的,不保证数据一定可以到达另一端
'''
# 建立TCP服务端

from socket import *
host = ''
bufferSize = 1024
port = 9876
addr = (host, port)
# 1、创建Socket对象
# AF_INET: IPV4   AF_INET6:IPV6    SOCK_STREAM:TCP
tcpServerSocket = socket(AF_INET, SOCK_STREAM)
# 2、绑定一个端口号
tcpServerSocket.bind(addr)
# 3、监听端口号
tcpServerSocket.listen()
# 4、等待客户端Socket的连接
print('Server port:9876')
print('正在等待客户端连接')
tcpClientSocket, addr = tcpServerSocket.accept()
print('客户端已经连接','address =',addr)
# 5、读取从客户端发过来的数据
data = tcpClientSocket.recv(bufferSize)  # 最多读取bufferSize个字节的数据
print(data.decode('utf8'))
# 6、向客户端发送数据
tcpClientSocket.send('你好 I love you.\n'.encode(encoding='gbk'))  # windows系统的cmd窗口默认使用gbk编码
# 7、关闭客户端的Socket连接
tcpClientSocket.close()
# 8、关闭服务端的Socket连接
tcpServerSocket.close()

'''
windows系统需要配置Telnet客户端 https://jingyan.baidu.com/article/ca00d56c186d40e99febcf62.html
运行本程序后,打开cmd命令行窗口,输入:telnet localhost 9876
连接成功后,键盘输入:Ctrl + ]
再输入send+要传输的信息,如:send hello world
'''
Server port:9876
正在等待客户端连接
客户端已经连接 address = ('127.0.0.1', 54741)
hello world

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

'''
如果客户端发送给服务器的数据过多,需要分多次读取,每次最多读取缓冲区尺寸的数据,bufferSize
'''
from socket import *
host = ''
bufferSize = 2
port = 9876
addr = (host, port)
tcpServerSocket = socket(AF_INET, SOCK_STREAM)
tcpServerSocket.bind(addr)
tcpServerSocket.listen()
print('Server port:9876')
print('正在等待客户端连接')
tcpClientSocket, addr = tcpServerSocket.accept()
print('客户端已经连接','addr =',addr)
fullDataBytes = b''
while True:
    # 每次最多读取两个字节的数据
    data = tcpClientSocket.recv(bufferSize)
    print(data)  # 打印出来可以清晰看到每次只读取了两个字节
    fullDataBytes += data
    if len(data) < bufferSize:
        break
print(fullDataBytes)
print(fullDataBytes.decode('ISO-8859-1'))
tcpClientSocket.close()
tcpServerSocket.close()
Server port:9876
正在等待客户端连接
客户端已经连接 addr = ('127.0.0.1', 54773)
b'he'
b'll'
b'o '
b'wo'
b'rl'
b'd'
b'hello world'
hello world

15.3 服务端的请求队列

'''
请求队列根据操作系统的不同,存储客户端Socket是有上限的,Linux:128

'''
from socket import *
host = ''
bufferSize = 1024
port = 9876
addr = (host, port)
tcpServerSocket = socket(AF_INET, SOCK_STREAM)
tcpServerSocket.bind(addr)
tcpServerSocket.listen(2)
print('Server port:9876')
print('正在等待客户端连接')

while True:
    tcpClientSocket, addr = tcpServerSocket.accept()
    print('客户端已经连接', 'addr =', addr)
    data = tcpClientSocket.recv(bufferSize)
    print(data.decode('utf8'))
    tcpClientSocket.send('你好 I love you.\n'.encode(encoding='gbk'))
    tcpClientSocket.close()
tcpServerSocket.close()
Server port:9876
正在等待客户端连接
客户端已经连接 addr = ('127.0.0.1', 54821)
hello world1
客户端已经连接 addr = ('127.0.0.1', 54830)
hello world2
客户端已经连接 addr = ('127.0.0.1', 54856)
hello world3
客户端已经连接 addr = ('127.0.0.1', 54891)
hello world4

15.4 时间戳服务器

# 先运行本程序,再运行demo15.6.py
from socket import *
from time import ctime
host = ''
bufferSize = 1024
port = 1234
addr = (host, port)
tcpServerSocket = socket(AF_INET, SOCK_STREAM)
tcpServerSocket.bind(addr)
tcpServerSocket.listen()
while True:
    print('正在等待客户端连接')
    tcpClientSocket, addr = tcpServerSocket.accept()
    print('客户端已经连接', 'addr =', addr)
    while True:
        data = tcpClientSocket.recv(bufferSize)
        if not data:
            break
        tcpClientSocket.send(ctime().encode(encoding='utf-8')+ b' '+data)
    tcpClientSocket.close()
tcpServerSocket.close()
正在等待客户端连接
客户端已经连接 addr = ('127.0.0.1', 54938)

15.5 用Socket实现HTTP服务器

'''
HTTP请求头
HTTP响应头
Chrome浏览器,右键-检查,选择network,刷新页面,出现一个文件,点击,就能查看HTTP请求头与响应头
'''
# 运行本程序后,打开浏览器,输入localhost:8765/test.txt、localhost:8765/index.html、或随便的后缀地址localhost:8765/asklgyhag
# 还可以使用浏览器查看上述地址的HTTP请求头和响应头
from socket import *
import os
# 从文件中读取要返回的HTTP响应头文本,并将设置返回数据长度为length
def responseHeaders(file, length):
    f = open(file, 'r')
    headersText = f.read()
    headersText = headersText % length  # 文本中有占位符'%d',这里会由参数length进行替换
    return headersText
print(responseHeaders('response_headers.txt', 10))
# 根据HTTP请求头的路径得到服务端的本地路径
def filePath(get):
    if get == '/':
        return 'static'+os.sep+'index.html'
    else:
        paths = get.split('/')
        s = 'static'
        for path in paths:
            if path.strip() != '':
                s = s + os.sep + path
        return s
host = ''
bufferSize = 1024
port = 8765
addr = (host, port)
tcpServerSocker = socket(AF_INET, SOCK_STREAM)
tcpServerSocker.bind(addr)
tcpServerSocker.listen()
while True:
    print('正在等待客户端连接...')
    tcpClientSocket, addr = tcpServerSocker.accept()
    print('客户端已经连接','addr =',addr)
    data = tcpClientSocket.recv(bufferSize)
    data = data.decode('utf-8')
    try:
        # 获取HTTP请求头的第一行
        firstLine = data.split('\n')[0]
        # 获取请求路径
        path = firstLine.split(' ')[1]
        # 将Web路径转换为本地路径
        path = filePath(path)
        if os.path.exists(path):
            file = open(path, 'rb')
            content = file.read()
            file.close()
        else:
            content = '<h1>File Not Found</h1>'.encode(encoding='utf-8')
        rh = responseHeaders('response_headers.txt',len(content)) +'\r\n'
        tcpClientSocket.send(rh.encode(encoding='utf-8')+content)
    except Exception as e:
        print(e)
    tcpClientSocket.close()

tcpServerSocket.close()
HTTP/1.1 200 OK
Server:custom
Content-Type:text/html
Content-length:10

正在等待客户端连接...
客户端已经连接 addr = ('127.0.0.1', 55041)
正在等待客户端连接...

15.6 Socket客户端

# 先运行demo15.4.py,再运行本程序
from socket import *
host = 'localhost'
port = 1234
bufferSize = 1024
addr = (host, port)
tcpClientSocket = socket(AF_INET, SOCK_STREAM)
tcpClientSocket.connect(addr)
while True:
    data = input('>')
    if not data:
        break
    data = data.encode(encoding='utf-8')
    tcpClientSocket.send(data)
    data = tcpClientSocket.recv(bufferSize)
    print(data.decode('utf-8'))
tcpClientSocket.close()
>hello world
Sat Nov 13 15:28:22 2021 hello world
>there you are
Sat Nov 13 15:28:33 2021 there you are
>

15.7 UDP时间戳服务器

# 结合demo15.8.py
from socket import *
from time import ctime
host = ''
port = 9876
bufferSize = 1024
addr = (host, port)
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' ',addr)
    print('客户端地址:',addr)
udpServerSocket.close()
正在等待消息......
客户端地址: ('127.0.0.1', 61648)
正在等待消息......
客户端地址: ('127.0.0.1', 61648)
正在等待消息......

15.8 UDP时间戳客户端

from socket import *
host = 'localhost'
port = 9876
bufferSize = 1024
addr = (host, port)
udpClientSocket = socket(AF_INET, SOCK_DGRAM)
while True:
    data = input('>')
    if not data:
        break
    data = data.encode(encoding='utf-8')
    udpClientSocket.sendto(data,addr)
    data,addr = udpClientSocket.recvfrom(bufferSize)
    if not data:
        break
    print(data.decode('utf-8'))
udpClientSocket.close()
>hi,there
Sat Nov 13 15:38:35 2021 
>how are you
Sat Nov 13 15:38:40 2021 
>

15.9 实现socketserver TCP时间戳服务器

from socketserver import (TCPServer as TCP, StreamRequestHandler as SRH)
from time import ctime
host = ''
port = 9876
addr = (host, port)

class MyRequestHandler(SRH):
    def handle(self):
        print('客户端已经连接,地址:',self.client_address)
        self.wfile.write(ctime().encode(encoding='utf-8')+b''+self.rfile.readline())
tcpServer = TCP(addr, MyRequestHandler)
tcpServer.serve_forever()
客户端已经连接,地址: ('127.0.0.1', 55413)

16 网络高级技术

说明:本章的多个代码使用的是同一个端口,运行时可能出现端口占用的情况,可以通过查看和关闭本地端口解决。
https://www.cnblogs.com/bulrush/p/11120214.html

16.1 网络高级技术

'''
1. urllib3
2. Twisted
3. FTP
4. Email
'''
from urllib3 import *
from urllib.parse import urlencode
disable_warnings()
http = PoolManager()
url = 'http://www.baidu.com/s?' + urlencode({'wd':'极客起源'})
print(url)

response = http.request('GET', url)
print(response.data.decode('utf-8'))
# 返回未经渲染的html格式的搜索内容
http://www.baidu.com/s?wd=%E6%9E%81%E5%AE%A2%E8%B5%B7%E6%BA%90
# 检索返回内容过长,这里不作展示

16.2 发送HTTP POST请求

# 先运行server.py,再运行本程序
from urllib3 import *
disable_warnings()
http = PoolManager()
url = 'http://localhost:5000/register'
response = http.request('POST', url, fields={'name':'李宁','age':30})
data = response.data.decode('utf-8')
print(data)
注册成功

server.py

from flask import Flask,request
app = Flask(__name__)
@app.route('/register',methods = ['POST'])
def register():
    print(request.form.get('name'))
    print(request.form.get('age'))
    return '注册成功'
if __name__ == '__main__':
    app.run()
 * Serving Flask app 'server' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [13/Nov/2021 16:11:32] "POST /register HTTP/1.1" 200 -
李宁
30

16.3 HTTP请求头

# 利用Google浏览器对网页的右键-检查功能,查看一个网页的HTTP请求头、响应头格式
from urllib3 import *
import re
disable_warnings()
http = PoolManager()
url = 'https://list.tmall.com/search_product.htm?q=%E7%94%B5%E8%84%91&type=p&vmarket=&spm=875.7931836%2FB.a2227oh.d100&from=mallfp..pc_1_searchbutton'
def str2Headers(file):
    headerDict={}
    f = open(file,'r')
    headersText = f.read()
    headers = re.split('\n',headersText)
    for header in headers:
        result = re.split(':',header,maxsplit=1)
        headerDict[result[0]] = result[1]
    f.close()
    return headerDict
headers = str2Headers('header.txt')
# print(headers)
response = http.request('GET',url,headers = headers)
data = response.data.decode('GB18030')
print(data)

16.4 HTTP响应头

from urllib3 import *
disable_warnings()
http=PoolManager()
url = 'https://www.baidu.com'
response=http.request('GET',url)
print(response.info())
print()
print(response.info()['content-Length'])
HTTPHeaderDict({'Accept-Ranges': 'bytes', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Content-Length': '227', 'Content-Type': 'text/html', 'Date': 'Sat, 13 Nov 2021 08:30:24 GMT', 'P3p': 'CP=" OTI DSP COR IVA OUR IND COM ", CP=" OTI DSP COR IVA OUR IND COM "', 'Pragma': 'no-cache', 'Server': 'BWS/1.1', 'Set-Cookie': 'BD_NOT_HTTPS=1; path=/; Max-Age=300, BIDUPSID=197EA2480ADF71253F42D4DE3B72745A; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com, PSTM=1636792224; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com, BAIDUID=197EA2480ADF7125CE4A18C488A5908E:FG=1; max-age=31536000; expires=Sun, 13-Nov-22 08:30:24 GMT; domain=.baidu.com; path=/; version=1; comment=bd', 'Strict-Transport-Security': 'max-age=0', 'Traceid': '1636792224085861863410111604001318584065', 'X-Frame-Options': 'sameorigin', 'X-Ua-Compatible': 'IE=Edge,chrome=1'})

227

16.5 上传文件

# 先运行upload_server.py
from urllib3 import *
disable_warnings()
http = PoolManager()
url='http://localhost:5000'
while True:
    filename=input('请输入要上传的文件名:')
    if not filename:
        break
    with open(filename,'rb') as f:
        fileData = f.read()
    response = http.request('POST',url,fields={'file':(filename,fileData)})
    print(response.data.decode('utf-8'))

upload_server.py

import os
from flask import Flask,request
UPDATE_FOLDER='uploads'
app=Flask(__name__)
@app.route('/',methods=['POST'])
def upload_file():
    file = request.files['file']
    if file:
        file.save(os.path.join(UPDATE_FOLDER,file.filename))
        return '文件上传成功'
if __name__ == '__main__':
    app.run()
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [13/Nov/2021 16:47:57] "POST / HTTP/1.1" 200 -
请输入要上传的文件名:header.txt
文件上传成功

16.6 超时

'''
urllib3:连接超时、读超时
http.request('GET',url,timeout=Timeout(connect=2.0,read=3.0))
'''
from urllib3 import *
disable_warnings()
http=PoolManager(timeout=Timeout(connect=2.0,read=3.0))
url1='http://www.baidu123.com'
url2='http://httpbin.org/delay/3'
try:
    http.request('GET',url1)
except Exception as e:
    print(e)

response=http.request('GET',url2,timeout=Timeout(connect=2.0,read=4.0))
print(response.info)

response=http.request('GET',url2,timeout=Timeout(connect=2.0,read=2.0))

16.7 Twisted框架程序

'''
Twisted是一个完整的事件驱动的网络框架(异步编程模型的网络框架),scrapy底层使用了Twisted
1、同步编程模型:一个任务执行完成才执行下一个任务
2、线程编程模型:多个任务同时开始执行
3、异步编程模型:按同步方式执行多个任务,但是当某个任务暂停,会先执行另一个任务
为什么要使用异步编程模型?
1、线程编程模型使用起来很复杂,而且由于线程调度不可控,所以在使用线程模型时要认为这些线程是同时执行的(尽管实际情况并非如此),因此要在代码上加一些与线程有关的机制,如同步、加锁、解锁等
2、如果有一两个任务需要与用户交互,使用异步编程可以立刻切换到其他任务,这一切是可控的
3、任务之间相互独立,以至于任务内部的交互很少,异步编程比线程编程更简单、更易操作
'''
'''
Reactor(反应堆)模型
demo:
使用Socket来访问多个服务器
select模块中的select方法监视所有的socket是否有数据需要接受
1、Twisted的Reactor模式必须通过run函数启动
2、run函数需要在主线程中运行
3、一旦启动Reactor,会一直运行下去。Reactor会在程序控制之下
4、Reactor循环不会消耗任何的CPU资源
'''
# 注意:pollreactor需要从select模块中导入poll等类,而在Windows下,select模块的使用受限
# Note that on Windows, it only works for sockets; on other operating systems, it also works for other file types (in particular, on Unix, it works on pipes).
# 该程序无法在Windows下运行
from twisted.internet import pollreactor
pollreactor.install()
def hello():
    print('Hello, how are you?')
from twisted.internet import reactor
reactor.callWhenRunning(hello)
reactor.run()

16.8 用Twisted框架实现时间戳客户端

'''
connectedTCP函数、host、port、ClientFactory
'''
# 先运行demo15.4.py,再运行本程序
from twisted.internet import protocol,reactor
host = 'localhost'
port = 1234
class MyProtocol(protocol.Protocol):
    def sendData(self):
        data = input('>')
        if data:
            print('...正在发送 %s' % data)
            self.transport.write(data.encode(encoding='utf-8'))
        else:
            self.transport.loseConnection()
    # 发送数据
    def connectionMade(self):
        self.sendData()
    def dataReceived(self, data):
        print(data.decode('utf-8'))
        self.sendData()
class MyFactory(protocol.ClientFactory):
    protocol = MyProtocol
    clientConnetionLost = clientConnectionFailed = lambda self,connector,reason:reactor.stop()
reactor.connectTCP(host,port,MyFactory())
reactor.run()
>hello world
...正在发送 hello world
Tue Nov 16 12:11:08 2021 hello world
>

16.9 用Twisted框架实现时间戳服务器

# 运行本程序后,再运行demo16.8.py启动客户端
from twisted.internet import protocol,reactor
from time import ctime
port=1234
class MyProtocol(protocol.Protocol):
    def connectionMade(self):
        # 获取客户端的IP
        client = self.transport.getPeer().host
        print(f"客户端{client}已经连接")
    def dataReceived(self, data):
        self.transport.write(ctime().encode(encoding='utf-8'))
factory = protocol.Factory()
factory.protocol=MyProtocol
print('正在等待客户端的连接......')
reactor.listenTCP(port,factory)
reactor.run()
正在等待客户端的连接......
客户端127.0.0.1已经连接

16.10 FTP客户端

'''
Mac OS X 启动FTP服务器
sudo -s launchctl load -w /System/Library/LaunchDaemons/ftp.plist
关闭:sudo -s launchctl unload -w /System/Library/LaunchDaemons/ftp.plist
Windows系统:需要开启FTP服务 https://www.cnblogs.com/ziyu-trip/p/7844162.html
'''
'''
1、login(username,password):登录FTP服务器
2、cwd(dirname):改变当前目录
3、dir(callback):列出当前目录中所有的子目录和文件
4、mkd(dirname):当前目录下建立子目录
5、storlines(cmd,f):向FTP服务器上传文本文件,cmd是FTP命令,如STOR filenam
    f是文件对象,要用文本方式打开,如f=open(filename,'r')
6、storbinary(cmd,f):向FTP服务器上传二进制文件
7、retrlines(cmd,f):从FTP服务器下载文本文件
8、retrbinary(cmd,f):从FTP服务器下载二进制文件
9、quit():关闭FTP连接并退出
'''
import ftplib
host='192.168.1.8'  # windows系统下,host修改为本机IP
# def dirCallback(dir):
#     print("hi",dir)  # 自定义目录打印格式,可以取消注释对比查看效果

def main():
    try:
        f=ftplib.FTP(host)
        f.encoding='gbk'
        # Windows系统下,ftplib默认使用utf-8编码,使用dir()时,如果目录中的文件名包含中文,会报错UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb7 in position 93: invalid start byte
        # 经测试,需要添加上面这行代码,f.encoding='gbk'或'gb2312'或'gb18030',可以正常读取中文名的文件
    except Exception as e:
        print(e)
        return
    print('FTP服务器已经连接成功')
    try:
        f.login('localhost','12345678')
    except Exception as e:
        print(e)
        return
    print('FTP服务器已经登录成功')
    f.cwd('Pictures')  # 'Pictures'改为本机FTP根目录下的目录名
    f.dir()  # 可以添加回调函数dirCallback作为参数,即f.dir(dirCallback)
    print('当前的工作目录',f.pwd())
main()
FTP服务器已经连接成功
FTP服务器已经登录成功
11-14-21  09:56PM                    0 test1.txt
11-14-21  09:56PM                    0 test符-.txt
11-15-21  12:15PM       <DIR>          Videos
当前的工作目录 /Pictures

16.11 使用SMTP发送简单的Email

'''
SMTP:Simple Message Transfer
aa@126.com(smtp.126.com)    bb@sina.com(stmp.sina.com)
email -> smtp.126.com -> stmp.sina.com -> pop3.sina.com/imap4.sina.com -> 送达
内置的smtplib模块
1、创建SMTP或SMTP_SSL对象
2、登录Email
3、准备MIMEText对象
4、发送Email
'''
import smtplib
from email.mime.text import MIMEText

sender = input("发送人的邮箱账号,如aa@126.com:")
password = input('发送人的邮箱密码(使用qq邮箱发送时,需要输入授权码而非邮箱密码):')
to = input('收件人的邮箱账号,如bb@sina.com:')
def main():
    # 指定邮件正文
    msg = MIMEText('这是第一封邮件','plain','utf-8')
    msg['From'] = sender
    msg['To'] = to
    # 设置邮件标题
    msg['Subject']= '这是宁哥教育发送的一封email'
    # 这里要相应地修改为发送人邮箱的stmp地址,使用qq邮箱发送时需要授权码
    server = smtplib.SMTP_SSL('smtp.qq.com',465)
    server.login(sender,password)
    server.sendmail(sender,[to],msg.as_string())
    server.quit()
main()

16.12 使用SMTP发送带附件的email

import smtplib
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
sender = input("发送人的邮箱账号,如aa@126.com:")
password = input('发送人的邮箱密码(使用qq邮箱发送时,需要输入授权码而非邮箱密码):')
to = input('收件人的邮箱账号,如bb@sina.com:')
def mail():
    ret = True
    try:
        msg=MIMEMultipart('related')
        msg['From'] = sender
        msg['To'] = to
        msg['Subject'] = '宁哥教育(带附件)'
        msgAlternative=MIMEMultipart('alternative')
        msg.attach(msgAlternative)
        mail_msg='''
            <p>宁哥教育祝广大学员更上一层楼</p>
            <p><a href='http://ningedu.com'>宁哥教育</a></p>
            <p>图片演示:</p>
            <p><img src = 'cid:image1'></p>
        '''
        msgAlternative.attach(MIMEText(mail_msg,'html','utf-8'))

        fp = open('Milky Way.jpeg','rb')
        msgImage = MIMEImage(fp.read())
        fp.close()

        msgImage.add_header('Content-ID','<image1>')
        msg.attach(msgImage)
        server=smtplib.SMTP_SSL('smtp.qq.com',465)
        server.login(sender,password)
        server.sendmail(sender,[to],msg.as_string())
        server.quit()
    except Exception as e:
        print(e)
        ret = False
    return ret
ret = mail()
if ret:
    print('邮件发送成功')
else:
    print('邮件发送失败')

16.13 使用IMAP4接受email

'''
IMAP : Internet Message Access Protocol, IMAP4
IMAP4 和 IMAP3 的区别:
POP3用于从邮件服务器上下载邮件信息,在本地删除、修改邮件,并不会影响服务器上的邮件
IMAP4是双向的,在本地修改、删除邮件,同时会影响到服务器
'''
import imaplib
import base64
connection=imaplib.IMAP4_SSL('imap.qq.com',993)
username=input('请输入邮箱账号:')
password=input('请输入邮箱密码:')
try:
    connection.login(username,password)
except Exception as e:
    print(e)
connection.print_log()  # 打印登录日志
# 列出所有目录
res,data=connection.list()
print('Response code',data)
res,data=connection.select('INBOX')  # 切换到INBOX
print(res, data)
print(data[0])  # 输出邮件数
res, msg_ids = connection.search(None, 'ALL')  # 搜索所有的邮件
print(res, msg_ids)
res, msg_data=connection.fetch(data[0],'(UID BODY(TEXT)')
print(msg_data)
connection.logout()

16.14 使用POP3接受email

'''
POP: Post Office Protocol POP3
'''
import poplib
import re
host = 'pop.qq.com'
username = input('邮箱地址:')
password = input('邮箱密码:')
pp = poplib.POP3_SSL(host)
pp.set_debuglevel(1)
pp.user(username)
pp.pass_(password)

ret = pp.stat()
# 获取邮件总数
mailCount = ret[0]
print(f"一共 {mailCount} 封邮件")
# 取出前20封邮件的头部信息
for i in range(1, mailCount):
    try:
        mlist = pp.top(i, 0)
        # 输出邮件的头部(列表形式)
        print(mlist[1])
        # 输出subject
        print(mlist[1][7])
        if i>20: break
    except:
        pass
ret = pp.list()
down = pp.retr(1)
charset = ''
for line in down[1]:
    result = re.search('charset\s*=\s*"([^\"]*)"',line.decode('iso8859-1'))
    if result != None:
        charset = result.group(1)
        print(charset)
    if charset != '':
        print(line.decode(charset))
pp.quit()

17 多线程

17.1 多线程

'''
1、python中的线程
2、高级线程模块
3、线程同步
4、生产者-消费者问题与queue模块
'''
'''
进程:静态和动态,静态(exe、dll),进程是动态执行的程序
线程:线程依赖于进程,一个进程可以有多个线程,一个进程必须至少有一个主线程
'''
from time import sleep, ctime
def fun1():
    print('开始运行fun1:',ctime())
    sleep(4)
    print('fun1运行结束:',ctime())
def fun2():
    print('开始运行fun2:',ctime())
    sleep(2)
    print('fun2运行结束:',ctime())
def main():
    print('开始运行时间',ctime())
    fun1()
    fun2()
    print('结束运行时间',ctime())
main()
开始运行时间 Tue Nov 16 12:33:53 2021
开始运行fun1: Tue Nov 16 12:33:53 2021
fun1运行结束: Tue Nov 16 12:33:57 2021
开始运行fun2: Tue Nov 16 12:33:57 2021
fun2运行结束: Tue Nov 16 12:33:59 2021
结束运行时间 Tue Nov 16 12:33:59 2021

17.2 多线程-2

import _thread as thread
from time import sleep, ctime
def fun1():
    print('开始运行fun1:',ctime())
    sleep(4)
    print('fun1运行结束:',ctime())
def fun2():
    print('开始运行fun2:',ctime())
    sleep(2)
    print('fun2运行结束:',ctime())
def main():
    print('开始运行时间',ctime())
    thread.start_new_thread(fun1,())
    thread.start_new_thread(fun2,())
    sleep(6)
    print('结束运行时间',ctime())
main()
# 多线程同时开始运行,print语句没有执行完毕就插入了另一个print语句的内容
开始运行时间 Tue Nov 16 12:34:25 2021
开始运行fun1:开始运行fun2:  Tue Nov 16 12:34:25 2021Tue Nov 16 12:34:25 2021

fun2运行结束: Tue Nov 16 12:34:27 2021
fun1运行结束: Tue Nov 16 12:34:29 2021
结束运行时间 Tue Nov 16 12:34:31 2021

17.3 为线程传递参数

import random
from time import sleep
import _thread as thread
def fun(a,b):
    print(a, b)
    sleep(random.randint(1,5))
for i in range(8):
    thread.start_new_thread(fun,(i+1,'a'*(i+1)))
input()
# 多线程同时开始运行,print语句没有执行完毕就插入了另一个print语句的内容
2 3aa 
aaa
4 aaaa
5 1aaaaa 
a6
 aaaaaa7
 aaaaaaa
8 aaaaaaaa

17.4 线程锁

# 用来检测某个线程是否结束
import _thread as thread
from time import sleep,ctime
def fun(index, sec, lock):
    print(F'开始执行 {index} 执行时间:',ctime())
    sleep(sec)
    print(f"执行结束 {index} 执行时间:",ctime())
    lock.release()
lock1 = thread.allocate_lock()
lock1.acquire()  # 把锁锁上
thread.start_new_thread(fun,(10,4,lock1))
lock2 = thread.allocate_lock()
lock2.acquire()
thread.start_new_thread(fun,(20,2,lock2))
while lock1.locked() or lock2.locked():
    pass
    # 当两个线程都没有被锁住时,整个程序执行完毕
# 多线程同时开始运行,print语句没有执行完毕就插入了另一个print语句的内容
开始执行 10 执行时间: 开始执行 20 执行时间:Tue Nov 16 12:36:53 2021 
Tue Nov 16 12:36:53 2021
执行结束 20 执行时间: Tue Nov 16 12:36:55 2021
执行结束 10 执行时间: Tue Nov 16 12:36:57 2021

17.5 Thread类与线程函数

import threading
from time import sleep,ctime
def fun(index,sec):
    print(f"开始执行 {index} 时间:",ctime())
    sleep(sec)
    print(f"结束执行 {index} 时间:",ctime())
thread1 = threading.Thread(target=fun,args=(10,4))
thread1.start()
thread2 = threading.Thread(target=fun,args=(20,2))
thread2.start()
thread1.join()
thread2.join()
print('程序退出')
开始执行 10 时间: Tue Nov 16 12:37:23 2021
开始执行 20 时间: Tue Nov 16 12:37:23 2021
结束执行 20 时间: Tue Nov 16 12:37:25 2021
结束执行 10 时间: Tue Nov 16 12:37:27 2021
程序退出

17.6 从Thread类继承

import threading
from time import sleep,ctime
# 线程类
class MyThread(threading.Thread):
    def __init__(self,func,args,name=''):
        super().__init__(target=func,name=name,args=args)
    def run(self):
        self._target(*self._args)  # 内部变量

# 线程函数
def fun(index, sec):
    print(f"开始执行 {index} 时间:{ctime()}")
    sleep(sec)
    print(f"执行完毕 {index} 时间:{ctime()}")
print('开始:',ctime())
thread1 = MyThread(fun,(10,4),'线程1')
thread2 = MyThread(fun,(20,2),'线程2')
thread1.start()
thread2.start()
print(thread1.name)
print(thread2.name)
thread1.join()
thread2.join()
print('结束:',ctime())
开始: Tue Nov 16 12:37:45 2021
开始执行 10 时间:Tue Nov 16 12:37:45 2021
开始执行 20 时间:Tue Nov 16 12:37:45 2021线程1

线程2
执行完毕 20 时间:Tue Nov 16 12:37:47 2021
执行完毕 10 时间:Tue Nov 16 12:37:49 2021
结束: Tue Nov 16 12:37:49 2021

17.7 线程同步-线程锁

import random
from time import sleep,ctime
from threading import Thread,Lock,current_thread
from atexit import register
# 创建线程锁对象
lock = Lock()
def fun():
    # 获取线程锁权限
    lock.acquire()
    # 将for循环变成原子操作
    for i in range(5):
        print('Thread Name =',current_thread().name,'i=',i)
        sleep(random.randint(1,5))
    lock.release()
def main():
    for i in range(3):
        Thread(target=fun).start()
@register
def exit():
    print('线程执行完毕',ctime())
main()
Thread Name = Thread-1 (fun) i= 0
Thread Name = Thread-1 (fun) i= 1
Thread Name = Thread-1 (fun) i= 2
Thread Name = Thread-1 (fun) i= 3
Thread Name = Thread-1 (fun) i= 4
Thread Name = Thread-2 (fun) i= 0
Thread Name = Thread-2 (fun) i= 1
Thread Name = Thread-2 (fun) i= 2
Thread Name = Thread-2 (fun) i= 3
Thread Name = Thread-2 (fun) i= 4
Thread Name = Thread-3 (fun) i= 0
Thread Name = Thread-3 (fun) i= 1
Thread Name = Thread-3 (fun) i= 2
Thread Name = Thread-3 (fun) i= 3
Thread Name = Thread-3 (fun) i= 4
线程执行完毕 Tue Nov 16 12:39:11 2021

17.8 线程同步-信号量

'''
信号量是一个计数器 消耗资源:p  释放资源:v
acquire   release
'''
from threading import BoundedSemaphore
MAX = 3
semaphore = BoundedSemaphore(MAX)
print(semaphore._value)
# 计数器减1
semaphore.acquire()
print(semaphore._value)
semaphore.acquire()
print(semaphore._value)
semaphore.acquire()
print(semaphore._value)
print(semaphore.acquire(False))  # 此时调用程序会堵塞

# 释放资源
semaphore.release()
print(semaphore._value)
semaphore.release()
print(semaphore._value)
semaphore.release()
print(semaphore._value)
# semaphore.release()  # 释放资源超过MAX,会报错
3
2
1
0
False
1
2
3

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

from random import randrange
from time import sleep,time,ctime
from threading import Lock,Thread
from queue import Queue
lock = Lock()
class MyThread(Thread):
    def __init__(self,func,args):
        super().__init__(target=func,args=args)
# 向队列中添加商品
def writeQ(queue):
    lock.acquire()
    print('生产了一个对象,并将其添加到队列中',end=' ')
    queue.put('商品')
    print('队列尺寸',queue.qsize())
    lock.release()
# 从队列中获取商品
def readQ(queue):
    lock.acquire()
    val = queue.get(1)
    print('消费了一个对象,队列尺寸',queue.qsize())
    lock.release()
def writer(queue, loops):
    for i in range(loops):
        writeQ(queue)
        sleep(randrange(1,4))
def reader(queue,loops):
    for i in range(loops):
        readQ(queue)
        sleep(randrange(2,6))
funcs=[writer,reader]
nfuncs=range(len(funcs))
def main():
    nloops=randrange(2,6)
    q=Queue(32)
    threads=[]
    for i in nfuncs:
        t=MyThread(funcs[i],(q,nloops))
        threads.append(t)
    for i in nfuncs:
        threads[i].start()
    for i in nfuncs:
        threads[i].join()
main()
生产了一个对象,并将其添加到队列中 队列尺寸 1
消费了一个对象,队列尺寸 0
生产了一个对象,并将其添加到队列中 队列尺寸 1
消费了一个对象,队列尺寸 0
生产了一个对象,并将其添加到队列中 队列尺寸 1
消费了一个对象,队列尺寸 0

18 GUI库-tkinter模块

本章建议自行运行程序,观察图形界面的展示效果。

18.1 GUI库:tkinter

'''
1、编写第一个tkinter程序
2、布局(pack/place/grid)
3、控件(Label/Button/Text/Radio/Checkbox等)
4、菜单
5、对话框
Python默认的GUI库,基于Tk工具包,为TCL(tool command language)设计的
Tk流行后,被移植到其他语言,Python、Perl、Ruby
'''
'''
1、导入tkinter模块
2、创建Tk类的实例,Tk对象表示一个窗口
3、对窗口进行设置
4、创建空间类的实例,并将控件添加到窗口上
5、调用mainloop函数进入事件循环
'''
import tkinter
window = tkinter.Tk()

window['background'] = 'blue'

w = 300
h = 200
# 获取屏幕的宽度和高度
ws = window.winfo_screenwidth()
hs = window.winfo_screenheight()
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
window.title('第一个tkinter应用')
window.geometry('%dx%d+%d+%d' % (w,h,x,y))  # geometry的格式设置就是如此!!!

label = tkinter.Label(window, text='Hello World')
label.pack()

tkinter.mainloop()

18.2 pack布局-水平居中

'''
三种布局方法:pack/grid/place
pack可以指定相对位置,另外两种需要指定绝对位置
'''
from tkinter import *
window = Tk()
window.title('水平居中')
window.geometry('200x100')
window['background'] = 'blue'
Label(window,text='复仇者联盟',bg='red',fg='white').pack()
Label(window,text='正义联盟',bg='green',fg='black').pack()
Label(window,text='天启星',bg='yellow',fg='blue').pack()
mainloop()

18.3 pack布局-水平填充

from tkinter import *
window = Tk()
window['background'] = 'blue'
window.geometry('200x100')
w = Label(window,text='复仇者联盟',bg='red',fg='white')
w.pack(fill=X)
w = Label(window,text='超人',bg='green',fg='yellow')
w.pack(fill=X)
w = Label(window,text='保卫地球',bg='yellow',fg='blue')
w.pack(fill=X)
mainloop()

18.4 pack布局-设置水平外边框

from tkinter import *
window = Tk()
window.title('设置水平外边框')
window['background'] = 'blue'
window.geometry('200x100')
w = Label(window,text='复仇者联盟',bg='red',fg='white')
w.pack(fill=X,padx = 10)  # 无论窗口拉得多大,Label的左右外边框宽度都是10个像素
w = Label(window,text='超人',bg='green',fg='yellow')
w.pack(fill=X,padx = 10)
w = Label(window,text='保卫地球',bg='yellow',fg='blue')
w.pack(fill=X,padx = 10)
mainloop()

18.5 pack布局-设置垂直外边框

from tkinter import *
window = Tk()
window.title('设置垂直外边框')
window['background'] = 'blue'
window.geometry('200x100')
w = Label(window,text='复仇者联盟',bg='red',fg='white')
w.pack(fill=X,pady = 10)  # 无论窗口拉得多大,Label的上下外边框的宽度都是10个像素
w = Label(window,text='超人',bg='green',fg='yellow')
w.pack(fill=X,pady = 10)
w = Label(window,text='保卫地球',bg='yellow',fg='blue')
w.pack(fill=X,pady = 10)
mainloop()

18.6 pack布局-设置水平及垂直外边框

from tkinter import *
window = Tk()
window.title('设置水平及垂直外边框')
window['background'] = 'blue'
window.geometry('200x100')
w = Label(window,text='复仇者联盟',bg='red',fg='white')
w.pack(fill=X,padx=10,pady = 10)  # 无论窗口拉得多大,Label的上下、左右外边框的宽度都是10个像素
w = Label(window,text='超人',bg='green',fg='yellow')
w.pack(fill=X,padx=10,pady = 10)
w = Label(window,text='保卫地球',bg='yellow',fg='blue')
w.pack(fill=X,padx=10,pady = 10)
mainloop()

18.7 pack布局-设置内边距

from tkinter import *
window = Tk()
window.title('设置内边距')
window['background'] = 'blue'
window.geometry('200x200')
w = Label(window,text='复仇者联盟',bg='red',fg='white')
w.pack(fill=X,ipadx=10,ipady=10,padx=10,pady = 10)
w = Label(window,text='超人',bg='green',fg='yellow')
w.pack(fill=X,ipadx=10,ipady=10,padx=10,pady = 10)
w = Label(window,text='保卫地球',bg='yellow',fg='blue')
w.pack(fill=X,ipadx=10,ipady=10,padx=10,pady = 10)
mainloop()

18.8 pack布局-水平排列

# 从左向右排列,如果将下面的side参数改为RIGHT,则可实现从右向左排列
from tkinter import *
window = Tk()
window.title('水平排列')
window['background'] = 'blue'
window.geometry('200x200')
w = Label(window,text='复仇者联盟',bg='red',fg='white')
w.pack(side=LEFT,padx=10,pady = 10)
w = Label(window,text='超人',bg='green',fg='yellow')
w.pack(side=LEFT,padx=10,pady = 10)
w = Label(window,text='保卫地球',bg='yellow',fg='blue')
w.pack(side=LEFT,padx=10,pady = 10)
mainloop()

18.9 place布局

import random
from tkinter import *
window = Tk()
window.title('place布局')
window['background'] = 'blue'
window.geometry('180x200+30+30')
languages = ['Python','Swift','C++','Java','Kotlin']
labels = range(5)
for i in labels:
    # 随机产生三原色
    ct = [random.randrange(256) for x in range(3)]
    # 取亮度
    brightness = int(round(0.299*ct[0]+0.587*ct[1]+0.144*ct[2]))
    # 得到背景色的十六进制形式
    ct_hex = '%02x%02x%02x' % tuple(ct)
    bg_colour='#'+''.join(ct_hex)
    label = Label(window,text=languages[i],fg='White' if brightness<120 else 'Black',
                  bg=bg_colour)
    label.place(x=25,y=30+i*30,width=120,height=25)
mainloop()

18.10 grid布局

from tkinter import *
window = Tk()
window.title('grid布局')
window['background'] = 'blue'
window.geometry('400x150+30+30')
colours = ['red','green','orange','white','yellow','blue']
r = 0
for c in colours:
    Label(window,text=c,relief=RIDGE,width=15).grid(row=r,column=0)
    Label(window,bg=c,relief=RIDGE,width=15).grid(row=r,column=1)
    Label(window,text=c,relief=RIDGE,width=15).grid(row=r,column=2)
    r = r+1
mainloop()

18.11 控件-Label和Button

import tkinter as tk
window = tk.Tk()
window.title('Label控件和Button控件')
window['background'] = 'blue'
window.geometry('300x200+30+30')
label1=tk.Label(window,text='Hello World',bg='green',
                font=('Arial',12))
label1.pack()
var=tk.StringVar()
var.set('I love you.')
label2=tk.Label(window,textvariable=var,fg='blue',
                bg='yellow',font=('Arial',12),width=15,height=2)
label2.pack(pady=20)
onHit = False
# 第一个按钮的点击回调函数
def hitMe():
    global onHit
    if onHit==False:
        onHit = True
        var.set('世界你好')
    else:
        onHit = False
        var.set('I am fine.')
button1=tk.Button(window,text='点击我',command=hitMe)
button1.pack()
def getLabelText():
    print(var.get())
button2=tk.Button(window,text='获取Label控件的文本',command=getLabelText)
button2.pack(pady=20)
window.mainloop()

18.12 Entry控件与Text控件

'''
Entry:只能用于输入单行文本
Text:可以用于输入多行文本,还支持图像、富文本

Entry类:构造方法的show参数设置一个字符,回显字符。如show='*'
'''
import tkinter as tk
window=tk.Tk()
window.title('Entry控件与Text控件')
window['background']='blue'
window.geometry('1920x1800+30+30')
entryVar1=tk.StringVar()
def callback():
    # 更新第二个Entry控件中的文本
    entryVar2.set(entryVar1.get())
# 将entryVar1与callback关联
entryVar1.trace('w',lambda a,b,c:callback())

# 创建第一个Entry控件
entry1=tk.Entry(window,textvariable=entryVar1,show='*')
entry1.pack(pady=10)

entryVar2=tk.StringVar()
entry2=tk.Entry(window,textvariable=entryVar2)
entry2.pack(pady=10)

# Text控件
text=tk.Text(window)
text.pack(pady=10)
# gif/bmp等,不直接支持JPG、png。要使用PIL进行处理才能插入JPG、PNG
from PIL import Image,ImageTk
# PIL包已经更新为pillow包
png=Image.open('pic.png')  # 选取的图片不能太大,否则显示不了
photo1=ImageTk.PhotoImage(png)
# # 在Text控件结尾插入图像
text.image_create(tk.END,image=photo1)
# 进行字体、字号等设置,big只是一个标识,相当于字典,对应的值为后面的font
text.tag_configure('big',font=('Arial',25,'bold'))
text.insert(tk.END,'懵圈','big')
jpg=Image.open('pic.jpg')
photo2=ImageTk.PhotoImage(jpg)
text.image_create(tk.END,image=photo2)

window.mainloop()

18.3 Radiobutton控件

# 单选控件
import tkinter as tk
window=tk.Tk()
window.title('Radiobutton控件')
window.geometry('200x200')
window['background']='blue'

var=tk.StringVar()
label=tk.Label(window,bg='yellow',width=20,text='empty')
label.pack()
var.set('选项A')
# 当Radiobutton的选择变化后,会调用该函数
def printSelection():
    label.config(text='您已经选择了'+var.get())
r1=tk.Radiobutton(window,text='选项A',variable=var,value='A',command=printSelection)
r1.pack()
r2=tk.Radiobutton(window,text='选项B',variable=var,value='B',command=printSelection)
r2.pack()
r3=tk.Radiobutton(window,text='选项C',variable=var,value='C',command=printSelection)
r3.pack()
window.mainloop()

18.14 Checkbutton控件

# 多选控件,通过Checkbutton类构造方法的onvalue参数指定控件选中状态的值
# 通过offvalue参数指定控件未选中状态的值
import tkinter as tk
window=tk.Tk()
window.title('Radiobutton控件')
window.geometry('200x200')
window['background']='blue'

label=tk.Label(window,bg='yellow',width=20,text='empty')
label.pack()
# 当Checkbutton的选择变化后,会调用该函数
def printSelection():
    text=''
    if var1.get()==1:
        text += ' '+c1.cget('text')
    if var2.get()==1:
        text += ' '+c2.cget('text')
    if var3.get()==1:
        text += ' '+c3.cget('text')
    if var4.get()==1:
        text += ' '+c4.cget('text')
    label.config(text=text)
var1=tk.IntVar()
var2=tk.IntVar()
var3=tk.IntVar()
var4=tk.IntVar()
c1=tk.Checkbutton(window,text='Python',variable=var1,onvalue=1,offvalue=0,
                  command=printSelection)
c2=tk.Checkbutton(window,text='C++',variable=var2,onvalue=1,offvalue=0,
                  command=printSelection)
c3=tk.Checkbutton(window,text='Kotlin',variable=var3,onvalue=1,offvalue=0,
                  command=printSelection)
c4=tk.Checkbutton(window,text='Swift',variable=var4,onvalue=1,offvalue=0,
                  command=printSelection)
c1.pack()
c2.pack()
c3.pack()
c4.pack()
window.mainloop()

18.15 Scale控件(滑块控件)

'''
label:在Scale控件旁边显示的标签文本,水平滑块控件在上方显示,垂直滑块在右侧显示
length:Scale控件的长度
from_:滑块能设置的最小值,也是Scale控件的起始值
to:滑块能设置的最大值,也是Scale控件的结束值
tickinterval:Scale控件刻度的步长
resolution:滑块能滑动的步长
command:指定滑动事件对应的函数
orient:设置Scale控件的类型,HORIZONTAL表示水平控件,VERTICAL表示垂直控件
'''
import tkinter as tk
window=tk.Tk()
window.title('Scale控件')
window.geometry('200x400')
window['background']='blue'

label1=tk.Label(window,bg='yellow',width=20)
label1.pack()
# 水平Scale控件滑块滑动时调用的函数
def printSelection1(v):
    label1.config(text='当前值:'+v)
scale1=tk.Scale(window,label='拖我',from_=5,to=11,orient=tk.HORIZONTAL,
                length=200,tickinterval=2,resolution=0.01,command=printSelection1)
scale1.pack(pady=10)
label2=tk.Label(window,bg='yellow',width=20)
label2.pack()
# 垂直Scale控件滑块滑动时调用的函数
def printSelection2(v):
    label2.config(text='当前值:'+v)
scale2=tk.Scale(window,label='拖我',from_=5,to=11,orient=tk.VERTICAL,
                length=200,tickinterval=2,resolution=0.01,command=printSelection2)
scale2.pack(pady=10)
window.mainloop()

18.16 Listbox控件(列表控件)

import tkinter as tk
window=tk.Tk()
window.title('Listbox控件')
window.geometry('300x400')
window['background']='blue'

var1=tk.StringVar()

label=tk.Label(window,textvariable=var1,bg='yellow',width=20)
label.pack()

var2=tk.StringVar()
var2.set((11,22,33,44))
lb=tk.Listbox(window,listvariable=var2)
def onselect(evt):
    w=evt.widget
    value=w.get(w.curselection())
    var1.set(value)

lb.bind('<<ListboxSelect>>',onselect)
lb.pack(pady=20)
lb.selection_set(first=0)
value=lb.get(lb.curselection())
var1.set(value)

list_items=[11111,2222,4444,5555]
for item in list_items:
    lb.insert('end',item)
lb.insert(1,'Python')
lb.insert(2,'Kotlin')
lb.insert(3,'Swift')
# lb.delete(2)
window.mainloop()

18.17 向窗口添加菜单

'''
Menu对象   第一个Menu对象,构造方法需要传入window
'''
import tkinter as tk
window=tk.Tk()
window.title('菜单')
window.geometry('300x400')

label=tk.Label(window,width=20,bg='yellow')
label.pack()
counter=0
def menuClick():
    global counter
    label.config(text='第'+str(counter)+'次点击')
    counter +=1
# 创建根菜单
menubar=tk.Menu(window)

# 创建文件菜单
filemenu=tk.Menu(menubar)
menubar.add_cascade(label='文件',menu=filemenu)
filemenu.add_command(label='新建',command=menuClick)
filemenu.add_command(label='打开',command=menuClick)
filemenu.add_command(label='保存',command=menuClick)
# 在“文件”菜单项下添加带子菜单项的“导入”菜单项
submenu=tk.Menu(filemenu)
filemenu.add_cascade(label='导入',menu=submenu)
submenu.add_command(label='导入文本文件',command=menuClick)
submenu.add_command(label='导入PDF文件',command=menuClick)

# 添加一个分隔条
filemenu.add_separator()
filemenu.add_command(label='退出',command=window.quit)

editmenu=tk.Menu(menubar)
menubar.add_cascade(label='编辑',menu=editmenu)
editmenu.add_command(label='剪切',command=menuClick)
editmenu.add_command(label='复制',command=menuClick)
editmenu.add_command(label='粘贴',command=menuClick)

window.config(menu=menubar)
window.mainloop()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值