python学习笔记(十八)正则表达式初入门

分布式进程

在Thread和Process中,应当优选Process,因为Process更稳定,而且Process可以分布到多台机器上,而Thread最多只能分不到同一台机器的多个CPU上。
Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分配到其它多个进程中,依靠网络通信。由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。

例:有一个通过Queue通信的多进程程序在同一台机器上运行,现在由于处理人物的进程任务繁重,希望把发送任务的进程和处理任务的进程分布到两台机器上,怎么用分布式进程实现?
原有的Queue可以继续使用,但通过managers模块吧Queue通过网络暴露出去,就可以让其他机器的进程访问Queue了。
我们先看服务进程,服务进程负责启动Queue,把Queue注册到网络上,然后往Queue连写入任务:

import random,time,queue
from multiprocessing.managers import BaseManager
#发送任务的队列
task_queue = queue.Queue()
#接受结果的队列
result_queue = queue.Queue()
#从BaseManager继承的QueueManager:
class QueueManager(BaseManager):
    pass
#把两个Queue都注册到网络上,callable参数关联了Queue对象:
QueueManager.register('get_task_queue' , callable = lambda:task_queue)
QueueManager.register('get_result_queue', callable = lambda:result_queue)
#绑定端口5000,设置验证码‘abc'
manager = QueueManager(address=('', 5000), authkey=b'abc')
#启动Queue
manager.start()
#获得通过网络访问的Queue对象
task = manager.get_task_queue()
result = manager.get_result_queue()
#放几个任务进去
for i in range(10):
    n = random.randint(0, 10000)
    print('Put task %d...' % n)
    task.put(n)
#从result队列读取结果
print('Try get results...')
for i in range(10):
    r = result.get(timeout=10)
#关闭
manager.shutdown()
print('master exit')

当我们在 一台机器上写多进程程序时,创建的Queue可以直接拿来用,但是在分布式多进程环境下,添加任务到Queue不可以直接对原始的task_queue进行操作,那样就绕过了QueueManager的封装,必须通过manager.get_task_queue()获得的Queue接口添加。
然后在另一台机器启动任务进程 (本机也行):

import time, sys, queue
from multiprocessing.managers import  BaseManager

#创建类似的QueueManager:
class QueueManager(BaseManager):
    pass
#由于这个QueueManager只从网络上获取Queue,所以注册时只提供名字:
QueueManager.register('get_task_queue')
QueueManager.register('get_result_queue')

#连接到服务器,也就是运行task_master.py的机器:
server_addr = '127.0.0.1'
print('Connect to server %s...' % server_addr)
#端口和验证码注意保持与task_master.py设置的完全一致:
m = QueueManager(address=(server_addr, 5000), authkey=b'abc')
#从网络连接
m.connect()
#获取Queue的对象
task = m.get_task_queue()
result = m.get_result_queue()
#从task队列领取任务,并把结果写去result队列
for i in range(10):
    try:
        n = task.get(timeout=1)
        print('run task %d  *  %d...' % (n, n))
        r = '%d  * %d = %d' % (n, n, n*n)
        time.sleep(1)
        result.put(r)
    except Queue.Empty:
        print('task queue is empty')
print('worker exit')
结果:
Result: 3411 * 3411 = 11634921
Result: 1605 * 1605 = 2576025
Result: 1398 * 1398 = 1954404
Result: 4729 * 4729 = 22363441
Result: 5300 * 5300 = 28090000
Result: 7471 * 7471 = 55815841
Result: 68 * 68 = 4624
Result: 4219 * 4219 = 17799961
Result: 339 * 339 = 114921
Result: 7866 * 7866 = 61873956

这就是一个 分布式计算,把代码稍加 改造,启动多个worker,就可以把任务分布到几台甚至几十台机器上,比如把计算n*n的代码换成发送永健 ,就实现了邮件队列的异步发送。
Queue对象存储在哪? 注意到task_woker.py中 根本没有创建Queue的代码,所以,Queue对象存储在task_master.py进程中。
而Queue之所以能通过网络访问,就是通过QueueManager实现的。由于QueueManager管理的不止一个Queue,所以要给每个Queue的网络调用接口起个名字,比如get_task_queue。
authkey有什么用?这是为了保证两台机器正常通信,不被其他机器恶意干扰。如果task_worker.py的authkey和task_master.py的authkey不一致,肯定连接不上。

正则表达式

字符串是变成是涉及最多的数据结构 ,对字符串进行操作的需求无处不在。比如判断一个字符串是否合法的Email地址,虽然可以编程提取@前后的字串,再分别判断是否是 单词和域名,但这样做不但麻烦,而且代码难以复用。
正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了 ,否则该字符串就是不合法的。
所以我们判断一个字符串是否合法的Email的方法是:
1.创建一个匹配Email的正则表达式;
2.用该正则表达式去匹配用户的 输入来判断是否合法。
因为正则表达式也是用字符串表示的,所以我们首先要了解如何使用字符来描述字符。
在正则表达式中,如果直接给出字符,就是精确匹配。可以用\d匹配一个数字,\w可以匹配一个字母或数字
例:
'00\d'可以匹配'007',但无法匹配'00A'
'\d\d\d'可以匹配'010'
'\w\w\d'可以匹配'py3'
.可以匹配任意字符,所以:
'py.'可以匹配'pyc''pyo''py!'等等。
要匹配变长的字符,在 正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:
来看一个复杂的例子:
\d{3}\s+\d{3,8}
1.\d{3}表示匹配 3个数字 ,例如‘010‘
2.\s可以匹配 一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如 匹配‘ ’‘ ’等;
3.\d{3,8}表示3-8个 数字,例如‘1234567’
综合起来 ,上面的正则表达式可以匹配 任意个空格隔开的带区号的电话号码。
如果要匹配‘010-12345’这样的号码呢? 由于‘-’是特殊字符,在正则表达式中,要用‘\’转义,所以,上面的正则是\d{3}-\d{3,8}。
但是,任然 无法匹配‘010 - 12345’,因为带有空格。所以我们需要更复杂的匹配方式。
进阶
要做更精确地匹配,可以用[]表示范围,比如;
[0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;
[0-9a-zA-Z\_]+可以匹配至少由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
[a-zA-Z\_][0-9a-zA-Z\_]{0 , 19}更精确地限制了变量的长度是1-20个字符(前面 1个字符+后面 最多19个字符)。
A|B可以匹配A或B,所以(P|p)ython可以匹配‘Python’或者‘python’
^表示行的开头,^\d表示必须以数字开头 。
$表示行的结束,\d$表示必须以数字结束。
你可能注意到了 ,py也可以匹配‘python’,但是加上^py$就变成了整行匹配,就只能匹配 ‘py’了。

re模块
python提供re模块,包含所有正则表达式的功能。由于Python的字符串本身也用\转义,所以要特别注意:

s = 'ABC\\-001' # Python的字符串
# 对应的正则表达式字符串变成:
# 'ABC\-001'

强烈建议使用Python的r前缀,就不用考虑转义的问题了:

s = r'ABC\-001' # Python的字符串
# 对应的正则表达式字符串不变:
# 'ABC\-001'

例:判断正则表达式是否匹配

import re
i = re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
print(i)
i = re.match(r'^\d{3}\-\d{3,8}$', '010 12345')
print(i)
结果:
<re.Match object; span=(0, 9), match='010-12345'>
None

match()判断是否匹配,匹配成功,返回Match对象,匹配失败返回None。常见的判断方法就是:

test = '用户输入的字符串'
if re.match(r'正则表达式', test):
    print('ok')
else:
    print('failed')

切分字符串
用正则表达式切分字符串比用固定的字符更灵活,请看普通的切分代码:

print('a b   c'.split(' '))
['a', 'b', '', '', 'c']

嗯,无法 识别连续的空格,用正则表达式试试:

import re
print(re.split(r'\s+', 'a b     c'))
['a', 'b', 'c']

无论多少个空格都可以正常分割,加入,呢:

print(re.split(r'[\s\,]+','a, b,   c   d'))
['a', 'b', 'c', 'd']

再加入;试试:

print(re.split(r'[\s\,\;]+', 'a, b;;c  d'))
['a', 'b', 'c', 'd']

如果用户 输入了一组标签,下次记得用正则表达式吧不规范的输入转化成正确的数组。
分组
除了简单地判断是否匹配之外,正则表达式还有提取字串的强大功能。用()表示的就是要提取的分组(Group)。比如:
^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:

m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
print(m)
print(m.group(0))
print(m.group(1))
print(m.group(2))
结果:
<re.Match object; span=(0, 9), match='010-12345'>
010-12345
010
12345

如果正则表达式中定义了组,就可以在Match对象上用group()方法提取出字串来。
注意到group(0)永远是原始字符串,group(1)group (2)表示1、2、。。个字符串。
提取字串非常有用 。来看一个更凶残的例子 :

t = '19:05:30'
m = re.match(r'^(0[0-9]|1[0-9]|2[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t)
print(m.groups())

这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
'^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$'
对于’2-30’,'4-31’这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。

贪婪匹配
正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
例:匹配出数字后面的0

print(re.match(r'^(\d+)(0*)$', '102300').groups())
结果 :('102300', '')

由于\d+采用贪婪匹配,直接把后面的0全部匹配 了,结果0*只能 匹配空字符串了。
必须让\d+采用非贪婪匹配(尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:

print(re.match(r'^(\d+?)(0*)$', '102300').groups())
结果:('1023', '00')

编译
当我们在Python 中使用正则表达式时,re模块 内部会干两件事情:
1.编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
2.用编译后的正则表达式去匹配字符串。
如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要 编译这个步骤了,直接匹配:

import re
#编译
re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
#使用
print(re_telephone.match('010-12345').groups())
print(re_telephone.match('010-8086').groups())

编译后生成Regular Expression对象,由于该对象自己包含了 正则表达式,所以调用对应的方法时不用给出正则字符串。

你可能感兴趣的:(python)