第一章:正则表达式

匹配模式

(?id/name)Y|N) : 如果分组所提供的id或者name(名称)存在,就返回正则表达式的条件匹配Y,如果不存在,就返回N;

  1. |N是可选项
  2. eg : (?(1)y|x)

管道符注意

管道符表示一个从多个模式中选择其一的操作 , 所以不必添加括号

abc | bcd | efg
不必写成 (abc) | (bcd) | (efg)

</?[^>]+> : 匹配全部有效的(和无效的)HTML 标签

对已编译的对象进行缓存

  1. 强烈建议使用预编译
  2. 模块函数会对已编译的对象进行缓存,所以不是所有使用相同正则表达式模式的 search()和 match()都需要编译。
  3. 即使这样,使用预编译也节省了缓存查询时间,并且不必对于相同的字符串反复进行函数调用。
  4. 在不同的 Python 版本中,缓存中已编译过的正则表达式对象的数目可能不同。
  5. purge()函数能够用于清除这些缓存。

使用regex更换位置

当我们需要更换字符串的位置的时候 , 不一定需要使用split()函数,然后重组字符串 .可以直接使用regex

1
2
3
4
5
6
7
8
9
10
11
import re

cn_names = ['he-yingliang','deng-shouze','chen-zhuji','guo-zhirong']

en_names = []
for name in cn_names:
x = re.sub(r'(\w+)-(\w+)',r'\2 \1',name)
en_names.append(x)

print(en_names)
# ['yingliang he', 'shouze deng', 'zhuji chen', 'zhirong guo']

re.split

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re
DATA = (
'Mountain View, CA 94040',
'Sunnyvale, CA',
'Los Altos, 94023',
'Cupertino 95014',
'Palo Alto CA',
)

for datum in DATA:
print(re.split(r', |(?= (?:\d{5}|[A-Z]{2})) ', datum))
# ['Mountain View', 'CA', '94040']
# ['Sunnyvale', 'CA']
# ['Los Altos', '94023']
# ['Cupertino', '95014']
# ['Palo Alto', 'CA']

', |(?= (?:\d{5}|[A-Z]{2})) ' : 匹配逗号+空格 或者 前面是5个数字或2个字母的空格

通常这样说:如果空格紧跟在五个数字或者两个大写字母之后,就用 split 语句分割该空格。这就允许我们在城市名中放置空格。

(?iLmsux)拓展符号

re.I

如果我们要想使用re.I/IGNORECASE标志,可以直接在正则表达式里面指定一个或者多个标记,而不是通过 compile()

1
2
3
import re
print(re.findall(r'(?i)yes', 'yes? Yes. YES!!'))
# ['yes', 'Yes', 'YES']

re.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import re

x = re.findall(r'(^th[\w ]+)',
"""
This line is the first,
another line,
that line, it's the best
"""
)
print(x)
# []

x = re.findall(r'(?im)(^th[\w ]+)',
"""
This line is the first,
another line,
that line, it's the best
"""
)
print(x)
# ['This line is the first', 'that line']

re.M/MULTILINE : 多行模式改变了^和$的匹配行为 .能够在目标字符串中实现跨行搜索,而不必将整个字符串视为单个实体。

re.s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re

x = re.findall(r'th.+', '''
the second line
the third line
'''
)
print(x)
# ['the second line', 'the third line']

x = re.findall(r'(?s)th.+', '''
The first line
the second line
the third line
'''
)

print(x)
# ['the second line\nthe third line\n']

re.x

该标记允许用户通过抑制在正则表达式中使用空白符(除了在字符类中或者在反斜线转义中)来创建更易读的正则表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import re

x = re.search(
r'''(?x)
\((\d{3})\) # 区号
[ ] # 空白符
(\d{3}) # 前缀
- # 横线
(\d{4}) # 终点数字
''',
'(800) 555-1212').groups()

print(x)
# ('800', '555', '1212')

正则获取表格的最佳实践

使用split和\s\s+分列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
x='''UID        PID  PPID  C STIME TTY          TIME CMD
root 2 0 0 Nov30 ? 00:00:00 [kthreadd]
root 3 2 0 Nov30 ? 00:00:14 [ksoftirqd/0]
root 5 2 0 Nov30 ? 00:00:00 [kworker/0:0H]
root 7 2 0 Nov30 ? 00:00:00 [migration/0]
root 8 2 0 Nov30 ? 00:00:00 [rcu_bh]
root 9 2 0 Nov30 ? 00:00:57 [rcu_sched]'''


import re

for line in re.split(r'\n',x):
res = re.split(r'\s\s+',line)
print(res)
# ['UID', 'PID', 'PPID', 'C STIME TTY', 'TIME CMD']
# ['root', '2', '0', '0 Nov30 ?', '00:00:00 [kthreadd]']
# ['root', '3', '2', '0 Nov30 ?', '00:00:14 [ksoftirqd/0]']
# ['root', '5', '2', '0 Nov30 ?', '00:00:00 [kworker/0:0H]']
# ['root', '7', '2', '0 Nov30 ?', '00:00:00 [migration/0]']
# ['root', '8', '2', '0 Nov30 ?', '00:00:00 [rcu_bh]']
# ['root', '9', '2', '0 Nov30 ?', '00:00:57 [rcu_sched]']
  1. 先使用\n分行
  2. 再使用\s\s+分列

使用split和\s\s*分列

1
2
3
4
5
6
7
8
9
10
11
12
13
s = '''UID        PID  PPID  C STIME TTY          TIME CMD
root 1 0 0 Nov30 ? 00:01:19 /usr/lib/systemd/systemd --system --deserialize 21
root 2 0 0 Nov30 ? 00:00:00 [kthreadd]
root 3 2 0 Nov30 ? 00:00:14 [ksoftirqd/0]'''

import re
for line in re.split(r'\n',s):
res = re.split(r'\s\s*',line,maxsplit=7)
print(res)
# ['UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD']
# ['root', '1', '0', '0', 'Nov30', '?', '00:01:19', '/usr/lib/systemd/systemd --system --deserialize 21']
# ['root', '2', '0', '0', 'Nov30', '?', '00:00:00', '[kthreadd]']
# ['root', '3', '2', '0', 'Nov30', '?', '00:00:14', '[ksoftirqd/0]']
  • 当遇到C STIME TTY两个字段只有一个空格的时候,可以使用\s\s*分列
  • 对于使用\t的 , 我们可以使用\s\s*|\t分列

全部使用正则匹配

1
2
3
4
5
6
映像名称                       PID 会话名
========================= ========
System Idle Process 0
System 4
Registry 120
smss.exe 472
1
2
3
4
5
6
7
8
def jerry_pick():
with os.popen('tasklist') as f:
for line in f:
res = re.findall(r'((?:\w+\s?)+?)\s\s*(\d+)',line)
print(res)
# [('System Idle Process ', '0'), ('Services ', '0')]
# [('System ', '4'), ('Services ', '0')]
# [('Registry ', '120'), ('Services ', '0')]

第二章:网络编程

套接字概述

  • 说白了 , 套接字就是通信端点

  • 套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个进程与另一个运行的程序进行通信。这就是所谓的进程间通信(Inter Process Communication,IPC)。

  • 最常用的两种类型的套接字:

    • 基于文件的UNIX 套接字 : 即 AF_UNIX ,

      因为两个进程运行在同一台计算机上 , 多个进程之间经常的共享常量。所以进程间通信的套接字都是基于文件的

    • 面向网络的套接字 : AF_INET ,

      另一个地址家族 AF_INET6 : 用于第 6 版因特网协议(IPv6)寻址

  • Python 只支持 AF_UNIX、AF_NETLINK、AF_TIPC 和 AF_INET 家族。

面向连接的套接字与无连接的套接字

面向连接的套接字

  • 面向连接的套接字,这意味着在进行通信之前必须先建立一个连接,例如,使用电话系统给一个朋友打电话

    这种类型的通信也称为虚拟电路流套接字

  • 实现这种连接类型的主要协议是传输控制协议(即TCP)。

  • 为了创建 TCP 套接字,必须使用 SOCK_STREAM 作为套接字类型。

无连接的套接字

  • 数据报类型的套接字

  • 在数据传输过程中并无法保证它的顺序性、可靠性或重复性

  • 实现这种连接类型的主要协议是用户数据报协议(即UDP)。

  • 创建 UDP 套接字,必须使用 SOCK_DGRAM 作为套接字类型。

    SOCK_DGRAM 名字来自于单词“datagram”(数据报)。

Python 中的网络编程

要创建套接字,必须使用 socket.socket()函数

1
socket(socket_family, socket_type, protocol)
  • socket_family 是 AF_UNIX 或 AF_INET,
  • socket_type 是 SOCK_STREAM或 SOCK_DGRAM

所以:

1
2
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

套接字对象(内置)方法

服务端套接字

函数 描述
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来

客户端套接字

函数 描述
s.connect() 主动初始化TCP服务器连接。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数

函数 描述
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.recv_into() 接收 TCP 消息到指定的缓冲区
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom() 接收 UDP 消息
s.recvfrom_into() 接收 UDP 消息到指定的缓冲区
s.sendto() 发送 UDP 消息
s.getpeername() 连接到套接字(TCP)的远程地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回给定套接字选项的值
s.setsockopt() 设置给定套接字选项的值
s.shutdown() 关闭连接
s.close() 关闭套接字
s.detach() 在未关闭文件描述符的情况下关闭套接字,返回文件描述符
s.ioctl() 控制套接字的模式(仅支持 Windows)

面向阻塞的套接字方法

函数 描述
s.setblocking() 设置套接字的阻塞或非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 获取阻塞套接字操作的超时时间

面向文件的套接字方法

函数 描述
s.fileno() 套接字的文件描述符
s.makefile() 创建与套接字关联的文件对象

数据属性

函数 描述
s.family 套接字家族
s.type 套接字类型
s.proto 套接字协议

服务端的

1
data = sock.recv(1024)
  • 一旦服务器接受了一个连接,就会返回一个独立的客户端套接字,用来与即将到来的消息进行交换。
  • 当新的客户端请求过来时 , 服务器会创建一个新的通信端口来直接与客户端进行通信
  • 这样就能够空出主线(原始服务器套接字),以便接线员可以继续等待客户请求
  • 一旦创建了临时套接字,通信就可以开始,通过使用这个新的套接字,客户端与服务器就可以开始参与发送和接收的对话中,直到连接终止。当一方关闭连接或者向对方发送一个空字符串时,通常就会关闭连接。

优雅地退出和调用服务器 close()方法

  • 在开发中,“友好的”退出方式的一种方法就是,将服务器的 while 循环放在一个 try-except 语句中的 except 子句中,并监控 EOFError 或 KeyboardInterrupt 异常,这样你就可以在 except 或 finally 字句中关闭服务器的套接字。
  • 在生产环境中,会以一种更加自动化的方式启动和关闭服务器 : 通过使用一个线程或创建一个特殊文件或数据库条目来设置一个标记以关闭服务。

UDP 和 TCP 的在编码时差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

server.bind(('0.0.0.0',8000))
server.listen()
# 转换操作
sock,addr = server.accept()

while True:
data = sock.recv(1024)
print(data.decode('utf-8'))
re_data = input('请输入信息:\n')
sock.send(re_data.encode('utf-8'))
1
2
3
4
5
6
7
8
9
10
11
from socket import *
BUFSIZ = 1024

udpServerSock = socket(AF_INET,SOCK_DGRAM)
udpServerSock.bind(('localhost',10086))

while True:
data,addr = udpServerSock.recvfrom(BUFSIZ)
udpServerSock.sendto('aaa')

udpServerSock.close()
  • 因为数据报套接字是无连接的,所以就没有为了成功通信而使一个客户端连接到一个独立的套接字“转换”的操作
  • UDP服务器仅仅接受消息并有可能回复数据。

84 创建 UDP 客户端