python常用模块(二)


 1.ConfigParser模块

用于生成和修改配置文档,在python3.x中变更为configparser

 1 [DEFAULT]
 2 ServerAliveInterval = 45
 3 Compression = yes
 4 CompressionLevel = 9
 5 ForwardX11 = yes
 6  
 7 [bitbucket.org]
 8 User = hg
 9  
10 [topsecret.server.com]
11 Port = 50022
12 ForwardX11 = no
常见conf格式
 1 import configparser
 2  
 3 config = configparser.ConfigParser()
 4 config["DEFAULT"] = {'ServerAliveInterval': '45',
 5                       'Compression': 'yes',
 6                      'CompressionLevel': '9'}
 7  
 8 config['bitbucket.org'] = {}
 9 config['bitbucket.org']['User'] = 'hg'
10 config['topsecret.server.com'] = {}
11 topsecret = config['topsecret.server.com']
12 topsecret['Host Port'] = '50022'     # mutates the parser
13 topsecret['ForwardX11'] = 'no'  # same here
14 config['DEFAULT']['ForwardX11'] = 'yes'
15 with open('example.ini', 'w') as configfile:
16    config.write(configfile)
17 
18 #读取
19 >>> import configparser
20 >>> config = configparser.ConfigParser()
21 >>> config.sections()
22 []
23 >>> config.read('example.ini')
24 ['example.ini']
25 >>> config.sections()
26 ['bitbucket.org', 'topsecret.server.com']
27 >>> 'bitbucket.org' in config
28 True
29 >>> 'bytebong.com' in config
30 False
31 >>> config['bitbucket.org']['User']
32 'hg'
33 >>> config['DEFAULT']['Compression']
34 'yes'
35 >>> topsecret = config['topsecret.server.com']
36 >>> topsecret['ForwardX11']
37 'no'
38 >>> topsecret['Port']
39 '50022'
40 >>> for key in config['bitbucket.org']: print(key)
41 ...
42 user
43 compressionlevel
44 serveraliveinterval
45 compression
46 forwardx11
47 >>> config['bitbucket.org']['ForwardX11']
48 'yes'
49 
50 #增删改查
51 [section1]
52 k1 = v1
53 k2:v2
54   
55 [section2]
56 k1 = v1
57  
58 import ConfigParser
59   
60 config = ConfigParser.ConfigParser()
61 config.read('i.cfg')
62   
63 # ########## 读 ##########
64 #secs = config.sections()
65 #print secs
66 #options = config.options('group2')
67 #print options
68   
69 #item_list = config.items('group2')
70 #print item_list
71   
72 #val = config.get('group1','key')
73 #val = config.getint('group1','key')
74   
75 # ########## 改写 ##########
76 #sec = config.remove_section('group1')
77 #config.write(open('i.cfg', "w"))
78   
79 #sec = config.has_section('wupeiqi')
80 #sec = config.add_section('wupeiqi')
81 #config.write(open('i.cfg', "w"))
82   
83   
84 #config.set('group2','k1',11111)
85 #config.write(open('i.cfg', "w"))
86   
87 #config.remove_option('group2','age')
88 #config.write(open('i.cfg', "w"))
使用模块生成

2.hashlib模块

用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法

 1 import hashlib
 2 
 3 -----md5-----
 4 md_five = hashlib.md5()
 5 md_five.update(b'Hello World')            #加密
 6 
 7 print(md_five.digest())                         #二进制hash
 8 print(md_five.hexdigest())                   #十六进制hash
 9 
10 '''
11 def digest(self, *args, **kwargs): # real signature unknown
12     """ Return the digest value as a string of binary data. """
13     pass
14  
15 def hexdigest(self, *args, **kwargs): # real signature unknown
16     """ Return the digest value as a string of hexadecimal digits. """
17     pass
18  
19 '''
20 ----sha1-----
21 md_five = hashlib.sha1()
22 md_five.update(b'Hello World')
23 
24 print(md_five.hexdigest())
25 
26 -----sha256-----
27 md_five = hashlib.sha256()
28 md_five.update(b'Hello World')
29 
30 print(md_five.hexdigest())
31 
32 -----sha384-----
33 md_five = hashlib.sha384()
34 md_five.update(b'Hello World')
35 
36 print(md_five.hexdigest())
37 
38 -----sha512-----
39 md_five = hashlib.sha512()
40 md_five.update(b'Hello World')
41 
42 print(md_five.hexdigest())
43 
44 
45 #还有一个,这个就是相当于双层加密,大家应该都知道撞库啊,那么用了这个你解掉一层还有一层,是不是非常的牛X!
46 
47 #HMAC:散列消息鉴别码,简称HMAC,是一种基于消息鉴别码MAC(Message Authentication Code)的鉴别机制。使用HMAC时,消息通讯的双方,通过验证消息中加入的鉴别密钥K来鉴别消息的真伪;
48 一般用于网络通信中消息加密,前提是双方先要约定好key,就像接头暗号一样,然后消息发送把用key把消息加密,接收方用key + 消息明文再加密,拿加密后的值 跟 发送者的相对比是否相等,这样就能验证消息的真实性,及发送者的合法性了。
49 
50 #怎么用呢???
51 import hmac
52 
53 hm = hmac.new(b'Hello World!','你好世界!'.encode(encoding="utf-8"))
54 print(hm.digest())
55 print(hm.hexdigest())
View Code

3.subprocess模块

  这个模块跟os模块有点类似,也是可以和操作系统进行交互

  我们这里举个例子

  

import os

os.system("dir")
#这里会直接打印到屏幕上,也就是直接输出结果,那我想储存到一个变量里可以吗?
a = os.system("dir")
a
>>>0
#为什么输出的是0呢?os.system()只能输出命令结果到屏幕,赋值则返回的是命令执行结果,0为成功,失败则为非0而不是1

#那么我想保存到一个变量里就要用os.popen()
os.popen("dir"),read

  subprocess是从3.x的新模块,比os更好用,接下来我们就举一些例子

import subprocess

#run
subprocess.run("df -h",shell = True)
#命令正常则执行0,命令错误会报错

#call
subprocess.call("df -h",shell = True)
#命令正常则执行并返回状态0,命令错误会报错并返回状态非0

#check_call
subprocess.echk_call("df -h",shell = True)
#同上

#getstatusoutput
subprocess.getstatusoutput('mkdir -p /Q/Q/Q')
>>>(0, '')
#接收命令,以元组的方式返回,第一个为执行状态,第二个为执行结果

#getoutput
#跟上面比没有执行状态

#check_output
subprocess.check_output("ls -l",shell = True)
#直接返回结果,以二进制方式返回

#上面的这些底层封装的都是subprocess.Popen
例子:
a = subprocess.Popen("pwd",shell = True,stdout = subprocess.PIPE)
a.stdout.read()

#stdin:标准输入
#stdout:标准输出
#stderr:标准错误

#poll
a = subprocess.Popen("sleep 10;echo 'hehe'",shell = True,stdout=subprocess.PIPE)
print(a.poll())
>>>None
print(a.poll())
>>>0
#查询执行状态,未执行完返回None,执行完返回0,执行失败返回非0,有一个类似的是wait()

#terminate:杀掉所启动进程
#communicate:等待任务结束

可用参数:

    • args:shell命令,可以是字符串或者序列类型(如:list,元组)
    • bufsize:指定缓冲。0 无缓冲,1 行缓冲,其他 缓冲区大小,负值 系统缓冲
    • stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
    • preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
    • close_sfs:在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。
      所以不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout, stderr)。
    • shell:同上
    • cwd:用于设置子进程的当前目录
    • env:用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
    • universal_newlines:不同系统的换行符不同,True -> 同意使用
    • startupinfo与createionflags只在windows下有效
      将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等

终端输入的命令分为两种:

    • 输入即可得到输出,如:ifconfig
    • 输入进行某环境,依赖再输入,如:python

 

4.logging模块

#做运维的肯定要记录日志,不管是服务的报警还是记录日常的错误以及一些其他信息,日志都是不可或缺的.在python中有一个logging模块使专门用来干这个的,可以用logging模块来记录各种格式的日志,熟悉日志程序的都知道,日志有五个级别,分别是debug()info()warning()error() and critical(),让我们看看5个级别都如何使用

 1 import logging
 2 
 3 logging.warning("Hello World!")
 4 logging.debug("Hello World!")
 5 logging.info("Hello World!")
 6 logging.error("Hello World!")
 7 logging.critical("Hello World!")
 8 >>>WARNING:root:Hello World!
 9 ERROR:root:Hello World!
10 CRITICAL:root:Hello World!
11 
12 #诶,这里为什么只打印除了"warning","error","critical"这三个级别呢?
13 
14 #日志肯定是可以写到文件里的,so
15 logging.basicConfig(filename="cacti.log",level=logging.DEBUG)  #level是日志级别
16 #这样就写到文件里了,再写就是追加不会覆盖,设置日志级别,只会把比设置的日志级别高的日志写到日志中,比如设置的是error,那么debug就不会出现在日志中
17 
18 #那么做运维的就会说了“你TM的时间呢,没有时间我排个毛线的错”,说得好,所以呢:
19 
20 logging.basicConfig(filename="cacti.log",level=logging.ERROR,format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
21 logging.error("Hello error")
22 >>>11/23/2017 02:45:46 PM Hello error
View Code

 

#以下是其他格式

日志格式

%(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

用户输出的消息

 通过上面的东西呢,我们知道了日志可以输出到屏幕上也可以输出到文件里,那么我想同时输出到文件中和屏幕上呢.那么我们就需要学习其他姿势

logging模块记录日志主要分为四个类,请看下面

1.logger:用户可直接调用

  每个程序在输出信息之前都要获得一个Logger。Logger通常对应了程序的模块名,比如聊天工具的图形界面模块可以这样获得它的Logger:
  LOG=logging.getLogger(”chat.gui”)
  而核心模块可以这样:
  LOG=logging.getLogger(”chat.kernel”)

  Logger.setLevel(lel):指定最低的日志级别,低于lel的级别将被忽略。debug是最低的内置级别,critical为最高
  Logger.addFilter(filt)、Logger.removeFilter(filt):添加或删除指定的filter
  Logger.addHandler(hdlr)、Logger.removeHandler(hdlr):增加或删除指定的handler
  Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical():可以设置的日志级别

2.handler:将(logger)创建的日志信息发送到指定的输出位置

handler对象负责发送相关的信息到指定目的地。Python的日志系统有多种Handler可以使用。有些Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些 Handler可以把信息发送到网络上。如果觉得不够用,还可以编写自己的Handler。可以通过addHandler()方法添加多个多handler
Handler.setLevel(lel):指定被处理的信息级别,低于lel级别的信息将被忽略
Handler.setFormatter():给这个handler选择一个格式
Handler.addFilter(filt)、Handler.removeFilter(filt):新增或删除一个filter对象


每个Logger可以附加多个Handler。接下来我们就来介绍一些常用的Handler:
1) logging.StreamHandler
使用这个Handler可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息。它的构造函数是:
StreamHandler([strm])
其中strm参数是一个文件对象。默认是sys.stderr


2) logging.FileHandler
和StreamHandler类似,用于向一个文件输出日志信息。不过FileHandler会帮你打开这个文件。它的构造函数是:
FileHandler(filename[,mode])
filename是文件名,必须指定一个文件名。
mode是文件的打开方式。参见Python内置函数open()的用法。默认是’a',即添加到文件末尾。

3) logging.handlers.RotatingFileHandler
这个Handler类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建 一个新的同名日志文件继续输出。比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把 文件改名为chat.log.1。不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息。它的构造函数是:
RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]])
其中filename和mode两个参数和FileHandler一样。
maxBytes用于指定日志文件的最大文件大小。如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。
backupCount用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。


4) logging.handlers.TimedRotatingFileHandler
这个Handler和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就 自动创建新的日志文件。重命名的过程与RotatingFileHandler类似,不过新的文件不是附加数字,而是当前时间。它的构造函数是:
TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])
其中filename参数和backupCount参数和RotatingFileHandler具有相同的意义。
interval是时间间隔。
when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:
S 秒
M 分
H 小时
D 天
W 每星期(interval==0时代表星期一)
midnight 每天凌晨

3.filter:可以用来过滤日志信息(一般用不到)

4.formatter:就跟上面提到的fomat一样,用于决定输出格式

#这俩不难,就那么几个用法,就不多说了,接下来我们填一下坑,刚才说到如何同时屏幕输出和文件输出

 1 import logging
 2 logger = logging.getLogger("log")                      #定义一个logger
 3 logger.setLevel(logging.DEBUG)                         #定义logger日志最小级别
 4 
 5 delog = logging.StreamHandler()                       #定义屏幕输出handler
 6 delog.setLevel(logging.DEBUG)
 7 
 8 errlog = logging.FileHandler("error.txt")           #定义文件输出
 9 errlog.setLevel(logging.ERROR)
10 
11 delog_format = logging.Formatter("%(asctime)s %(lineno)d %(message)s")   #定义屏幕输出格式
12 errlog_format = logging.Formatter("%(asctime)s %(message)s")               #定义文件输出格式
13 delog.setFormatter(delog_format)                                              #给delog增加格式
14 errlog.setFormatter(errlog_format)                                            #给errlog增加格式
15 
16 logger.addHandler(delog)
17 logger.addHandler(errlog)                                                     #给logger增加handler
18 
19 logger.warning("Hello World")
20 
21 >>>2017-11-10 22:12:10,480 19 Hello World
22 
23 #到这里大家会发现,只有屏幕输出了,为什么文件没有输出呢,因为我们定义的屏幕输出handler的最小级别为error,输出的级别是warning,所以没有输出,利用这个呢我们就可以将一些重大错误输出到文件且报警,而一些类似debug,warning的呢就可以输出到屏幕
View Code

#运维平常要看很多日志,可是如果把每一条记录进去,就会造成一个超大的文件,所以会定期删除日志,那么python有一个日志文件截断的功能

 1 import logging
 2 from logging import handlers
 3 logger = logging.getLogger("log")                      #定义一个logger
 4 logger.setLevel(logging.DEBUG)                         #定义logger日志最小级别
 5 
 6 errlog = handlers.RotatingFileHandler(filename="error.log",maxBytes=10,backupCount=3)           
 7 #定义文件输出,到达10个字节则自动将error.log改名为error.log.1,有了.1则变成.2然后在重新创建一个error.log,但是呢最多备份3个
 8 errlog.setLevel(logging.ERROR)
 9 
10 errlog_format = logging.Formatter("%(asctime)s %(message)s")               #定义文件输出格式
11 errlog.setFormatter(errlog_format)                                            #给errlog增加格式
12 
13 logger.addHandler(errlog)                                                     #给logger增加handler
14 
15 logger.error("Hello World")
16 logger.error("Hello World")
17 logger.error("Hello World"
18 #这样你就会发现你多了几个文件*.log.1,*.log.2,*.log.3
19 
20 #上面呢是按文件大小来截断,还有一个按时间来截断
21 
22 import logging,time
23 from logging import handlers
24 logger = logging.getLogger("log")                      #定义一个logger
25 logger.setLevel(logging.DEBUG)                         #定义logger日志最小级别
26 
27 errlog = handlers.TimedRotatingFileHandler(filename="error.log",when="S",interval=1,backupCount=3,encoding="utf-8")          #定义文件输出,文件名,时间单位为秒,1秒备份一次,最多三次
28 errlog.setLevel(logging.ERROR)
29 
30 errlog_format = logging.Formatter("%(asctime)s %(message)s")               #定义文件输出格式
31 errlog.setFormatter(errlog_format)                                            #给errlog增加格式
32 
33 logger.addHandler(errlog)                                                     #给logger增加handler
34 
35 logger.error("你好世界1")
36 time.sleep(2)
37 logger.error("你好世界2")
38 time.sleep(2)
39 logger.error("你好世界3")
40 time.sleep(2)
41 logger.error("你好世界4")
42 time.sleep(2)
43 logger.error("你好世界5")
44 #多了三个带时间格式的error.log.2017...
View Code

 5.re正则表达式模块

熟悉linux的知道很多用来匹配的语句,比如grep,awk,sed等,那么python中也有一个re也可以做到

举一些常用的匹配字符:

 1 "."  # 默认匹配除
之外的任意一个字符,若指定flag DOTALL,则匹配任意字符,包括换行
 2 re.search(".{10}","name:Daniel age:18")
 3 >>><_sre.SRE_Match object; span=(0, 10), match='name:Danie'>
 4 
 5 "^"  #匹配字符开头,若指定flags MULTILINE,这种也可以匹配上
 6 (r"^a","
abc
eee",flags=re.MULTILINE)
 7 re.search("^name","name:Daniel age:18")
 8 >>><_sre.SRE_Match object; span=(0, 4), match='name'>
 9 
10 "$"  # 匹配字符结尾,或re.search("foo$","bfoo
sdfsf",flags=re.MULTILINE).group()也可以
11 re.search("18$","name:Daniel age:18")
12 >>><_sre.SRE_Match object; span=(16, 18), match='18'>
13 
14 "*"  #匹配*号前的字符0次或多次,re.findall("ab*","cabb3abcbbac")  结果为['abb', 'ab', 'a']
15 >>>re.findall("ab*","abbb2abb2ab2")
16 ['abbb', 'abb', 'ab']
17 
18 "+"    #匹配前一个字符1次或多次
19 >>>re.findall("a+","abbb2abb2ab2")
20 ['a', 'a', 'a']
21 
22 "?"  #匹配前一个字符1次或0次
23 >>>re.findall("a?","abbb2abb2ab2")
24 ['a', '', '', '', '', 'a', '', '', '', 'a', '', '', '']
25 
26 "{n}"  #匹配前一个字符n次
27 re.findall("ab{3}","abbb2abb2ab2")
28 ['abbb']
29 
30 "{n,m}"   #匹配前一个字符n到m次
31 re.findall("ab{1,3}","abbb2abb2ab2")
32 ['abbb', 'abb', 'ab']
33 
34 "|"    #匹配|左或|右的字符
35 re.findall("AB|ab","ABabbb2abb2ab2")
36 ['AB', 'ab', 'ab', 'ab']
37 
38 '(```)'  #分组匹配
39  re.findall("(d+)(a|b)","ABabbb2abb2ab2")
40 >>>[('2', 'a'), ('2', 'a')]
41 re.search("(?P<name>[A-z]{1,6})(?P<age>[0-9]{2})","Daniel18").groupdict()
42 >>>{'name': 'Daniel', 'age': '18'}
43 
44 
45 "A" #从头开始匹配
46 re.findall("AAB","ABabbb2abb2ab2")
47 >>>['AB']
48 
49 ''    #匹配字符结尾,同$
50 re.findall("2","ABabbb2abb2ab2")
51 >>>['2']
52 
53 "d"   #匹配数字
54 re.findall("d","ABabbb2abb2ab2")
55 ['2', '2', '2']
56 
57 "D"  #匹配非数字
58 re.findall("D","ABabbb2abb2ab2")
59 >>>['A', 'B', 'a', 'b', 'b', 'b', 'a', 'b', 'b', 'a', 'b']
60 
61 "w"   #匹配[A-Za-z0-9]
62 re.findall("w","ABabbb2abb2ab2")
63 >>>['A', 'B', 'a', 'b', 'b', 'b', '2', 'a', 'b', 'b', '2', 'a', 'b', '2']
64 
65 "W"   #匹配非[A-Za-z0-9]
66 re.findall("W","ABabbb2abb2ab2$!##@")
67 >>>['$', '!', '#', '#', '@']
68 
69 's'     匹配空白字符、	、
、
 ,
70 re.search("s+","1
2	3
4
56").group()
71 >>>'
'
View Code

大家从上面可以看到re.findall,re.search,这些是什么意思呢?还有没有别的呢?

 1 re.match 从头开始匹配
 2 re.match("a+","sa")                           #这里就匹配不到
 3 re.match("a+","asa")
 4 >>><_sre.SRE_Match object; span=(0, 1), match='a'>  #只匹配开头有的
 5 
 6 re.search 匹配包含
 7 re.search("a+","sa")
 8 >>><_sre.SRE_Match object; span=(1, 2), match='a'>      #匹配所有的,一般我们都用这个
 9 
10 re.findall 把所有匹配到的字符放到以列表中的元素返回
11 re.findall("a+","asaa")
12 >>>['a', 'aa']                          #返回所匹配到的元素
13 
14 re.split 以匹配到的字符当做列表分隔符
15  re.split("d+","a1b2c3d4e5")
16 ['a', 'b', 'c', 'd', 'e', '']                 #把数字都给分割
17 
18 re.sub      匹配字符并替换
19 re.sub("d","~","a1b2c3d4e5f6")
20 'a~b~c~d~e~f~'                  #将数字替换成~

反斜杠的困扰
与大多数编程语言相同,正则表达式里使用""作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符"",那么使用编程语言表示的正则表达式里将需要4个反斜杠"\\":前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r"\"表示。同样,匹配一个数字的"\d"可以写成r"d"。有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。

re.split("\\",r"usrlocalpython")
>>>['usr', 'local', 'python']  

re.split("\\",r"usr\localpython")        
>>>['usr', '', 'local', 'python']

re.split("\/",r"usr\localpython")
>>>['usr\\local\python']

re.split("/",r"/usr\localpython")
>>>['', 'usr\\local\python']


#四个匹配一个

到这里你会问了还有吗,还有一点点你需要稍微知道的,当然忘掉也无所谓

 1 re.I(re.IGNORECASE): 忽略大小写
 2 re.findall("a+","abAbAbab",flags=re.I)
 3 >>>['a', 'A', 'A', 'a']
 4 
 5 M(MULTILINE): 多行模式,改变'^''$'的行为
 6 re.findall("^a","abc
abc
abc",flags=re.M)
 7 >>>['a', 'a', 'a']               #匹配多行
 8 
 9 re.findall("c$","abc
abc
abc",flags=re.M)
10 >>>['c', 'c', 'c']
11 
12 S(DOTALL):改变"."的行为
13 re.findall(".","abc
abc
abc")
14 >>> ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'] #"."原本是匹配不到'
'
15 
16 re.findall(".","abc
abc
abc",flags=re.S)
17 >>> ['a', 'b', 'c', '
', 'a', 'b', 'c', '
', 'a', 'b', 'c']  #现在就匹配到

原文地址:https://www.cnblogs.com/wazy/p/7883320.html