第五章-补充2(常用模块)

第五章 - 函数、模块、包

5.15 常用模块


5.15.1 collections模块


在内置数据类型(dict、list、set、tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter、deque、defaultdict、namedtuple和OrderedDict等。

  1. namedtuple: 生成可以使用名字来访问元素内容的tuple

  2. deque: 双端队列,可以快速的从另外一侧追加和推出对象

  3. Counter: 计数器,主要用来计数

  4. OrderedDict: 有序字典

  5. defaultdict: 带有默认值的字典

  • namedtuple

用法

from collections import namedtuple

namedtuple('name',[属性])

实例

from collections import namedtuple

Point = namedtuple('Point',['x','y'])
p = Point(1,2)

print(p.x)  # 1
print(p.y)  # 2
  • deque

collections.deque返回一个新的双向队列对象,从左到右初始化(用方法 append()) ,从 iterable (迭代对象) 数据创建。如果 iterable 没有指定,新队列为空。

collections.deque队列支持线程安全,对于从两端添加(append)或者弹出(pop),复杂度O(1)。

虽然list对象也支持类似操作,但是这里优化了定长操作(pop(0)、insert(0,v))的开销。

如果 maxlen 没有指定或者是 None ,deques 可以增长到任意长度。否则,deque就限定到指定最大长度。一旦限定长度的deque满了,当新项加入时,同样数量的项就从另一端弹出。

# 支持的方法:
append(x):添加x到右端

appendleft(x):添加x到左端

clear():清楚所有元素,长度变为0

copy():创建一份浅拷贝

count(x):计算队列中个数等于x的元素

extend(iterable):在队列右侧添加iterable中的元素

extendleft(iterable):在队列左侧添加iterable中的元素,注:在左侧添加时,iterable参数的顺序将会反过来添加

index(x[,start[,stop]]):返回第 x 个元素(从 start 开始计算,在 stop 之前)。返回第一个匹配,如果没找到的话,升起 ValueError 。

insert(i,x):在位置 i 插入 x 。注:如果插入会导致一个限长deque超出长度 maxlen 的话,就升起一个 IndexError 。

pop():移除最右侧的元素

popleft():移除最左侧的元素

remove(value):移去找到的第一个 value。没有抛出ValueError

reverse():将deque逆序排列。返回 None 。

maxlen:队列的最大长度,没有限定则为None。

使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。

deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:

from collections import deque

q = deque(['a','b','c'])

q.append('d')
q.appendleft('e')

print(q)
# deque(['e', 'a', 'b', 'c', 'd'])

deque除了实现list的append()pop()外,还支持appendleft()popleft(),这样就可以非常高效地往头部添加或删除元素。

  • OrderedDict

使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。

如果要保持Key的顺序,可以用OrderedDict:

from collections import OrderedDict

d = dict([('a',1),('b',2),('c',4)])
print(d)
# {'a': 1, 'b': 2, 'c': 4}

od = OrderedDict([('a',1),('b',2),('c',4)])
print(od)
OrderedDict([('a', 1), ('b', 2), ('c', 4)])

OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:

from collections import OrderedDict
od = OrderedDict()

od['z'] = 1
od['y'] = 2
od['x'] = 3

od.keys() # 按照插入的Key的顺序返回
# ['z', 'y', 'x']
  • defaultdict

为字典的没有的key提供一个默认的值。参数应该是一个函数,当没有参数调用时返回默认值。如果没有传递任何内容,则默认为None。

将集合 [11,22,33,44,55,66,77,88,99,90...],所有大于 66 的值保存至字典的第一个key中,小于 66 的值保存至第二个key的值中。

即: {'k1': [大于66] , 'k2': [小于66]}

使用正常字典方式则需要判断key是否存在如果不存在则要先添加key:

lst = [11,22,33,44,55,77,88,99,90]
dict1 = {}

for row in lst:
    if row > 66:
        if 'key1' not in dict1:
            dict1['key1'] = []
        dict1['key1'].append(row)
    else:
        if 'key2' not in dict1:
            dict1['key2'] = []
        dict1['key2'].append(row)

print(dict1)

而使用defaultdict则不需要

from collections import defaultdict

values = [11, 22, 33,44,55,66,77,88,99,90]

my_dict = defaultdict(list)

for value in  values:
    if value>66:
        my_dict['key1'].append(value)
    else:
        my_dict['key2'].append(value)

print(my_dict)
# defaultdict(<class 'list'>, {'key2': [11, 22, 33, 44, 55, 66], 'key1': [77, 88, 99, 90]})

使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:

from collections import defaultdict
lst = defaultdict(lambda: 'N/A')

lst['key1'] = 'abc'

print(lst['key1']) # key1存在
# abc
print(lst['key2']) # key2不存在,返回默认值
# 'N/A'
  • Counter

Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。Counter类和其他语言的bagsmultisets很相似。

from collections import Counter

c = Counter('abcdeabcdabcaba')

print(c)
# Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1})

Counter类的创建

from collections import Counter

c = Counter()  # 创建一个空的Counter类

c = Counter('gallahad')  # 从一个可iterable对象(list、tuple、dict、字符串等)创建

c = Counter({'a': 4, 'b': 2})  # 从一个字典对象创建

c = Counter(a=4, b=2)  # 从一组键值对创建

计数值的访问与缺失的键

c = Counter("abcdefgab")

print(c["a"])
# 2

print(c["c"])
# 1

print(c["h"])
# 0

计数器的更新(update和subtract)

可以使用一个iterable对象或者另一个Counter对象来更新键值。

计数器的更新包括增加和减少两种。其中,增加使用update()方法:

c = Counter('which')
c.update('witch')  # 使用另一个iterable对象更新

print(c['h'])
# 3

d = Counter('watch')
c.update(d)  # 使用另一个Counter对象更新

print(c['h'])
# 4

减少则使用subtract()方法:

计数器的更新(subtract)

c = Counter('which')
c.subtract('witch')  # 使用另一个iterable对象更新
print(c['h'])
# 1

d = Counter('watch')
c.subtract(d)  # 使用另一个Counter对象更新
print(c['a'])
# -1

键的修改和删除

当计数值为0时,并不意味着元素被删除,删除元素应当使用del。

键的删除

c = Counter("abcdcba")

print(c)
# Counter({'a': 2, 'c': 2, 'b': 2, 'd': 1})

c["b"] = 0
print(c)
# Counter({'a': 2, 'c': 2, 'd': 1, 'b': 0})

del c["a"]
print(c)
# Counter({'c': 2, 'b': 2, 'd': 1})

elements()

返回一个迭代器。元素被重复了多少次,在该迭代器中就包含多少个该元素。元素排列无确定顺序,个数小于1的元素不被包含。
elements()方法。

c = Counter(a=4, b=2, c=0, d=-2)

print(list(c.elements()))
# ['a', 'a', 'a', 'a', 'b', 'b']

most_common([n])

返回一个TopN列表。如果n没有被指定,则返回所有元素。当多个元素计数值相同时,排列是无确定顺序的。

most_common()方法

c = Counter('abracadabra')
print(c.most_common())
# [('a', 5), ('r', 2), ('b', 2), ('c', 1), ('d', 1)]

print(c.most_common(3))
# [('a', 5), ('r', 2), ('b', 2)] 

浅拷贝copy

c = Counter("abcdcba")
print(c)
# Counter({'a': 2, 'c': 2, 'b': 2, 'd': 1})

d = c.copy()
print(d)
# Counter({'a': 2, 'c': 2, 'b': 2, 'd': 1})

算术和集合操作

+-&|操作也可以用于Counter。其中&和|操作分别返回两个Counter对象各元素的最小值和最大值。需要注意的是,得到的Counter对象将删除小于1的元素。

Counter对象的算术和集合操作

c = Counter(a=3, b=1)
d = Counter(a=1, b=2)

print(c + d)
# c[x] + d[x]
# Counter({'a': 4, 'b': 3})

print(c - d)
# subtract(只保留正数计数的元素)
# Counter({'a': 2})

print(c & d)
# 交集:  min(c[x], d[x])
# Counter({'a': 1, 'b': 1})

print(c | d)
# 并集:  max(c[x], d[x])
# Counter({'a': 3, 'b': 2})

其他常用操作

下面是一些Counter类的常用操作,来源于Python官方文档
Counter类常用操作

sum(c.values())  # 所有计数的总数
c.clear()  # 重置Counter对象,注意不是删除
list(c)  # 将c中的键转为列表
set(c)  # 将c中的键转为set
dict(c)  # 将c中的键值对转为字典
c.items()  # 转为(elem, cnt)格式的列表
Counter(dict(list_of_pairs))  # 从(elem, cnt)格式的列表转换为Counter类对象
c.most_common()[:-n:-1]  # 取出计数最少的n个元素
c += Counter()  # 移除0和负值



5.15.2 时间模块

  • 日期和时间

与时间相关的操作,我们就需要使用到时间模块。

常用方法

import time

time.sleep()  # 推迟指定时间运行,单位为秒

time.time()  # 获取当前时间戳

在Python中,通常有这三种方式来表示时间:时间戳、元组(struct_time)、格式化的时间字符串:

  1. 时间戳(timestamp) :

通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。我们运行“type(time.time())”,返回的是float类型。

import time

print(time.time())
# 1681209459.990895
  1. 格式化的时间字符串(Format String): ‘1999-12-06’
python中时间日期格式化符号:

%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00=59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身
import time

print(time.strftime("%Y-%m-%d %x"))
# 2023-04-11 04/11/23

print(time.strftime("%Y-%m-%d %H-%M-%S"))
# 2023-04-11 10-40-23
  1. 元组(struct_time) :

struct_time元组共有9个元素共九个元素:(年,月,日,时,分,秒,一年中第几周,一年中第几天等)

索引(Index) 属性(Attribute) 值(Values)
0 tm_year(年) 比如2011
1 tm_mon(月) 1 - 12
2 tm_mday(日) 1 - 31
3 tm_hour(时) 0 - 23
4 tm_min(分) 0 - 59
5 tm_sec(秒) 0 - 60
6 tm_wday(weekday) 0 - 6(0表示周一)
7 tm_yday(一年中的第几天) 1 - 366
8 tm_isdst(是否是夏令时) 默认为0
import time

t = time.localtime()

print(t)
# time.struct_time(tm_year=2023, tm_mon=4, tm_mday=11, tm_hour=10, tm_min=44, tm_sec=24, tm_wday=1, tm_yday=101, tm_isdst=0)

时间戳是计算机能够识别的时间;时间字符串是人能够看懂的时间;元组则是用来操作时间的

时间格式的转换

  • 时间戳-->结构化时间
# time.gmtime(时间戳)    
# # UTC时间,与英国伦敦当地时间一致

# time.localtime(时间戳) 

# 当地时间。例如我们现在在北京执行这个方法:与UTC时间相差8小时,UTC时间+8小时 = 北京时间 

import time

print(time.time())

print(time.gmtime(1681210288))
# time.struct_time(tm_year=2023, tm_mon=4, tm_mday=11, tm_hour=10, tm_min=51, tm_sec=28, tm_wday=1, tm_yday=101, tm_isdst=0)

print(time.localtime(1681210288))
# time.struct_time(tm_year=2023, tm_mon=4, tm_mday=11, tm_hour=10, tm_min=51, tm_sec=28, tm_wday=1, tm_yday=101, tm_isdst=0)
  • 结构化时间-->时间戳
# time.mktime(结构化时间)
import time

time_tuple = time.localtime()

print(time.mktime(time_tuple))
# 1681210505.0

  • 结构化时间-->字符串时间
# time.strftime("格式定义","结构化时间")  
# 结构化时间参数若不传,则显示当前时间
import time

print(time.strftime("%Y-%m-%d %X"))
# 2023-04-11 10:57:01

print(time.strftime("%Y-%m-%d",time.localtime(1600000000)))
# 2020-09-13

字符串时间-->结构化时间

#time.strptime(时间字符串,字符串对应格式)
import time

print(time.strptime("2022-03-16","%Y-%m-%d"))
# time.struct_time(tm_year=2022, tm_mon=3, tm_mday=16, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=2, tm_yday=75, tm_isdst=-1)

print(time.strptime("07/24/2023","%m/%d/%Y"))
# time.struct_time(tm_year=2023, tm_mon=7, tm_mday=24, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=205, tm_isdst=-1)

  • 结构化时间 --> %a %b %d %H:%M:%S %Y串
#time.asctime(结构化时间) 如果不传参数,直接返回当前时间的格式化串
import time

t = time.asctime(time.localtime(1650000000))
print(t)
# Fri Apr 15 05:20:00 2022

t2 = time.asctime()
print(t2)
# Tue Apr 11 11:02:07 2023
  • 时间戳 --> %a %d %d %H:%M:%S %Y串
#time.ctime(时间戳)  如果不传参数,直接返回当前时间的格式化串
import time

t1 = time.ctime()
print(t1)
# Tue Apr 11 11:05:15 2023
t2 = time.ctime(1657800000)
print(t2)
# Thu Jul 14 12:00:00 2022

t = time.time()
ft = time.ctime(t)
print(ft)
# Tue Apr 11 11:05:37 2023

st = time.localtime()
ft = time.asctime(st)
print(ft)
# Tue Apr 11 11:05:37 2023
  • time方法总结

1. time.altzone

返回格林威治西部的夏令时地区的偏移秒数。如果该地区在格林威治东部会返回负值(如西欧,包括英国)。对夏令时启用地区才能使用。

以下实例展示了 altzone()函数的使用方法:

import time
print ("time.altzone %d " % time.altzone)
# time.altzone 0 

2. time.asctime([tupletime])

接受时间元组并返回一个可读的形式为"Tue Dec 11 18:07:14 2008"(2008年12月11日 周二18时07分14秒)的24个字符的字符串。

以下实例展示了 asctime()函数的使用方法:

import time
t = time.localtime()
print ("time.asctime(t): %s " % time.asctime(t))
# time.asctime(t): Thu Apr  7 10:36:20 2016 

3. time.clock()

用以浮点数计算的秒数返回当前的CPU时间。用来衡量不同程序的耗时,比time.time()更有用。

import time

def procedure():
    time.sleep(2.5)

# time.clock
t0 = time.clock()
procedure()
print (time.clock() - t0)

# time.time
t0 = time.time()
procedure()
print (time.time() - t0)

4. time.ctime([secs])

作用相当于asctime(localtime(secs)),未给参数相当于asctime()

以下实例展示了 ctime()函数的使用方法:

import time
print ("time.ctime() : %s" % time.ctime())
# time.ctime() : Thu Apr  7 10:51:58 2016

5. time.gmtime([secs])

接收时间辍(1970纪元后经过的浮点秒数)并返回格林威治天文时间下的时间元组t。注:t.tm_isdst始终为0

以下实例展示了 gmtime()函数的使用方法:

import time
print ("gmtime :", time.gmtime(1455508609.34375))
# gmtime : time.struct_time(tm_year=2016, tm_mon=2, tm_mday=15, tm_hour=3, tm_min=56, tm_sec=49, tm_wday=0, tm_yday=46, tm_isdst=0)

6. time.localtime([secs]

接收时间辍(1970纪元后经过的浮点秒数)并返回当地时间下的时间元组t(t.tm_isdst可取0或1,取决于当地当时是不是夏令时)。

以下实例展示了 localtime()函数的使用方法:

import time
print ("localtime(): ", time.localtime(1455508609.34375))
# localtime():  time.struct_time(tm_year=2016, tm_mon=2, tm_mday=15, tm_hour=11, tm_min=56, tm_sec=49, tm_wday=0, tm_yday=46, tm_isdst=0)

7. time.mktime(tupletime)

time.mktime() 函数执行与gmtime(), localtime()相反的操作,它接收struct_time对象作为参数,返回用秒数来表示时间的浮点数。

如果输入的值不是一个合法的时间,将触发 OverflowError 或 ValueError。

接受时间元组并返回时间辍(1970纪元后经过的浮点秒数)。

import time

t = (2016, 2, 17, 17, 3, 38, 1, 48, 0)
secs = time.mktime( t )

print ("time.mktime(t) : %f" %  secs)
print ("asctime(localtime(secs)): %s" % time.asctime(time.localtime(secs)))

8. time.sleep(secs)

推迟调用线程的运行,secs指秒数。

以下实例展示了 sleep()函数的使用方法:

import time

print ("Start : %s" % time.ctime())
time.sleep( 5 )
print ("End : %s" % time.ctime())

9. time.strftime(fmt[,tupletime])

接收以时间元组,并返回以可读字符串表示的当地时间,格式由fmt决定。

以下实例展示了 strftime()函数的使用方法:

import time
print (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
# 2016-04-07 11:18:05

10. time.strptime(str,fmt='%a %b %d %H:%M:%S %Y')

根据fmt的格式把一个时间字符串解析为时间元组。

以下实例展示了 strptime()函数的使用方法:

import time
struct_time = time.strptime("30 Nov 00", "%d %b %y")
print ("返回元组: ", struct_time)
# 返回元组:  time.struct_time(tm_year=2000, tm_mon=11, tm_mday=30, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=335, tm_isdst=-1)

11. time.time( )

返回当前时间的时间戳(1970纪元后经过的浮点秒数)。

以下实例展示了 time()函数的使用方法:

import time
print(time.time())
# 1459999336.1963577

12. time.tzset()

根据环境变量TZ重新初始化时间相关设置。

import time
import os

os.environ['TZ'] = 'EST+05EDT,M4.1.0,M10.5.0'
time.tzset()
print (time.strftime('%X %x %Z'))

os.environ['TZ'] = 'AEST-10AEDT-11,M10.5.0,M3.5.0'
time.tzset()
print (time.strftime('%X %x %Z'))
  • 日历(Calendar)模块

此模块的函数都是日历相关的,例如打印某月的字符月历。

星期一是默认的每周第一天,星期天是默认的最后一天。更改设置需调用calendar.setfirstweekday() 函数。模块包含了以下内置函数:

1. calendar.calendar(year,w=2,l=1,c=6)

返回一个多行字符串格式的 year 年年历,3 个月一行,间隔距离为 c。 每日宽度间隔为 w 字符。每行长度为 21* W+18+2* C。l 是每星期行数。

2. calendar.firstweekday( )

返回当前每周起始日期的设置。默认情况下,首次载入 calendar 模块时返回 0,即星期一。

3. calendar.isleap(year)

是闰年返回 True,否则为 False。

4. calendar.leapdays(y1,y2)

返回在 Y1,Y2 两年之间的闰年总数。

5. calendar.month(year,month,w=2,l=1)

返回一个多行字符串格式的 year 年 month 月日历,两行标题,一周一行。每日宽度间隔为 w 字符。每行的长度为 7* w+6。l 是每星期的行数。

6. calendar.monthcalendar(year,month)

返回一个整数的单层嵌套列表。每个子列表装载代表一个星期的整数。Year 年 month 月外的日期都设为 0;范围内的日子都由该月第几日表示,从 1 开始。

7. calendar.monthrange(year,month)

返回两个整数。第一个是该月的星期几的日期码,第二个是该月的日期码。日从 0(星期一)到 6 (星期日);月从 1 到 12。

8. calendar.prcal(year,w=2,l=1,c=6)

相当于 print(calendar.calendar(year,w,l,c)) .

9. calendar.prmonth(year,month,w=2,l=1)

相当于 print calendar.calendar(year,w,l,c)。

10. calendar.setfirstweekday(weekday)

设置每周的起始日期码。0(星期一)到 6(星期日)。

11. calendar.timegm(tupletime)

和 time.gmtime 相反:接受一个时间元组形式,返回该时刻的时间辍(1970 纪元后经过的浮点秒数)。

12. calendar.weekday(year,month,day)

返回给定日期的日期码。0(星期一)到6(星期日)。月份为 1(一月) 到 12(12月)。

  • datetime模块

datetime模块供应类操作日期和时间。日期和时间的算术支持时,实施的焦点是有效的输出格式和属性提取操作。

1.datetime.now() 获取当前datetime

datetime.utcnow() 获取当前格林威治时间

from datetime import datetime

#获取当前本地时间
a=datetime.now()
print('当前日期:',a)
# 当前日期: 2023-04-11 11:47:03.572452

#获取当前世界时间
b=datetime.utcnow()
print('世界时间:',b)
# 世界时间: 2023-04-11 11:47:03.572473

2.datetime(2023, 4, 11, 12, 20)用指定日期时间创建datetime

from datetime import datetime

#用指定日期创建
c=datetime(2023, 4, 11, 12, 20)
print('指定日期:',c)

3.将以下字符串转换成datetime类型:

'2017/9/30'

'2017年9月30日星期六'

'2017年9月30日星期六8时42分24秒'

'9/30/2017'

'9/30/2017 8:42:50 '

from datetime import datetime

d=datetime.strptime('2017/9/30','%Y/%m/%d')
print(d)

e=datetime.strptime('2017年9月30日星期六','%Y年%m月%d日星期六')
print(e)

f=datetime.strptime('2017年9月30日星期六8时42分24秒','%Y年%m月%d日星期六%H时%M分%S秒')
print(f)

g=datetime.strptime('9/30/2017','%m/%d/%Y')
print(g)

h=datetime.strptime('9/30/2017 8:42:50 ','%m/%d/%Y %H:%M:%S ')
print(h)

4.将以下datetime类型转换成字符串:

2017年9月28日星期4,10时3分43秒

Saturday, September 30, 2017

9/30/2017 9:22:17 AM

September 30, 2017

from datetime import datetime

i=datetime(2017,9,28,10,3,43)
print(i.strftime('%Y年%m月%d日%A,%H时%M分%S秒'))

j=datetime(2017,9,30,10,3,43)
print(j.strftime('%A,%B %d,%Y'))

k=datetime(2017,9,30,9,22,17)
print(k.strftime('%m/%d/%Y %I:%M:%S%p'))

l=datetime(2017,9,30)
print(l.strftime('%B %d,%Y'))

5.用系统时间输出以下字符串:

今天是2017年9月30日

今天是这周的第?天

今天是今年的第?天

今周是今年的第?周

今天是当月的第?天

from datetime import datetime

#获取当前系统时间
m=datetime.now()
print(m.strftime('今天是%Y年%m月%d日'))
print(m.strftime('今天是这周的第%w天'))
print(m.strftime('今天是今年的第%j天'))
print(m.strftime('今周是今年的第%W周'))
print(m.strftime('今天是当月的第%d天'))



5.15.3 random随机数模块

random 模块方法

方法 描述
seed() 初始化随机数生成器
getstate() 返回捕获生成器当前内部状态的对象。
setstate() state 应该是从之前调用 getstate() 获得的,并且 setstate() 将生成器的内部状态恢复到 getstate() 被调用时的状态。
getrandbits(k) 返回具有 k 个随机比特位的非负 Python 整数。 此方法随 MersenneTwister 生成器一起提供,其他一些生成器也可能将其作为 API 的可选部分提供。 在可能的情况下,getrandbits() 会启用 randrange() 来处理任意大的区间。
randrange() 从 range(start, stop, step) 返回一个随机选择的元素。
randint(a, b) 返回随机整数 N 满足 a <= N <= b。
choice(seq) 从非空序列 seq 返回一个随机元素。 如果 seq 为空,则引发 IndexError。
choices(population, weights=None, *, cum_weights=None, k=1) 从 population 中选择替换,返回大小为 k 的元素列表。 如果 population 为空,则引发 IndexError。
shuffle(x[, random]) 将序列 x 随机打乱位置。
sample(population, k, *, counts=None) 返回从总体序列或集合中选择的唯一元素的 k 长度列表。 用于无重复的随机抽样。
random() 返回 [0.0, 1.0) 范围内的下一个随机浮点数。
uniform() 返回一个随机浮点数 N ,当 a <= b 时 a <= N <= b ,当 b < a 时 b <= N <= a 。
triangular(low, high, mode) 返回一个随机浮点数 N ,使得 low <= N <= high 并在这些边界之间使用指定的 mode 。 low 和 high 边界默认为零和一。 mode 参数默认为边界之间的中点,给出对称分布。
betavariate(alpha, beta) Beta 分布。 参数的条件是 alpha > 0 和 beta > 0。 返回值的范围介于 0 和 1 之间。
expovariate(lambd) 指数分布。 lambd 是 1.0 除以所需的平均值,它应该是非零的。
gammavariate() Gamma 分布( 不是伽马函数) 参数的条件是 alpha > 0 和 beta > 0。
gauss(mu, sigma) 正态分布,也称高斯分布。 mu 为平均值,而 sigma 为标准差。 此函数要稍快于下面所定义的 normalvariate() 函数。
lognormvariate(mu, sigma) 对数正态分布。 如果你采用这个分布的自然对数,你将得到一个正态分布,平均值为 mu 和标准差为 sigma 。 mu 可以是任何值,sigma 必须大于零。
normalvariate(mu, sigma) 正态分布。 mu 是平均值,sigma 是标准差。
vonmisesvariate(mu, kappa) 冯·米塞斯分布。 mu 是平均角度,以弧度表示,介于0和 2pi 之间,kappa 是浓度参数,必须大于或等于零。 如果 kappa 等于零,则该分布在 0 到 2pi 的范围内减小到均匀的随机角度。
paretovariate(alpha) 帕累托分布。 alpha 是形状参数。
weibullvariate(alpha, beta) 威布尔分布。 alpha 是比例参数,beta 是形状参数。
import random

#随机小数
print(random.random())      # 大于0且小于1之间的小数
# 0.7664338663654585

print(random.uniform(1,3)) #大于1小于3的小数
1.6270147180533838
#恒富:发红包

#随机整数
# 大于等于1且小于等于5之间的整数
print(random.randint(1,5)) 

# 大于等于1且小于10之间的奇数
print(random.randrange(1,10,2))


#随机选择一个返回
# #1或者23或者[4,5]
print(random.choice([1,'23',[4,5]]))

#随机选择多个返回,返回的个数为函数的第二个参数
# 列表元素任意2个组合
print(random.sample([1,'23',[4,5]],2)) 
# [[4, 5], '23']


#打乱列表顺序
item=[1,3,5,7,9]
random.shuffle(item) # 打乱次序
print(item)
# [5, 1, 3, 7, 9]

random.shuffle(item)
print(item)
# [5, 9, 7, 1, 3]

生成随机验证码

import random

def v_code():

    code = ''
    for i in range(5):

        num=random.randint(0,9)
        alf=chr(random.randint(65,90))
        add=random.choice([num,alf])
        code="".join([code,str(add)])

    return code

print(v_code())



5.15.4 os模块

os 模块提供了非常丰富的方法用来处理文件和目录。常用的方法如下表所示:

  • 目录路径相关
方法 描述
os.chdir(path) 改变当前工作目录。
os.fchdir(fd) 通过文件描述符改变当前工作目录。
os.getcwd() 返回当前工作目录。
os.getcwdu() 返回一个当前工作目录的 Unicode 对象。
os.curdir 返回当前目录: ('.')
os.pardir 获取当前目录的父目录字符串名:('..')
  • 文件夹相关
方法 描述
os.mkdir(path[, mode]) 以数字 mode 的 mode 创建一个名为 path 的文件夹。默认的 mode 是 0777 (八进制)。
os.makedirs(path[, mode]) 递归文件夹创建函数。像 mkdir(), 但创建的所有 intermediate-level 文件夹需要包含子文件夹。
os.removedirs(path) 递归删除目录。
os.rmdir(path) 删除 path 指定的空目录,如果目录非空,则抛出一个 OSError 异常。
os.renames(old, new) 递归地对目录进行更名,也可以对文件进行更名。
os.listdir(path) 返回 path 指定的文件夹包含的文件或文件夹的名字的列表。
  • 文件相关
方法 描述
os.open(file, flags[, mode]) 打开一个文件,并且设置需要的打开选项,mode 参数是可选的。
os.close(fd) 关闭文件描述符 fd。
os.rename(src, dst) 重命名文件或目录,从 src 到 dst。
os.remove(path) 删除路径为 path 的文件。如果 path 是一个文件夹,将抛出 OSError; 查看下面的 rmdir() 删除一个 directory。
os.stat(path) 获取 path 指定的路径的信息,功能等同于 C API 中的 stat() 系统调用。
os.closerange(fd_low, fd_high) 关闭所有文件描述符,从 fd_low (包含) 到 fd_high (不包含), 错误会忽略。
os.dup(fd) 复制文件描述符 fd。
os.dup2(fd, fd2) 将一个文件描述符 fd 复制到另一个 fd2。
os.fstat(fd) 返回文件描述符 fd 的状态,像 stat()。
os.read(fd, n) 从文件描述符 fd 中读取最多 n 个字节,返回包含读取字节的字符串,文件描述符 fd 对应文件已达到结尾,返回一个空字符串。
  • 操作系统差异相关
方法 描述
os.access(path, mode) 检验权限模式。
os.chflags(path, flags) 设置路径的标记为数字标记。
os.chmod(path, mode) 更改权限。
os.chown(path, uid, gid) 更改文件所有者。
os.chroot(path) 改变当前进程的根目录。
os.fchmod(fd, mode) 改变一个文件的访问权限,该文件由参数 fd 指定,参数 mode 是 Unix 下的文件访问权限。
os.fchown(fd, uid, gid) 修改一个文件的所有权,这个函数修改一个文件的用户 ID 和用户组 ID,该文件由文件描述符 fd 指定。
os.fdatasync(fd) 强制将文件写入磁盘,该文件由文件描述符 fd 指定,但是不强制更新文件的状态信息。
os.fdopen(fd[, mode[, bufsize]]) 通过文件描述符 fd 创建一个文件对象,并返回这个文件对象。
os.fpathconf(fd, name) 返回一个打开的文件的系统配置信息。name 为检索的系统配置的值,它也许是一个定义系统值的字符串,这些名字在很多标准中指定(POSIX.1, Unix 95, Unix 98, 和其它)。
os.fstatvfs(fd) 返回包含文件描述符 fd 的文件的文件系统的信息,像 statvfs()。
os.fsync(fd) 强制将文件描述符为 fd 的文件写入硬盘。
os.ftruncate(fd, length) 裁剪文件描述符 fd 对应的文件, 所以它最大不能超过文件大小。
os.isatty(fd) 如果文件描述符 fd 是打开的,同时与 tty(-like) 设备相连,则返回 true, 否则 False。
os.lchflags(path, flags) 设置路径的标记为数字标记,类似 chflags(),但是没有软链接。
os.lchmod(path, mode) 修改连接文件权限。
os.lchown(path, uid, gid) 更改文件所有者,类似 chown,但是不追踪链接。
os.link(src, dst) 创建硬链接,名为参数 dst,指向参数 src。
os.lseek(fd, pos, how) 设置文件描述符 fd 当前位置为 pos, how 方式修改: SEEK_SET 或者 0 设置从文件开始的计算的 pos; SEEK_CUR 或者 1 则从当前位置计算;os.SEEK_END 或者 2 则从文件尾部开始。在 unix,Windows 中有效。
os.lstat(path) 像 stat(),但是没有软链接。
os.major(device) 从原始的设备号中提取设备 major 号码 (使用 stat 中的 st_dev 或者 st_rdev field)。
os.makedev(major, minor) 以 major 和 minor 设备号组成一个原始设备号。
os.minor(device) 从原始的设备号中提取设备 minor 号码 (使用 stat 中的 st_dev 或者 st_rdev field )。
os.mkfifo(path[, mode]) 创建命名管道,mode 为数字,默认为 0666 (八进制)。
os.mknod(filename[, mode=0600, device]) 创建一个名为 filename 文件系统节点(文件,设备特别文件或者命名 pipe)。
os.openpty() 打开一个新的伪终端对。返回 pty 和 tty 的文件描述符。
os.pathconf(path, name) 返回相关文件的系统配置信息。
os.pipe() 创建一个管道. 返回一对文件描述符 (r, w) 分别为读和写
os.popen(command[, mode[, bufsize]]) 从一个 command 打开一个管道。
os.readlink(path) 返回软链接所指向的文件。
os.stat_float_times([newvalue]) 决定 stat_result 是否以 float 对象显示时间戳。
os.statvfs(path) 获取指定路径的文件系统统计信息。
os.symlink(src, dst) 创建一个软链接。
os.tcgetpgrp(fd) 返回与终端 fd(一个由 os.open() 返回的打开的文件描述符)关联的进程组。
os.tcsetpgrp(fd, pg) 设置与终端 fd(一个由 os.open() 返回的打开的文件描述符)关联的进程组为 pg。
os.tempnam([dir[, prefix]]) 返回唯一的路径名用于创建临时文件。
os.tmpfile() 返回一个打开的模式为 (w+b) 的文件对象,这文件对象没有文件夹入口,没有文件描述符,将会自动删除。
os.tmpnam() 为创建一个临时文件返回一个唯一的路径。
os.ttyname(fd) 返回一个字符串,它表示与文件描述符 fd 关联的终端设备。如果 fd 没有与终端设备关联,则引发一个异常。
os.unlink(path) 删除文件路径。
os.utime(path, times) 返回指定的 path 文件的访问和修改的时间。
os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]]) 输出在文件夹中的文件名通过在树中游走,向上或者向下。
os.write(fd, str) 写入字符串到文件描述符 fd 中,返回实际写入的字符串长度
  • path系列,和路径相关
方法 描述
os.path.abspath(path) 返回path规范化的绝对路径
os.path.split(path) 将path分割成目录和文件名二元组返回
os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值,即os.path.split(path)的第二个元素。
os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path) 如果path是绝对路径,返回True
os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.getatime(path) 返回path所指向的文件或者目录的最后访问时间
os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间
os.path.getsize(path) 返回path的大小



5.15.5 sys模块

sys模块是与python解释器交互的一个接口

方法 描述
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.exit(n) 退出程序,正常退出时exit(0),错误退出sys.exit(1)
sys.version 获取Python解释程序的版本信息
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
import sys

try:
    sys.exit(1)
except SystemExit as e:
    print(e)

5.15.6 序列化模块

序列化的本质就是将一种数据结构(如字典、列表)等转换成一个特殊的序列(字符串或者bytes)的过程就叫做序列化。

序列化模块就是将一个常见的数据结构转化成一个特殊的序列,并且这个特殊的序列还可以反解回去。它的主要用途:文件读写数据,网络传输数据。

1、序列化的目的

  1. 以某种存储形式使自定义对象持久化;
    内存是无法永久保存数据的,当程序运行了一段时间,我们断点或者重启程序,内存中关于这个程序的之前一段时间的数据都被清空了。但是在在断电或重启程序之前将程序当前内存中所有的数据都保存在文件中,以便下次程序执行能够从文件中载入之前的数据,然后继续执行。
  1. 将对象从一个地方传递到另一个地方。
    序列化时不仅可以吧序列化后的内容写入磁盘,还可以通过网络传输到别的机器上,如果收发双方约定好使用一种序列化的格式,那边打破了平台、语言差异化带来的限制,实现了跨平台数据交互。
  1. 使程序更具维护性。

2、Python中这种序列化模块有三种:

json模块:

  • 不同语言都遵循的一种数据转化格式,即不同语言都使用的特殊字符串。(比如Python的一个列表[1, 2, 3]利用json转化成特殊的字符串,然后在编码成bytes发送给php的开发者,php的开发者就可以解码成特殊的字符串,然后在反解成原数组(列表): [1, 2, 3])

  • json序列化只支持部分Python数据结构:dict,list, tuple,str,int, float,True,False,None

pickle模块:

  • 只能是Python语言遵循的一种数据转化格式,只能在python语言中使用。

  • 支持Python所有的数据类型包括实例化对象。

shelve模块:

  • 类似于字典的操作方式去操作特殊的字符串。

3、json模块

json模块是将满足条件的数据结构转化成特殊的字符串,并且也可以反序列化还原回去。

序列化模块总共只有两种用法,要不就是用于网络传输的中间环节,要不就是文件存储的中间环节,所以json模块总共就有两对四个方法:

用于网络传输:dumps、loads
用于文件写读:dump、load
  • dumps, loads
import json

dic = {'k1':'v1','k2':'v2','k3':'v3'}

#序列化:将一个字典转换成一个字符串
str_dic = json.dumps(dic) 

print(type(str_dic),str_dic)  
#<class 'str'> {"k3": "v3", "k1": "v1", "k2": "v2"}

#注意,json转换完的字符串类型的字典中的字符串是由""表示的

​#反序列化:将一个字符串格式的字典转换成一个字典
dic2 = json.loads(str_dic)  
#注意,要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示
print(type(dic2),dic2)  
#<class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
import json

list_dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}]

# 也可以处理嵌套的数据类型 
str_dic = json.dumps(list_dic) 

print(type(str_dic),str_dic) 
#<class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}]

list_dic2 = json.loads(str_dic)

print(type(list_dic2),list_dic2) 
#<class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
  • dump, load
import json

f = open('json_file.json','w')

dic = {'k1':'v1','k2':'v2','k3':'v3'}

# dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件
json.dump(dic,f)  

f.close()

# json文件也是文件,就是专门存储json字符串的文件。
f = open('json_file.json')

# load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回
dic2 = json.load(f)  

f.close()

print(type(dic2),dic2)

json序列化存储多个数据到同一个文件中

对于json序列化,存储多个数据到一个文件中是有问题的,默认一个json文件只能存储一个json数据,但是也可以解决,

dic1 = {'name':'oldboy1'}
dic2 = {'name':'oldboy2'}
dic3 = {'name':'oldboy3'}

f = open('序列化',encoding='utf-8',mode='a')

str1 = json.dumps(dic1)
f.write(str1+'\n')

str2 = json.dumps(dic2)
f.write(str2+'\n')

str3 = json.dumps(dic3)
f.write(str3+'\n')

f.close()
​
f = open('序列化',encoding='utf-8')

for line in f:
    print(json.loads(line))

4、pickle模块

pickle模块是将Python所有的数据结构以及对象等转化成bytes类型,然后还可以反序列化还原回去。

pickle只是Python语言使用,它支持Python所有的数据类型包括后面我们要讲的实例化对象等,它能将这些所有的数据结构序列化成特殊的bytes,然后还可以反序列化还原。使用上与json几乎差不多,也是两对四个方法。

用于网络传输:dumps、loads
用于文件写读:dump、load
  • dumps, loads
import pickle

dic = {'k1':'v1','k2':'v2','k3':'v3'}

str_dic = pickle.dumps(dic)
print(str_dic)  # bytes类型
​
dic2 = pickle.loads(str_dic)
print(dic2)    #字典​
  • dump, load
dic = {(1,2):'zhangsan',1:True,'set':{1,2,3}}
f = open('pick序列化',mode='wb')
pickle.dump(dic,f)
f.close()
with open('pick序列化',mode='wb') as f1:
    pickle.dump(dic,f1)

​pickle序列化存储多个数据到一个文件中

dic1 = {'name':'zhnagsan'}
dic2 = {'name':'lisi'}
dic3 = {'name':'wangwu'}
​
f = open('pick多数据',mode='wb')
pickle.dump(dic1,f)
pickle.dump(dic2,f)
pickle.dump(dic3,f)
f.close()
​
f = open('pick多数据',mode='rb')
while True:
    try:
        print(pickle.load(f))
    except EOFError:
        break
f.close()

既然pickle如此强大,为什么还要学json呢?这里我们要说明一下,json是一种所有的语言都可以识别的数据结构。如果我们将一个字典或者序列化成了一个json存在文件里,那么java代码或者js代码也可以拿来用。但是如果我们用pickle进行序列化,其他语言就不能读懂这是什么了。

所以,如果你序列化的内容是列表或者字典,我们非常推荐你使用json模块,但如果出于某种原因你不得不序列化其他的数据类型,而未来你还会用python对这个数据进行反序列化的话,那么就可以使用pickle。

5、json与pickle的不同

json序列化除了可以解决写入文件的问题,还可以解决网络传输的问题,比如你将一个list数据结构通过网络传给另个开发者,那么你不可以直接传输,之前我们说过,你要想传输出去必须用bytes类型。但是bytes类型只能与字符串类型互相转化,它不能与其他数据结构直接转化,所以,你只能将list ---> 字符串 ---> bytes 然后发送,对方收到之后,在decode() 解码成原字符串。

此时这个字符串不能是我们之前学过的str那种字符串,因为它不能反解,必须要是这个特殊的字符串,他可以反解成list 这样开发者之间就可以借助网络互传数据了,不仅仅是开发者之间,你要借助网络爬取数据这些数据多半是这种特殊的字符串,你接受到之后,在反解成你需要的数据类型。




5.15.7 re模块

re模块是正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。re 模块使 Python 语言拥有全部的正则表达式功能。

1. 正则表达式介绍

现有一个要求:需要验证手机号的合法性。

根据手机号码一共11位并且是只以13、14、15、18开头的数字这些特点,我们用python写了如下代码:

while True:
    phone_number = input('please input your phone number : ')
    if len(phone_number) == 11 \
            and phone_number.isdigit()\
            and (phone_number.startswith('13') \
            or phone_number.startswith('14') \
            or phone_number.startswith('15') \
            or phone_number.startswith('18')):
        print('是合法的手机号码')
    else:
        print('不是合法的手机号码')

上述方法会是的判断条件非常的多,下面使用正则表达式来进行合法性检验:

import re

phone_number = input('please input your phone number : ')

if re.match('^(13|14|15|18)[0-9]{9}$',phone_number):
        print('是合法的手机号码')
else:
        print('不是合法的手机号码')

其实正则表达式与Python没有关系,正则表达式只是一种匹配字符串内容的规则。

官方定义:正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。


  • 为什么使用正则表达式?

典型的搜索和替换操作要求您提供与预期的搜索结果匹配的确切文本。虽然这种技术对于对静态文本执行简单搜索和替换任务可能已经足够了,但它缺乏灵活性,若采用这种方法搜索动态文本,即使不是不可能,至少也会变得很困难。

通过使用正则表达式,可以:

1、测试字符串内的模式。
例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。

2、替换文本。
可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。

3、基于模式匹配从字符串中提取子字符串。
可以查找文档内或输入域内特定的文本。

例如,需要搜索整个网站,删除过时的材料,以及替换某些 HTML 格式标记。在这种情况下,可以使用正则表达式来确定在每个文件中是否出现该材料或该 HTML 格式标记。此过程将受影响的文件列表缩小到包含需要删除或更改的材料的那些文件。然后可以使用正则表达式来删除过时的材料。最后,可以使用正则表达式来搜索和替换标记。

正则表达式在线测试工具:

http://tool.chinaz.com/regex/

https://c.runoob.com/front-end/854/


2. 正则表达式语法

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

  • 字符组

字符组 : [字符组]

在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示,字符分为很多类,比如数字、字母、标点等等。

假如你现在要求一个位置"只能出现一个数字",那么这个位置上的字符只能是0、1、2...9这10个数之一。

正则 说明 示例
[0123456789] 在一个字符组里枚举合法的所有字符,字符组里的任意一个字符和"待匹配字符"相同都视为可以匹配,则该位置上数字可以匹配返回True,字母或者其他字符均返回False 例如 :'[0123456789]'可以匹配‘hello01’中的‘0’和‘1’。
[0-9] 也可以用 - 表示范围,[0-9]就和[0123456789]是一个意思 例如 :'[0-9]'可以匹配‘hello01’中的‘h’、‘e’、‘l’、‘l’、‘o’。
[a-z] 字符范围。同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示 例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。
[A-Z] [A-Z]就表示所有的大写字母 例如 :'[A-Z]'可以匹配‘Hello01’中的‘H’。
[0-9a-zA-Z] 表示匹配所有的数字,小写字母和大写字母 例如 :'[0-9a-zA-Z]'可以匹配‘@Hello01’中的‘Hello01’。
[0-9a-fA-F] 可以匹配数字,大小写形式的a~f,用来验证十六进制字符 例如 :'[A-Z]'可以匹配‘Hello01’中的‘H’。
[xyz] 字符集合。匹配所包含的任意一个字符。 例如, '[abc]' 可以匹配 "plain" 中的 'a'。
[^xyz] 负值字符集合。匹配未包含的任意字符。 例如, '[^abc]' 可以匹配 "plain" 中的'p'、'l'、'i'、'n'。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。 例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范围内的任意字符。
  • 元字符
字符 描述
\d 匹配一个数字字符。等价于 [0-9]。
\D 匹配一个非数字字符。等价于 [^0-9]。
\w 匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。
\W 匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'。
. 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用像"(.|\n)"的模式。
  • 定位符
字符 描述
^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
  • 非打印字符
字符 描述
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\f 匹配一个换页符。等价于 \x0c 和 \cL。
\n 匹配一个换行符。等价于 \x0a 和 \cJ。
\r 匹配一个回车符。等价于 \x0d 和 \cM。
\t 匹配一个制表符。等价于 \x09 和 \cI。
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。
  • 量词(限定字符)
字符 描述 示例
* 匹配前面的子表达式零次或多次。 例如 :zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。 例如 :'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。 例如 :"do(es)?" 可以匹配 "do" 或 "does" 。? 等价于 {0,1}。
n 是一个非负整数。匹配确定的 n 次。 例如 :'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
n 是一个非负整数。至少匹配n 次。 例如 :'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。 例如 :"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。
x|y 匹配 x 或 y。 例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。

*+ 限定符都是贪婪的,因为它们会尽可能多的匹配文字,只有在它们的后面加上一个 ? 就可以实现非贪婪或最小匹配。

贪婪:下面的表达式匹配从开始小于符号 (<) 到关闭 h1 标记的大于符号 (>) 之间的所有内容。

非贪婪:如果您只需要匹配开始和结束 h1 标签,下面的非贪婪表达式只匹配 <h1>


  • 转义字符
字符 描述 示例
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。 例如,'n' 匹配字符 "n"。'\n' 匹配一个换行符。序列 '\' 匹配 "" 而 "(" 则匹配 "("。

字符 描述
\cx 匹配由 x 指明的控制字符。
例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。
否则,将 c 视为一个原义的 'c' 字符。
\xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。
例如,'\x41' 匹配 "A"。'\x041' 则等价于 '\x04' & "1"。正则表达式中可以使用 ASCII 编码。
\num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。
例如,'(.)\1' 匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。
如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。
否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。
如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。
如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。
如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。
\nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。
\un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。
例如, \u00A9 匹配版权符号 (?)。

  • 分组()

用圆括号 () 将所有选择项括起来,相邻的选择项之间用 | 分隔。

() 表示捕获分组,() 会把每个分组里的匹配的值保存起来, 多个匹配值可以通过数字 n 来查看(n 是一个数字,表示第 n 个捕获组的内容)。

但用圆括号会有一个副作用,使相关的匹配会被缓存,此时可用 ?: 放在第一个选项前来消除这种副作用。

其中 ?: 是非捕获元之一,还有两个非捕获元是 ?=?!,这两个还有更多的含义,前者为正向预查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串,后者为负向预查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。

对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 \n 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

可以使用非捕获元字符 ?:?=?! 来重写捕获,忽略对相关匹配的保存。

字符 描述 示例
(pattern) 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '(' 或 ')'。 例如 :([1]\d{13,16}[0-9x]$) , 匹配一个正确身份证。
(?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。 例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。
(?=pattern) 正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。 例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。 例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?<=pattern) 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。 例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。
(?<!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。 例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。

3. 正则表达式的先行断言(lookahead)和后行断言(lookbehind)

正则表达式的先行断言和后行断言一共有 4 种形式:

(?=pattern) 零宽正向先行断言(zero-width positive lookahead assertion)
(?!pattern) 零宽负向先行断言(zero-width negative lookahead assertion)
(?<=pattern) 零宽正向后行断言(zero-width positive lookbehind assertion)
(?<!pattern) 零宽负向后行断言(zero-width negative lookbehind assertion)

这里面的 pattern 是一个正则表达式。

如同 ^ 代表开头,$ 代表结尾,\b 代表单词边界一样,先行断言和后行断言也有类似的作用,它们只匹配某些位置,在匹配过程中,不占用字符,所以被称为"零宽"。所谓位置,是指字符串中(每行)第一个字符的左边、最后一个字符的右边以及相邻字符的中间(假设文字方向是头左尾右)。

下面分别举例来说明这 4 种断言的含义。

  • (?=pattern) 正向先行断言

代表字符串中的一个位置,紧接该位置之后的字符序列能够匹配 pattern。

例如:对 "a regular expression" 这个字符串,要想匹配 'regular' 中的 're',但不能匹配 'expression' 中的 're',可以用 re(?=gular),该表达式限定了 're' 右边的位置,这个位置之后是 'gular',但并不消耗 'gular' 这些字符。

将表达式改为 re(?=gular).,将会匹配 'reg',元字符' . '匹配了 'g',括号这一砣匹配了 'e' 和 'g' 之间的位置。

  • (?!pattern) 负向先行断言

代表字符串中的一个位置,紧接该位置之后的字符序列不能匹配 pattern。

例如对 "regex represents regular expression" 这个字符串,要想匹配除 'regex' 和 'regular' 之外的 're',可以用 re(?!g),该表达式限定了 're' 右边的位置,这个位置后面不是字符 'g'。

负向和正向的区别,就在于该位置之后的字符能否匹配括号中的表达式。

  • (?<=pattern) 正向后行断言

代表字符串中的一个位置,紧接该位置之前的字符序列能够匹配 pattern。

例如:对 "regex represents regular expression" 这个字符串,有 4 个单词,要想匹配单词内部的 're',但不匹配单词开头的 're',可以用 (?<=\w)re,单词内部的 're',在 're' 前面应该是一个单词字符。

之所以叫后行断言,是因为正则表达式引擎在匹配字符串和表达式时,是从前向后逐个扫描字符串中的字符,并判断是否与表达式符合,当在表达式中遇到该断言时,正则表达式引擎需要往字符串前端检测已扫描过的字符,相对于扫描方向是向后的。

  • (?<!pattern) 负向后行断言

代表字符串中的一个位置,紧接该位置之前的字符序列不能匹配 pattern。

例如对 "regex represents regular expression" 这个字符串,要想匹配单词开头的 're',可以用 (?<!\w)re。单词开头的 're',在本例中,也就是指不在单词内部的 're',即 're' 前面不是单词字符。当然也可以用 \bre 来匹配。

对于这 4 个断言的理解,可以从两个方面入手:

1. 关于先行(lookahead)和后行(lookbehind):
    正则表达式引擎在执行字符串和表达式匹配时,会从头到尾(从前到后)连续扫描字符串中的字符,设想有一个扫描指针指向字符边界处并随匹配过程移动。先行断言,是当扫描指针位于某处时,引擎会尝试匹配指针还未扫过的字符,先于指针到达该字符,故称为先行。后行断言,引擎会尝试匹配指针已扫过的字符,后于指针到达该字符,故称为后行。

2. 关于正向(positive)和负向(negative):
    正向就表示匹配括号中的表达式,负向表示不匹配。

对这 4 个断言形式的记忆:

1. 先行和后行:
    后行断言 (?<=pattern)、(?<!pattern) 中,有个小于号,同时也是箭头,对于自左至右的文本方向,这个箭头是指向后的,这也比较符合我们的习惯。把小于号去掉,就是先行断言。

2. 正向和负向:
    不等于 (!=)、逻辑非 (!) 都是用 !号来表示,所以有 ! 号的形式表示不匹配、负向;将 ! 号换成 = 号,就表示匹配、正向。

我们经常用正则表达式来检测一个字符串中包含某个子串,要表示一个字符串中不包含某个字符或某些字符也很容易,用 [^...] 形式就可以了。要表示一个字符串中不包含某个子串(由字符序列构成)呢?

[^...] 这种形式就不行了,这时就要用到(负向)先行断言或后行断言、或同时使用。

例如判断一句话中包含 this,但不包含 that。

包含 this 比较好办,一句话中不包含 that,可以认为这句话中每个字符的前面都不是 that 或每个字符的后面都不是 that。正则表达式如下:

^((?<!that).)*this((?<!that).)*$
或 
^(.(?!that))*this(.(?!that))*$

对于 this is runoob test 这句话,两个表达式都能够匹配成功,而 this and that is runoob test 都匹配失败。

在一般情况下,这两个表达式基本上都能够满足要求了。考虑极端情况,如一句话以 that 开头、以 that 结尾、that 和 this 连在一起时,上述表达式就可能不胜任了。 如 runoob thatthis is the case 或者 this is the case, not that 等。

只要灵活运用这几个断言,就很容易解决:

^(.(?<!that))*this(.(?<!that))*$
^(.(?<!that))*this((?!that).)*$
^((?!that).)*this(.(?<!that))*$
^((?!that).)*this((?!that).)*$

这 4 个正则表达式测试上述的几句话,结果都能够满足要求。

上述 4 种断言,括号里的 pattern 本身是一个正则表达式。但对 2 种后行断言有所限制,在 Perl 和 Python 中,这个表达式必须是定长(fixed length)的,即不能使用 、+、? 等元字符,如 (?<=abc) 没有问题,但 (?<=abc) 是不被支持的,特别是当表达式中含有|连接的分支时,各个分支的长度必须相同。之所以不支持变长表达式,是因为当引擎检查后行断言时,无法确定要回溯多少步。Java 支持 ?、{m}、{n,m} 等符号,但同样不支持 *、+ 字符。Javascript 干脆不支持后行断言,不过一般来说,这不是太大的问题。

先行断言和后行断言某种程度上就好比使用 if 语句对匹配的字符前后做判断验证。

  • 以下列出 ?=、?<=、?!、?<!= 的使用
exp1(?=exp2):查找 exp2 前面的 exp1。

(?<=exp2)exp1:查找 exp2 后面的 exp1。

exp1(?!exp2):查找后面不是 exp2 的 exp1。

(?<!=exp2)exp1:查找前面不是 exp2 的 exp1。

4. 修饰符(标记)

标记也称为修饰符,正则表达式的标记用于指定额外的匹配策略。

标记不写在正则表达式里,标记位于表达式之外,格式如下:

修饰符 含义 描述
i ignore - 不区分大小写 将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。
g global - 全局匹配 查找所有的匹配项。
m multi line - 多行匹配 使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。
s 特殊字符圆点 . 中包含换行符 \n 默认情况下的圆点 . 是匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。

5. 贪婪模式

贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配

字符 描述 示例
? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。 例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。

几个常用的非贪婪匹配Pattern

字符 描述
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

.*?的用法

. 是任意字符
* 是取 0 至 无限长度
? 是非贪婪模式。

何在一起就是 取尽量少的任意字符,一般不会这么单独写,他大多用在:

.*?x 
就是取前面任意长度的字符,直到一个x出现

6. re常用方法

  • re.match 函数

re.match() 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。

函数语法:

re.match(pattern, string, flags=0)
函数参数说明:
pattern - 匹配的正则表达式
string  - 要匹配的字符串。
flags   - 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

匹配成功re.match方法返回一个匹配的对象,否则返回None。

import re
print(re.match('www', 'www.runoob.com').span())  # 在起始位置匹配
print(re.match('com', 'www.runoob.com'))         # 不在起始位置匹配

我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。

匹配对象方法 描述
group(num=0) 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。
groups() 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。
import re
 
line = "Cats are smarter than dogs"
# .* 表示任意匹配除换行符(\n、\r)之外的任何单个或多个字符
# (.*?) 表示"非贪婪"模式,只保存第一个匹配到的子串
matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I)
 
if matchObj:
   print ("matchObj.group() : ", matchObj.group())
   print ("matchObj.group(1) : ", matchObj.group(1))
   print ("matchObj.group(2) : ", matchObj.group(2))
else:
   print ("No match!!")
import re

ret = re.match('a', 'abc').group()  # 同search,不过尽在字符串开始处进行匹配
print(ret)
#结果 : 'a'
  • re.search方法

re.search 扫描整个字符串并返回第一个成功的匹配。

函数语法:

re.search(pattern, string, flags=0)

函数参数说明:

pattern - 匹配的正则表达式
string  - 要匹配的字符串。
flags   - 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

匹配成功re.search方法返回一个匹配的对象,否则返回None

我们可以使用group(num)groups() 匹配对象函数来获取匹配表达式。

import re

# 在起始位置匹配
print(re.search('www', 'www.runoob.com').span())  
# (0, 3)
 
# 不在起始位置匹配
print(re.search('com', 'www.runoob.com').span())         
# (11, 14)
import re
 
line = "Cats are smarter than dogs"
 
searchObj = re.search( r'(.*) are (.*?) .*', line, re.M|re.I)
 
if searchObj:
   print ("searchObj.group() : ", searchObj.group())
   print ("searchObj.group(1) : ", searchObj.group(1))
   print ("searchObj.group(2) : ", searchObj.group(2))
else:
   print ("Nothing found!!")

# searchObj.group() :  Cats are smarter than dogs
# searchObj.group(1) :  Cats
# searchObj.group(2) :  smarter
import re

ret = re.search('a', 'eva egon yuan').group()
print(ret) #结果 : 'a'

# 函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以
# 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

re.matchre.search 的区别

re.match 只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回 None,而 re.search 匹配整个字符串,直到找到一个匹配。

import re
 
line = "Cats are smarter than dogs"
 
matchObj = re.match( r'dogs', line, re.M|re.I)
if matchObj:
   print ("match --> matchObj.group() : ", matchObj.group())
else:
   print ("No match!!")
 
matchObj = re.search( r'dogs', line, re.M|re.I)
if matchObj:
   print ("search --> matchObj.group() : ", matchObj.group())
else:
   print ("No match!!")

# No match!!
# search --> matchObj.group() :  dogs
  • re.sub(检索和替换)

re模块提供了re.sub用于替换字符串中的匹配项。

语法:

re.sub(pattern, repl, string, count=0, flags=0)

参数:

前三个为必选参数,后两个为可选参数。
pattern : 正则中的模式字符串。
repl : 替换的字符串,也可为一个函数。
string : 要被查找替换的原始字符串。
count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
flags : 编译时用的匹配模式,数字形式。
import re
 
phone = "2004-959-559 # 这是一个电话号码"
 
# 删除注释
num = re.sub(r'#.*$', "", phone)
print ("电话号码 : ", num)
 
# 移除非数字的内容
num = re.sub(r'\D', "", phone)
print ("电话号码 : ", num)

# 电话号码 :  2004-959-559 
# 电话号码 :  2004959559

repl 参数是一个函数

以下实例中将字符串中的匹配的数字乘以 2:

import re
 
# 将匹配的数字乘以 2
def double(matched):
    value = int(matched.group('value'))
    return str(value * 2)
 
s = 'A23G4HFD567'
print(re.sub('(?P<value>\d+)', double, s))

# A46G8HFD1134
import re

#将数字替换成'H',参数1表示只替换1个
ret = re.sub('\d', 'H', 'eva3egon4yuan4', 1)
print(ret) #evaHegon4yuan4

#将数字替换成'H',返回元组(替换的结果,替换了多少次)
ret = re.subn('\d', 'H', 'eva3egon4yuan4')
print(ret)
  • compile 函数

compile() 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。

语法格式为:

re.compile(pattern[, flags])
pattern : 一个字符串形式的正则表达式

flags 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:
    re.I 忽略大小写
    re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
    re.M 多行模式
    re.S 即为' . '并且包括换行符在内的任意字符(' . '不包括换行符)
    re.U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库
    re.X 为了增加可读性,忽略空格和' # '后面的注释
import re

pattern = re.compile(r'\d+')                    # 用于匹配至少一个数字
m = pattern.match('one12twothree34four')        # 查找头部,没有匹配
print( m )
# None

m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配,没有匹配
print( m )
# None

m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配
print( m )                                        # 返回一个 Match 对象
# <_sre.SRE_Match object at 0x10a42aac0>

print(m.group(0))   # 可省略 0
# '12'
print(m.start(0))   # 可省略 0
# 3
print(m.end(0))     # 可省略 0
# 5
print(m.span(0))    # 可省略 0
# (3, 5)

在上面,当匹配成功时返回一个 Match 对象,其中:

group([group1, …]) 
    方法用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用 group() 或 group(0);

start([group]) 
    方法用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0;

end([group]) 
    方法用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0;

span([group]) 
    方法返回 (start(group), end(group))。
import re

pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I)   # re.I 表示忽略大小写

m = pattern.match('Hello World Wide Web')

print( m )    # 匹配成功,返回一个 Match 对象
# <_sre.SRE_Match object at 0x10bea83e8>

print(m.group(0))      # 返回匹配成功的整个子串
# 'Hello World'

print(m.span(0))        # 返回匹配成功的整个子串的索引
# (0, 11)

print(m.group(1))    # 返回第一个分组匹配成功的子串
# 'Hello'

print(m.span(1))    # 返回第一个分组匹配成功的子串的索引
# (0, 5)

print(m.group(2))    # 返回第二个分组匹配成功的子串
# 'World'

print(m.span(2))    # 返回第二个分组匹配成功的子串索引
# (6, 11)

print(m.groups())    # 等价于 (m.group(1), m.group(2), ...)
# ('Hello', 'World')

print(m.group(3))  # 不存在第三个分组
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# IndexError: no such group
  • findall

在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果有多个匹配模式,则返回元组列表,如果没有找到匹配的,则返回空列表。

注意: match 和 search 是匹配一次 findall 匹配所有。

语法格式为:

re.findall(pattern, string, flags=0)
或
pattern.findall(string[, pos[, endpos]])

参数:

pattern 匹配模式。
string 待匹配的字符串。
pos 可选参数,指定字符串的起始位置,默认为 0。
endpos 可选参数,指定字符串的结束位置,默认为字符串的长度。

查找字符串中的所有数字:

import re
 
result1 = re.findall(r'\d+','runoob 123 google 456')
 
pattern = re.compile(r'\d+')   # 查找数字
result2 = pattern.findall('runoob 123 google 456')
result3 = pattern.findall('run88oob123google456', 0, 10)
 
print(result1)
print(result2)
print(result3)

# 输出结果:
# ['123', '456']
# ['123', '456']
# ['88', '12']

多个匹配模式,返回元组列表:

import re

result = re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10')

print(result)
# [('width', '20'), ('height', '10')]
  • re.finditer

和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。

re.finditer(pattern, string, flags=0)

参数:

pattern - 匹配的正则表达式
string  - 要匹配的字符串。
flags   - 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
import re
 
it = re.finditer(r"\d+","12a32bc43jf3") 
for match in it: 
    print (match.group() )

# 输出结果:
# 12 
# 32 
# 43 
# 3
  • re.split

split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:

re.split(pattern, string[, maxsplit=0, flags=0])

参数:

pattern - 匹配的正则表达式
string  - 要匹配的字符串。
maxsplit- 分割次数,maxsplit=1 分割一次,默认为 0,不限制次数。
flags   - 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
import re

print(re.split('\W+', 'runoob, runoob, runoob.'))
# ['runoob', 'runoob', 'runoob', '']

print(re.split('(\W+)', ' runoob, runoob, runoob.'))
# ['', ' ', 'runoob', ', ', 'runoob', ', ', 'runoob', '.', '']

print(re.split('\W+', ' runoob, runoob, runoob.', 1))
# ['', 'runoob, runoob, runoob.']
 
print(re.split('a*', 'hello world'))   # 对于一个找不到匹配的字符串而言,split 不会对其作出分割
# ['hello world']

7. 正则示例

  • 匹配文件名

点号 . 匹配字符串中的各种打印或非打印字符,除了换行符 \n\r。下面的正则表达式匹配 aac、abc、acc、adc 等等,以及 a1c、a2c、a-c 和 a#c:

/a.c/

若要匹配包含文件名的字符串,而句点 . 是输入字符串的组成部分,请在正则表达式中的句点前面加反斜杠 \ 字符。举例来说明,下面的正则表达式匹配 filename.ext

/filename\.ext/

这些表达式只让您匹配"任何"单个字符。可能需要匹配列表中的特定字符组。例如,可能需要查找用数字表示的章节标题(Chapter 1、Chapter 2 等等)。

  • 用户名的合法性正则表达式

用户名可以包含以下几种字符:

1、26 个大小写英文字母表示为 a-zA-Z。
2、数字表示为 0-9。
3、下划线表示为 _。
4、中划线表示为 -。

用户名由若干个字母、数字、下划线和中划线组成,所以需要用到 + 表示 1 次或多次出现。

根据以上条件得出用户名的表达式可以为:

[a-zA-Z0-9_-]+
str = "abc123-_def";
patt = "/[a-zA-Z0-9_-]+/"
print(str.match(patt))
  • 匹配 HTML 标签及内容

以下正则表达式用于匹配 iframe 标签:

/<iframe(([\s\S])*?)<\/iframe>/

其他标签的匹配可以替换 iframe 。

匹配 id="mydiv" 的 div 标签:

/<div id="mydiv"(([\s\S])*?)<\/div>/

匹配所有 img 标签:

/<img.*?src="(.*?)".*?\/?>/gi
  • 匹配车牌号
# 车牌号正则
cPattern = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/

# 输出 true
print(cPattern.test("京K39006"))
  • 匹配整数
import re

ret=re.findall(r"\d+","1-2*(60+(-40.35/5)-(-4*3))")
print(ret) #['1', '2', '60', '40', '35', '5', '4', '3']

ret=re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))")
print(ret) #['1', '-2', '60', '', '5', '-4', '3']

ret.remove("")
print(ret) #['1', '-2', '60', '5', '-4', '3']



5.15.8 hashlib模块


1. 算法介绍


此模块称为摘要算法,也叫做加密算法,或者是哈希算法,散列算法等等,简单来说hashlib就是做加密和校验使用,它的工作原理:通过一个函数,把任意长度的数据按照一定规则转换为一个固定长度的数据串(通常用16进制的字符串表示)。

比如在网站中我们保存的密码,并不是以明文的形式来保存的,如: 123456 ,通过哈希算法加密后为 e10adc3949ba59abbe56e057f20f883e ,在数据库中是以加密后的形式保存的。

摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。

摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。

hashlib模块就可以完成的就是这个功能。

hashlib的特征以及使用要点:

bytes类型数据 ---> 通过hashlib算法 ---> 固定长度的字符串

不同的bytes类型数据转化成的结果一定不同。

相同的bytes类型数据转化成的结果一定相同。

此转化过程不可逆。

那么刚才我们也说了,hashlib的主要用途有两个:

- 密码的加密。
- 文件一致性校验。

hashlib 模块就相当于一个算法的集合,这里面包含着很多的算法,算法越高,转化成的结果越复杂,安全程度越高,相应的效率就会越低。

2. 密码的加密

  • 普通加密

我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:

import hashlib
​
md5 = hashlib.md5()
md5.update('123456'.encode('utf-8'))
print(md5.hexdigest())
​
# 计算结果如下:
# 'e10adc3949ba59abbe56e057f20f883e'
# 验证:相同的bytes数据转化的结果一定相同
import hashlib
​
md5 = hashlib.md5()
md5.update('123456'.encode('utf-8'))
print(md5.hexdigest())
​
# 计算结果如下:
# 'e10adc3949ba59abbe56e057f20f883e'
# 验证:不相同的bytes数据转化的结果一定不相同
import hashlib
​
md5 = hashlib.md5()
md5.update('12345'.encode('utf-8'))
print(md5.hexdigest())
​
# 计算结果如下:
# '827ccb0eea8a706c4c34a16891f84e7b'

MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:

import hashlib
 
sha1 = hashlib.sha1()

sha1.update('how to use sha1 in ')
sha1.update('python hashlib?')

print(sha1.hexdigest())

SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。

3. 加盐

md5、sha1密级别是最低的,相对来说不很安全。虽然说hashlib加密是不可逆的加密方式,但也是可以破解的。

那么他是如何做的呢?

网上好多MD5解密软件,他们就是用最简单的方式,空间换时间。他们会把常用的一些密码比如:123456,111111,以及他们的md5的值做成对应关系,类似于字典,

dic = {'e10adc3949ba59abbe56e057f20f883e': 123456}

然后通过你的密文获取对应的密码。

只要空间足够大,那么里面容纳的密码会非常多,利用空间换取破解时间。

所以针对刚才说的情况,我们有更安全的加密方式:加盐。

  • 固定的盐

什么叫加盐?

加盐这个词儿来自于国外,外国人起名字我认为很随意,这个名字来源于烧烤,俗称BBQ。我们烧烤的时候,一般在快熟的时候,都会给肉串上面撒盐,增加味道,那么这个撒盐的工序,外国人认为比较复杂,所以就讲比较复杂的加密方式称之为加盐。

其实代码非常简单:

import hashlib

ret = hashlib.md5('xx教育'.encode('utf-8'))  # xx教育就是固定的盐
ret.update('a'.encode('utf-8'))

print(ret.hexdigest())

上面的 xx教育 就是固定的盐,比如你在一家公司,公司会将你们所有的密码在md5之前增加一个固定的盐,这样提高了密码的安全性。

  • 动态的盐

如果黑客通过手段窃取到你这个固定的盐之后,也是可以破解出来的。所以,我们还可以加动态的盐。

import hashlib

username = '张三'

ret = hashlib.md5(username[::2].encode('utf-8'))  # 针对于每个账户,每个账户的盐都不一样
ret.update('a'.encode('utf-8'))

print(ret.hexdigest())

这样,安全性能就大大提高了。

那么我们之前说了hahslib模块是一个算法集合,他里面包含很多种加密算法,刚才我们说的MD5算法是比较常用的一种加密算法,一般的企业用MD5就够用了。但是对安全要求比较高的企业,比如金融行业,MD5加密的方式就不够了,得需要加密方式更高的,比如sha系列,sha1,sha224,sha512等等,数字越大,加密的方法越复杂,安全性越高,但是效率就会越慢。

ret = hashlib.sha1()
ret.update('taibaijinxing'.encode('utf-8'))
print(ret.hexdigest())
​
#也可加盐
ret = hashlib.sha384(b'asfdsa')
ret.update('taibaijinxing'.encode('utf-8'))
print(ret.hexdigest())
​
# 也可以加动态的盐
ret = hashlib.sha384(b'asfdsa'[::2])
ret.update('taibaijinxing'.encode('utf-8'))
print(ret.hexdigest())

4. 文件的一致性校验

hashlib 模块除了可以用于密码加密之外,还有一个常用的功能,那就是文件的一致性校验。

linux讲究:一切皆文件,我们普通的文件,是文件,视频,音频,图片,以及应用程序等都是文件。

我们都从网上下载过资源,比如我们刚开学时让大家从网上下载pycharm这个软件,当时你可能没有注意过,其实你下载的时候都是带一个MD5或者shax值的,为什么? 

我们的网络世界是很不安全的,经常会遇到病毒,木马等,有些你是看不到的可能就植入了你的电脑中,那么他们是怎么来的? 都是通过网络传入来的,就是你在网上下载一些资源的时候,趁虚而入,当然大部分被我们的浏览器或者杀毒软件拦截了,但是还有一部分偷偷的进入你的磁盘中了。

那么我们自己如何验证我们下载的资源是否有病毒呢?这就需要文件的一致性校验了。在我们下载一个软件时,往往都带有一个MD5或者shax值,当我们下载完成这个应用程序时你要是对比大小根本看不出什么问题,你应该对比他们的md5值,如果两个md5值相同,就证明这个应用程序是安全的,如果你下载的这个文件的MD5值与服务端给你提供的不同,那么就证明你这个应用程序肯定是植入病毒了(文件损坏的几率很低),那么你就应该赶紧删除,不应该安装此应用程序。

md5计算的就是bytes类型的数据的转换值,同一个bytes数据用同样的加密方式转化成的结果一定相同,如果不同的bytes数据(即使一个数据只是删除了一个空格)那么用同样的加密方式转化成的结果一定是不同的。所以,hashlib也是验证文件一致性的重要工具。

我将文件校验写在一个函数中

简单版文件校验:

def func(file):
    with open(file,mode='rb') as f1:
        ret = hashlib.md5()
        ret.update(f1.read())
        return ret.hexdigest()
​
​
print(func('hashlib_file1'))

这样就可以计算此文件的MD5值,从而进行文件校验。但是这样写有一个问题,类似我们文件的改的操作,有什么问题?如果文件过大,全部读取出来直接就会撑爆内存的,所以我们要分段读取,那么分段读取怎么做呢?

import hashlib

# 直接 update
md5obj = hashlib.md5()
md5obj.update('太白 is a old driver'.encode('utf-8'))
print(md5obj.hexdigest())  
# 900e328fa53873fb245f418d6942e41b
​
​
# 分段update
md5obj = hashlib.md5()
md5obj.update('太白 '.encode('utf-8'))
md5obj.update('is '.encode('utf-8'))
md5obj.update('a '.encode('utf-8'))
md5obj.update('old '.encode('utf-8'))
md5obj.update('driver'.encode('utf-8'))
print(md5obj.hexdigest())  # 900e328fa53873fb245f418d6942e41b
# 结果相同

那么根据上面的代码,我们自己做一个高大上版的文件校验。

校验此版本的pycharm的sha256值是否相同。

def file_check(file_path):
    with open(file_path,mode='rb') as f1:
        sha256 = hashlib.sha256()
        while 1:
            content = f1.read(1024)
            if content:
                sha256.update(content)
            else:
                return sha256.hexdigest()
print(file_check('pycharm-professional-2019.1.1.exe'))

5. 摘要算法应用

任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:

name    | password
--------+----------
michael | 123456
bob     | abc999
alice   | alice2008

如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:

username | password
---------+---------------------------------
michael  | e10adc3949ba59abbe56e057f20f883e
bob      | 878ef96e86145580c38c87f0410ad153
alice    | 99b1c2188db85afee403b1536010c2c9

考虑这么个情况,很多用户喜欢用123456,888888,password这些简单的口令,于是,黑客可以事先计算出这些常用口令的MD5值,得到一个反推表:

'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'

这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用口令的用户账号。

对于用户来讲,当然不要使用过于简单的口令。但是,我们能否在程序设计上对简单口令加强保护呢?

由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:

hashlib.md5("salt".encode("utf8"))

经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。

但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。有没有办法让使用相同口令的用户存储不同的MD5呢?

如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。

摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。

#=========知识储备==========
#进度条的效果
[#             ]
[##            ]
[###           ]
[####          ]

#指定宽度
print('[%-15s]' %'#')
print('[%-15s]' %'##')
print('[%-15s]' %'###')
print('[%-15s]' %'####')

#打印%
print('%s%%' %(100)) #第二个%号代表取消第一个%的特殊意义

#可传参来控制宽度
print('[%%-%ds]' %50) #[%-50s]
print(('[%%-%ds]' %50) %'#')
print(('[%%-%ds]' %50) %'##')
print(('[%%-%ds]' %50) %'###')


#=========实现打印进度条函数==========
import sys
import time

def progress(percent,width=50):
    if percent >= 1:
        percent=1
    show_str = ('%%-%ds' % width) % (int(width*percent)*'|')
    print('\r%s %d%%' %(show_str, int(100*percent)), end='')


#=========应用==========
data_size=1025
recv_size=0
while recv_size < data_size:
    time.sleep(0.1) #模拟数据的传输延迟
    recv_size+=1024 #每次收1024

    percent=recv_size/data_size #接收的比例
    progress(percent,width=70) #进度条的宽度70



5.15.9 configparse模块


该模块适用于配置文件的格式与windows ini文件类似,可以包含一个或多个节(section),每个节可以有多个参数(键=值)。

1 、创建文件

来看一个好多软件的常见文档格式如下:

[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes
  
[bitbucket.org]
User = hg
  
[topsecret.server.com]
Port = 50022
ForwardX11 = no

如果想用python生成一个这样的文档怎么做呢?

import configparser

config = configparser.ConfigParser()

config["DEFAULT"] = {'ServerAliveInterval': '45',
                      'Compression': 'yes',
                     'CompressionLevel': '9',
                     'ForwardX11':'yes'
                     }

config['bitbucket.org'] = {'User':'hg'}

config['topsecret.server.com'] = {'Host Port':'50022','ForwardX11':'no'}

with open('example.ini', 'w') as configfile:

config.write(configfile)

2、查找文件

import configparser

config = configparser.ConfigParser()

#---------------------------查找文件内容,基于字典的形式

print(config.sections())        #  []

config.read('example.ini')

print(config.sections())        #   ['bitbucket.org', 'topsecret.server.com']

print('bytebong.com' in config) # False
print('bitbucket.org' in config) # True


print(config['bitbucket.org']["user"])  # hg

print(config['DEFAULT']['Compression']) #yes

print(config['topsecret.server.com']['ForwardX11'])  #no

print(config['bitbucket.org'])          #<Section: bitbucket.org>

for key in config['bitbucket.org']:     # 注意,有default会默认default的键
    print(key)

print(config.options('bitbucket.org'))  # 同for循环,找到'bitbucket.org'下所有键

print(config.items('bitbucket.org'))    #找到'bitbucket.org'下所有键值对

print(config.get('bitbucket.org','compression')) # yes       get方法Section下的key对应的value

3、增删改操作

import configparser

config = configparser.ConfigParser()

config.read('example.ini')

config.add_section('yuan')


config.remove_section('bitbucket.org')
config.remove_option('topsecret.server.com',"forwardx11")

config.set('topsecret.server.com','k1','11111')
config.set('yuan','k2','22222')

config.write(open('new2.ini', "w"))



5.15.10 logging模块

1、函数式简单配置

import logging  
logging.debug('debug message')  
logging.info('info message')  
logging.warning('warning message')  
logging.error('error message')  
logging.critical('critical message') 

默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG),默认的日志格式为日志级别:Logger名称:用户输出消息。

灵活配置日志级别,日志格式,输出位置:

import logging

file_handler = logging.FileHandler(filename='x1.log', mode='a', encoding='utf-8',)
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
    handlers=[file_handler,],
    level=logging.ERROR
)

logging.error('你好')

2、日志切割

import time
import logging
from logging import handlers

sh = logging.StreamHandler()
rh = handlers.RotatingFileHandler('myapp.log', maxBytes=1024,backupCount=5)
fh = handlers.TimedRotatingFileHandler(filename='x2.log', when='s', interval=5, encoding='utf-8')
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
    handlers=[fh,sh,rh],
    level=logging.ERROR
)

for i in range(1,100000):
    time.sleep(1)
    logging.error('KeyboardInterrupt error %s'%str(i))

3、配置参数:

logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有:

filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger(后边会讲解具体概念)的日志级别
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。

format参数中可能用到的格式化串:
%(name)s Logger的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s用户输出的消息

4、logger对象配置

import logging

logger = logging.getLogger()
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log',encoding='utf-8') 

# 再创建一个handler,用于输出到控制台 
ch = logging.StreamHandler() 
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

fh.setLevel(logging.DEBUG)

fh.setFormatter(formatter) 
ch.setFormatter(formatter) 

logger.addHandler(fh) #logger对象可以添加多个fh和ch对象 
logger.addHandler(ch) 

logger.debug('logger debug message') 
logger.info('logger info message') 
logger.warning('logger warning message') 
logger.error('logger error message') 
logger.critical('logger critical message')

logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。另外,可以通过:logger.setLevel(logging.Debug)设置级别,当然,也可以通过

fh.setLevel(logging.Debug)单对文件流设置某个级别。

5、logger的配置文件

有的同学习惯通过logger的对象配置去完成日志的功能,没问题,但是上面这种方式需要创建各种对象,比如logger对象,fileHandler对象,ScreamHandler对象等等,比较麻烦,那么下面给你提供一种字典的方式,创建logger配置文件,这种才是工作中经常使用的实现日志功能的方法,真正的做到 ----- 拿来即用(简单改改)。

# logging配置
import os
import logging.config

# 定义三种日志输出格式 开始

standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'

# 定义日志输出格式 结束

logfile_dir = os.path.dirname(os.path.abspath(__file__))  # log文件的目录

logfile_name = 'all2.log'  # log文件名

# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir):
    os.mkdir(logfile_dir)

# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name)

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},
    'handlers': {
        #打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'simple'
        },
        #打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': logfile_path,  # 日志文件
            'maxBytes': 1024*1024*5,  # 日志大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)传递
        },
    },
}


def load_my_logging_cfg():
    logging.config.dictConfig(LOGGING_DIC)  # 导入上面定义的logging配置
    logger = logging.getLogger(__name__)  # 生成一个log实例
    logger.info('It works!')  # 记录该文件的运行状态

if __name__ == '__main__':
    load_my_logging_cfg()
注意注意注意:


#1、有了上述方式我们的好处是:所有与logging模块有关的配置都写到字典中就可以了,更加清晰,方便管理


#2、我们需要解决的问题是:
    1、从字典加载配置:logging.config.dictConfig(settings.LOGGING_DIC)

    2、拿到logger对象来产生日志
    logger对象都是配置到字典的loggers 键对应的子字典中的
    按照我们对logging模块的理解,要想获取某个东西都是通过名字,也就是key来获取的
    于是我们要获取不同的logger对象就是
    logger=logging.getLogger('loggers子字典的key名')

    
    但问题是:如果我们想要不同logger名的logger对象都共用一段配置,那么肯定不能在loggers子字典中定义n个key   
 'loggers': {    
        'l1': {
            'handlers': ['default', 'console'],  #
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)传递
        },
        'l2: {
            'handlers': ['default', 'console' ], 
            'level': 'DEBUG',
            'propagate': False,  # 向上(更高level的logger)传递
        },
        'l3': {
            'handlers': ['default', 'console'],  #
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)传递
        },

}

    
#我们的解决方式是,定义一个空的key
    'loggers': {
        '': {
            'handlers': ['default', 'console'], 
            'level': 'DEBUG',
            'propagate': True, 
        },

}

这样我们再取logger对象时
logging.getLogger(__name__),不同的文件__name__不同,这保证了打印日志时标识信息不同,但是拿着该名字去loggers里找key名时却发现找不到,于是默认使用key=''的配置



5.15.11 shutil模块

高级的 文件、文件夹、压缩包 处理模块

shutil.copyfileobj(fsrc, fdst[, length])
将文件内容拷贝到另一个文件中

import shutil
shutil.copyfileobj(open('old.xml','r'), open('new.xml', 'w'))

shutil.copyfile(src, dst)
拷贝文件

shutil.copyfile('f1.log', 'f2.log') #目标文件无需存在

shutil.copymode(src, dst)
仅拷贝权限。内容、组、用户均不变

shutil.copymode('f1.log', 'f2.log') #目标文件必须存在

shutil.copystat(src, dst)
仅拷贝状态的信息,包括:mode bits, atime, mtime, flags

shutil.copystat('f1.log', 'f2.log') #目标文件必须存在

shutil.copy(src, dst)
拷贝文件和权限

import shutil
shutil.copy('f1.log', 'f2.log')

shutil.copy2(src, dst)
拷贝文件和状态信息

import shutil
shutil.copy2('f1.log', 'f2.log')

shutil.ignore_patterns(*patterns)
shutil.copytree(src, dst, symlinks=False, ignore=None)
递归的去拷贝文件夹

import shutil
shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) #目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除 
# 拷贝软连接
import shutil

shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))

# 通常的拷贝都把软连接拷贝成硬链接,即对待软连接来说,创建新的文件

shutil.rmtree(path[, ignore_errors[, onerror]])
递归的去删除文件

import shutil
shutil.rmtree('folder1')

shutil.move(src, dst)
递归的去移动文件,它类似mv命令,其实就是重命名。

import shutil
shutil.move('folder1', 'folder3')

shutil.make_archive(base_name, format,...)

创建压缩包并返回文件路径,例如:zip、tar

创建压缩包并返回文件路径,例如:zip、tar

- base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,
        如 data_bak => 保存至当前路径
        如:/tmp/data_bak => 保存至/tmp/
- format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”
- root_dir: 要压缩的文件夹路径(默认当前目录)
- owner: 用户,默认当前用户
- group: 组,默认当前组
- logger: 用于记录日志,通常是logging.Logger对象
#将 /data 下的文件打包放置当前程序目录
import shutil
ret = shutil.make_archive("data_bak", 'gztar', root_dir='/data')

#将 /data下的文件打包放置 /tmp/目录
import shutil
ret = shutil.make_archive("/tmp/data_bak", 'gztar', root_dir='/data')

shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的,详细:

# zipfile压缩解压缩

import zipfile

# 压缩
z = zipfile.ZipFile('laxi.zip', 'w')
z.write('a.log')
z.write('data.data')
z.close()

# 解压
z = zipfile.ZipFile('laxi.zip', 'r')
z.extractall(path='.')
z.close()
# tarfile压缩解压缩
import tarfile

# 压缩
t=tarfile.open('/tmp/egon.tar','w')
t.add('/test1/a.py',arcname='a.bak')
t.add('/test1/b.py',arcname='b.bak')
t.close()


# 解压
t=tarfile.open('/tmp/egon.tar','r')
t.extractall('/egon')
t.close()



5.15.12 xml模块

xml是实现不同语言或程序之间进行数据交换的协议,跟json差不多,但json使用起来更简单,不过,古时候,在json还没诞生的黑暗年代,
大家只能选择用xml呀,至今很多传统公司如金融行业的很多系统的接口还主要是xml。
现在这种格式的文件比较少了,但是还是存在的所以大家简单了解一下,以备不时之需。

<?xml version="1.0"?>
<data>
    <country name="Liechtenstein">
        <rank updated="yes">2</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank updated="yes">5</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank updated="yes">69</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>

xml数据

# 增删改查
# 在进行操作之前,都应该进行这两步:

# import xml.etree.ElementTree as ET
# tree = ET.parse('a.xml')  # 形成树形结构
# root = tree.getroot()  # 得到树的根系
# print(root)
# 循环打印:
# for i in root:
#     print(i)
# <Element 'country' at 0x00000196B51191D8>
# <Element 'country' at 0x00000196B5124B88>
# <Element 'country' at 0x00000196B5124D18>

# 所有的增删改查都是基于这个root根系去操作

# 查:
# 1,全文搜索 year 将所有的year标签全部找
# print(root.iter('year'))
# print([i for i in root.iter('year')])
# 2,只找第一个,找到就返回
# print(root.find('country'))
# 3,在root的子节点找,找所有的
# print(root.findall('country'))

# 练习
# 找到标签也可以找到标签相应的内容:tag,attrib,text

# 1,找所有的rank标签,以及 attrib 和 text (这里利用列表推导式比较方便)
# print([i for i in root.iter('rank')])
# [<Element 'rank' at 0x000001367D0D49F8>, <Element 'rank' at 0x000001367D0D4BD8>, <Element 'rank' at 0x000001367D0D4D68>]
# print([i.attrib for i in root.iter('rank')])
# [{'updated': 'yes'}, {'updated': 'yes'}, {'updated': 'yes'}]
# print([i.text for i in root.iter('rank')])  # ['2', '5', '69']

# 2,找到第二个country的 neighbor标签以及他的属性
# print([tag for tag in root.findall('country')][1].find('neighbor').attrib)
# {'direction': 'N', 'name': 'Malaysia'}


# 增 append
# import xml.etree.ElementTree as ET
# tree = ET.parse('a.xml')  # 形成树形结构
# root = tree.getroot()  # 得到树的根系

# 给 year 大于2010年的所有标签下面添加一个month标签,属性为name:month 内容为30days

# for country in root.findall('country'):
#     for year in country.findall('year'):
#         if int(year.text) > 2010:
#             month = ET.Element('month')
#             month.text = '30days'
#             month.attrib = {'name': 'month'}
#             country.append(month)
# tree.write('b.xml')

#改

# import xml.etree.ElementTree as ET
# tree = ET.parse('a.xml')  # 形成树形结构
# root = tree.getroot()  # 得到树的根系
# 对所有的year属性以及值进行修改
# for node in root.iter('year'):
#     new_year=int(node.text)+1
#     node.text=str(new_year)
#     node.set('updated','yes')
#     node.set('version','1.0')
# tree.write('test.xml')


# 删
# import xml.etree.ElementTree as ET
# tree = ET.parse('a.xml')  # 形成树形结构
# root = tree.getroot()  # 得到树的根系
#
# # 将 rank值大于50的country标签删除
# for country in root.findall('country'):
#    rank = int(country.find('rank').text)
#    if rank > 50:
#      root.remove(country)
#
# tree.write('output.xml')
import xml.etree.ElementTree as ET
 
 
new_xml = ET.Element("namelist")
name = ET.SubElement(new_xml,"name",attrib={"enrolled":"yes"})
age = ET.SubElement(name,"age",attrib={"checked":"no"})
sex = ET.SubElement(name,"sex")
sex.text = '33'
name2 = ET.SubElement(new_xml,"name",attrib={"enrolled":"no"})
age = ET.SubElement(name2,"age")
age.text = '19'
 
et = ET.ElementTree(new_xml) #生成文档对象
et.write("test.xml", encoding="utf-8",xml_declaration=True)
 
ET.dump(new_xml) #打印生成的格式



5.15.13 subprocess


import  subprocess


# sh-3.2# ls /Users/egon/Desktop |grep txt$
# mysql.txt
# tt.txt
# 事物.txt

res1=subprocess.Popen('ls /Users/jieli/Desktop',shell=True,stdout=subprocess.PIPE)
res=subprocess.Popen('grep txt$',shell=True,stdin=res1.stdout,
                 stdout=subprocess.PIPE)

print(res.stdout.read().decode('utf-8'))


#等同于上面,但是上面的优势在于,一个数据流可以和另外一个数据流交互,可以通过爬虫得到结果然后交给grep
res1=subprocess.Popen('ls /Users/jieli/Desktop |grep txt$',shell=True,stdout=subprocess.PIPE)
print(res1.stdout.read().decode('utf-8'))

#windows下:

# dir | findstr 'test*'
# dir | findstr 'txt$'
import subprocess
res1=subprocess.Popen(r'dir C:\Users\Administrator\PycharmProjects\test\函数备课',shell=True,stdout=subprocess.PIPE)
res=subprocess.Popen('findstr test*',shell=True,stdin=res1.stdout,
                 stdout=subprocess.PIPE)

print(res.stdout.read().decode('gbk'))
#subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码
 
#举例说明:
import subprocess

obj = subprocess.Popen('dir',
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
)

print(obj.stdout.read().decode('gbk'))  # 正确命令

print(obj.stderr.read().decode('gbk'))  # 错误命令
# shell: 命令解释器,相当于调用cmd 执行指定的命令。
# stdout:正确结果丢到管道中。
# stderr:错了丢到另一个管道中。
# windows操作系统的默认编码是gbk编码。


  1. 1-9 ↩︎

posted @ 2023-07-13 20:27  WNAG_zw  阅读(17)  评论(0)    收藏  举报