18--常用模块03:logging日志、re正则、collections模块、paramiko模块
1.日志模块
1.1 日志基本配置
日志级别默认是30
import logging
# 一:日志基本配置(实际开发不使用这个)
logging.basicConfig(
# 1.日志输出位置:终端、文件
filename='access.log', # 默认打印到终端
# 2.日志输出格式
format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
# 3.时间格式
datefmt='%Y-%m-%d %H:%M:%S %p',
# 4.日志级别 (设置日志级别数,只有大于或者等于该level的日志,才会打印出来)
# 默认级别数
critical => 50
error => 40
warning => 30
info => 20
debug => 10
level=30, # 默认级别为warning 30
)
# 二:输出日志
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')
打印结果:
# 注意下面的root是默认的日志名字
WARNING:root:警告warn
ERROR:root:错误error
CRITICAL:root:严重critical
1.2 日志配置字典
直接使用该配置字典
# 日志配置字典:LOGGING_DIC (使用时,可以直接用该字典,小的改动即可!)
# 1.日志格式化输出中,可能用到的格式化串如下:
%(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 用户输出的消息
# 2.定义三种日志输出格式 强调:其中的%(name)s为getlogger时指定的名字-->日志产生者的名字
# 完整的标准输出格式 (用'[]' 或 '-' 格式隔开)
standard_format = '%(asctime)s - %(threadName)s:%(thread)d - task_id:%(name)s - %(filename)s:%(lineno)d -%(levelname)s - %(message)s'
# 简单输出格式
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
# 测试输出格式
test_format = '%(asctime)s - %(message)s'
# 3.日志配置字典 (重点是日志产生者:loggers、日志接受者:handlers、日志输出格式:formatters)
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
# 三种日志输出格式,依次配置在这里
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
'test': {
'format': test_format
},
},
'filters': {}, # 过滤日志的对象
# handlers是日志的接收者,不同的handler会将日志处理输出到不同的位置
'handlers': {
# 打印到终端的日志
'console': {
'level': 'DEBUG', # 输出哪种级别以上的日志
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple' # 哪种格式输出
},
# 打印到文件'a1.log'的日志
'default': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,日志轮转 (rotating:旋转)
'maxBytes': 1024*1024*5, # 日志大小 5M (字节 'B'为单位)
'backupCount': 5, # 旧的日志文件备份份数 (超过即删掉最旧的那份)
'filename': 'a1.log', # 实际位置应在项目文件/log/下os.path.join(os.path.dirname(os.path.dirname(__file__)),'log','a2.log')
'encoding': 'utf-8', # 存储到文件里的日志,应该指定字符编码
'formatter': 'standard',
},
# 打印到文件'a2.log'的日志
'other': {
'level': 'DEBUG',
'class': 'logging.FileHandler', # 保存到文件
'filename': 'a2.log',
'encoding': 'utf-8',
'formatter': 'test',
},
},
# loggers是日志的产生者,产生的日志会传递给handler然后控制输出
'loggers': {
# logging.getLogger(__name__)拿到的logger配置,不同的文件__name__不同,
这保证了打印日志时标识信息不同 (在日志输出时,日志名为实际调用的文件名),
但是拿着该名字去loggers里找key名时却发现找不到,于是默认使用key=''的配置
'': {
'handlers': ['default','console'], # 即log数据既写入a1.log文件又打印到屏幕
'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
'propagate': False, # 默认为True,是否向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递 (了解)
},
# 若需要额外的采集,再自定义日志产生者
'专门的采集': {
'handlers': ['other',],
'level': 'DEBUG',
'propagate': False,
},
},
}
1.3 日志使用
# 1.日志模块导入 (logging包下的config 和 getlogger)
import settings
from logging import config,getLogger
# 2.加载配置 (将自定义的日志配置字典,添加到logging配置中)
config.dictConfig(settings.LOGGING_DIC)
# 3.输出日志
logger1=getLogger('kkk') # 获取日志产生者
logger1.info('这是一条info日志') # 产生日志信息: 产生者.日志级别('日志信息')
logger2=getLogger('终端提示')
logger2.info('logger2产生的info日志')
logger3=getLogger('用户交易')
logger3.info('logger3产生的info日志')
logger4=getLogger('用户常规')
logger4.info('logger4产生的info日志')
# 补充两个重要知识点
1.日志名的命名
日志名是区别日志业务归属的一种非常重要的标识
2.日志轮转
日志记录着程序员运行过程中的关键信息
日志轮转也叫日志切割,通俗来讲就是对日志文件进行大小的控制。
每次记录的日志文件都是同一个文件,随着时间的推移,日志的容量自然会越来越大,读写速度会越来越慢。
那为了控制日志文件的大小,就需要对日志文件进行控制。
同时是把目前的日志文件改名,紧接着创建一个新的日志文件,名称和原来的一样,
重启服务进程,让其向新的日志文件中写日志。
# 简述:定期(按照日志文件大小)将记录日志文件重命名,然后再新建记录日志文件来存储日志文件。
# 强调:
1.logging是一个包,需要使用其下的config、getLogger,可以如下导入
from logging import config, getLogger
# 也可以使用如下导入
import logging.config # 这样连同logging都一起导入了 ('.'左侧的包或模块也会被导入)
2.正则表达式
2.1 什么是正则
# 一:什么是正则?
正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法。
或者说:正则就是用来描述一类事物的规则。(在Python中)它内嵌在Python中,并通过 re 模块实现。
正则表达式模式被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。
生活中处处都是正则:
比如我们描述:4条腿
你可能会想到的是四条腿的动物或者桌子,椅子等
继续描述:4条腿,活的
就只剩下四条腿的动物这一类了
2.2 常用匹配模式
2.2.1 元字符匹配
import re
print(re.findall('\w','aAbc123_*()-='))
print(re.findall('\W','aAbc123_*()-= '))
print(re.findall('\s','aA\rbc\t\n12\f3_*()-= '))
print(re.findall('\S','aA\rbc\t\n12\f3_*()-= '))
print(re.findall('\d','aA\rbc\t\n12\f3_*()-= '))
print(re.findall('\D','aA\rbc\t\n12\f3_*()-= '))
print(re.findall('\Aalex',' alexis alex sb')) # 在同一行,以什么开头(了解,常用'^')
print(re.findall('sb\Z',' alexis alexsb sb')) # 在同一行,以什么结尾(了解, 常用'$')
print(re.findall('sb\Z',"""alexsb # 针对多行,'\Z'不起作用
alexis
sb
"""))
print(re.findall('^alex','alexis alex sb')) # 字符串以什么开头 (不管字符串单行还是多行都适用)
print(re.findall('sb$','alexis alex sb')) # 字符串以什么结尾
print(re.findall('sb$',"""alex
alexis
alex
sb
"""))
print(re.findall('^alex$','alexis alex sb')) # '^'和'$'组合使用,则是固定匹配这个词
print(re.findall('^alex$','al ex'))
print(re.findall('^alex$','alex')) # ['alex']
2.2.2 重复(数量)匹配
# 重复匹配:'.' '*' '+' '?' '{n,m}' '.*' '.*?'
# 1. '.':匹配除了\n之外任意一个字符,指定re.DOTALL之后才能匹配换行符
print(re.findall('a.b','a1b a2b a b abbbb a\nb a\tb a*b'))
# ['a1b', 'a2b', 'a b', 'abb', 'a\tb', 'a*b']
print(re.findall('a.b','a1b a2b a b abbbb a\nb a\tb a*b',re.DOTALL))
# ['a1b', 'a2b', 'a b', 'abb', 'a\nb', 'a\tb', 'a*b']
# 2. '*':左侧字符重复0次或无穷次,性格贪婪
print(re.findall('ab*','a ab abb abbbbbbbb bbbbbbbb'))
# ['a', 'ab', 'abb', 'abbbbbbbb']
# 3. '+':左侧字符重复1次或无穷次,性格贪婪
print(re.findall('ab+','a ab abb abbbbbbbb bbbbbbbb'))
# ['ab', 'abb', 'abbbbbbbb']
# 4. '?':左侧字符重复0次或1次,性格非贪婪 (满足一次就马上获取,不会在匹配到后续内容)
print(re.findall('ab?','a ab abb abbbbbbbb bbbbbbbb'))
# ['a', 'ab', 'ab', 'ab']
# 5. '{n,m}':左侧字符重复n次到m次
{0,} => *
{1,} => +
{0,1} => ?
{n} 单独一个n代表只出现n次,多一次不行少一次也不行
print(re.findall('ab{2,5}','a ab abb abbb abbbb abbbbbbbb bbbbbbbb'))
# ['abb','abbb','abbbb','abbbbb]
# 练习:匹配所有包含小数在内的数字
print(re.findall( '\d+\.?\d*',"asdfasdf123as1111111.123dfa12adsf1asdf3"))
# ['123', '1111111.123', '12', '1', '3']
# 这个模式若是被匹配的字符串是"11.fa" 会被取到,但不是正则的问题,是字符串本身的问题,可以取到后进一步优化
### 贪婪与非贪婪
# 6. '.*' 组合使用代表:任意字符(数量不限) 默认为贪婪匹配
print(re.findall('a.*b','a1b22222222b')) # ['a1b22222222b']
# 7. '.*?'组合使用代表:任意字符(数量不限) 为非贪婪匹配(推荐使用)
print(re.findall('a.*?b','a1b22222222b')) # ['a1b']
2.2.3 或、非、转义匹配
# 1.或匹配:[] 匹配一个 中括号中的指定字符
括号内都是普通字符了,且如果-没有被转义的话,应该放到[]的开头或结尾
print(re.findall('a\db','a111b a3b a4b a9b aXb a b a\nb',re.DOTALL))
print(re.findall('a[0-9]b','a111b a3b a4b a9b aXb a b a\nb',re.DOTALL))
# ['a3b', 'a4b', 'a9b']
# a和b之间出现小于5的数字,[0-5]或者[012345]
print(re.findall('a[501234]b','a111b a3b a4b a9b aXb a b a\nb',re.DOTALL))
print(re.findall('a[0-5]b','a111b a3b a1b a0b a4b a9b aXb a b a\nb',re.DOTALL))
# ['a3b', 'a4b']
# a和b之间是数字或字母
print(re.findall('a[0-9a-zA-Z]b','a111b axb a3b a1b a0b a4b a9b aXb a b a\nb',re.DOTALL))
# ['axb', 'a3b', 'a1b', 'a0b', 'a4b', 'a9b', 'aXb']
# 2.或匹配: a|b 匹配a或b
print(re.findall('company|companies', 'Too many companies and the next one is my company'))
# ['companies', 'company']
# 3.非匹配:[]内的^代表取反
print(re.findall('a[^0-9a-zA-Z]b','a1111111b axb a3b a1b a0b a4b a9b aXb a b a\nb',re.DOTALL))
# ['a b', 'a\nb']
print(re.findall('a-b','a-b aXb a b a\nb',re.DOTALL))
# ['a-b']
print(re.findall('a[-0-9\n]b','a-b a0b a1b a8b aXb a b a\nb',re.DOTALL))
# ['a-b', 'a0b', 'a1b', 'a8b', 'a\nb']
# 4.转义: '\' 匹配原始符号: '\'
在正则表达式中,有很多有特殊意义的是元字符,比如\n和\s等
如果要在正则中匹配正常的"\n"而不是"换行符",就需要对"\"进行转义,变成'\\'
在python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的
而在字符串中\也有特殊的含义,本身还需要转义
所以如果匹配一次"\n",字符串中要写成'\\n',那么正则里就要写成"\\\\n",这样就太麻烦了
这个时候我们就用到了r'\n'这个概念,此时的正则是r'\\n'就可以了
print(re.findall('a\\c','a\c')) # 报错
# 'r' 代表告诉解释器使用rawstring,即原生字符串,把我们正则内的所有符号都当普通字符处理,不要转义
print(re.findall(r'a\\c','a\c'))
print(re.findall('a\\\\c','a\c')) # 同上面的意思一样,和上面的结果一样都是['a\\c']
2.2.4 分组匹配
# ():分组
print(re.findall('ab+', 'ababab123'))
# ['ab', 'ab', 'ab']
# findall方法的结果不是匹配的全部内容,而是组内的内容
print(re.findall('(ab)+123', 'ababab123'))
# ['ab'] 匹配到末尾的ab123中的ab
# 加上 '?:' 可以让结果为匹配的全部内容,包含分组括号外的正则内容
print(re.findall('(?:ab)+123', 'ababab123'))
# ['ababab123']
print(re.findall('href="(.*?)"', '<a href="http://www.baidu.com">点击</a>'))
# ['http://www.baidu.com']
print(re.findall('href="(?:.*?)"', '<a href="http://www.baidu.com">点击</a>'))
# ['href="http://www.baidu.com"']
print(re.findall('compan(?:y|ies)','Too many companies and the next one is my company'))
# ['companies', 'company']
2.3 常用方法
# 0.匹配结果
group() : 获取到匹配的值
# 若正则中是分组匹配,则group(0)是原始字符串,group(1)、group(n) 表示分组匹配后的第1、2、…个子串
groups() : 分组匹配后,以元祖的形式,返回一个包含所有组的字符串(也就是子存储的值),从1到所含的小组号
# 1.finall() 匹配所有
ret = re.findall('a', 'eva jason yuan') # 返回所有满足匹配条件的结果,放在列表里
print(ret) # 结果: ['a', 'a']
# 2.search() 搜索
ret = re.search('a', 'eva jason yuan').group()
print(ret) # 结果: 'a'
# 函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象
该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None
# 3.match() 匹配以什么开头
ret = re.match('a', 'abc').group() # 同search,不过在字符串开始处进行匹配 search+^代替match
print(ret) # 结果: 'a'
# 4.split() 分割
ret = re.split('[ab]', 'abcd') # 按'a'和'b'分割
print(ret) # 结果:['', '', 'cd']
ret=re.split("([ab])",'abcd')
print(ret) # 结果 : ['', 'a', '', 'b', 'cd']
# 在匹配部分加上()之后所切出的结果是不同的:
没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项
# 5.sub() 替换 默认替换所有
ret = re.sub('\d', 'H', 'eva3jason4yuan4', 1) # 将数字替换成'H',参数1表示只替换1次
print(ret) # 结果: 'evaHjason4yuan4'
ret = re.subn('\d', 'H', 'eva3jason4yuan4') # 返回元组 (替换的结果,替换了多少次)
print(ret) ('evaHjasonHyuanH', 3)
# 6.compile() 编译成正则表达式对象,再进行模式匹配
obj = re.compile('\d{3}') # 规则要匹配的是3个数字
ret = obj.search('abc123eeee') # 正则表达式对象调用search,参数为待匹配的字符串
print(ret.group()) # 结果 : 123
print(obj.findall('abc123eeee')) # ['123']
# 7.finditer() 返回一个存放匹配结果的迭代器
ret = re.finditer('\d', 'ds3sy4784a')
print(ret) # <callable_iterator object at 0x10195f940>
print(next(ret).group()) # 查看第一个结果
print(next(ret).group()) # 查看第二个结果
print([i.group() for i in ret]) # 查看剩余的左右结果
# 8.特定的字符: 修正符 作为上面函数的 第三位flags 参数
re.I :不区分大小写匹配
re.M :多行匹配 # 影响了 ^ 和 $ 的功能
re.S :匹配任意字符 # 使.可以匹配换行符
# eg:
ret = re.findall('a', 'eva jAson yuan', re.I) # 多个修正符用 '|'连接 eg: re.S|re.I|re.M
print(ret) # 结果: ['a', 'A', 'a']
3.collections模块
在内置数据类型(dict、list、set、tuple)的基础上
collections模块 提供额外的数据类型:
Counter、deque、defaultdict、namedtuple和OrderedDict等
1.namedtuple: 生成可以使用名字来访问元素内容的tuple
2.deque: 双端队列,可以快速的从另外一侧追加和推出对象
3.OrderedDict: 有序字典
4.defaultdict: 带有默认值的字典
5.Counter: 计数器,主要用来计数
3.1 namedtuple
# namedtuple: 生成可以使用名字来访问元素内容的tuple
tuple可以表示不变集合
例如:一个点的二维坐标就可以表示成: p = (1, 2)
但是 看到(1, 2),很难看出这个tuple是用来表示一个坐标的
# eg:
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
p.x # 1
p.y # 2
3.2 deque
# deque: 双端队列,可以快速的从另外一侧追加和推出对象
使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了
因为list是线性存储,数据量大的时候,插入和删除效率很低
deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:
# eg:
from collections import deque
q = deque(['a', 'b', 'c'])
q.append('x') # 尾部插入
q.appendleft('y') # 首部插入
q # deque(['y', 'a', 'b', 'c', 'x'])
deque除了实现list的append()和pop()外,还支持appendleft()和popleft()
3.3 OrderedDict
# OrderedDict: 有序字典
使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序
# eg:
from collections import OrderedDict
d = dict([('a', 1), ('b', 2), ('c', 3)])
d # dict的Key是无序的 {'a': 1, 'c': 3, 'b': 2}
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
od # OrderedDict的Key是有序的
# OrderedDict([('a', 1), ('b', 2), ('c', 3)])
# 注意:
OrderedDict的Key会按照插入的顺序排列,不是Key本身排序
3.4 defaultdict
# defaultdict: 带有默认值的字典
# 语法格式:
collections.defaultdict([default_factory[, …]])
第一个参数 default_factory 是工厂函数 (factory function),默认为 None
可选 list、tuple、set、str、int 等
其作用在于:
当用 defaultdict 创建的默认字典的 key 不存在时,
将返工厂函数 default_factory 的默认值。
例如: list 对应 []、tuple 对应 ()、str对应 ''、int对应0
# eg:
有如下值集合 [11,22,33,44,55,66,77,88,99,90...],
返回:{'k1': 大于66 , 'k2': 小于66}
# 原生字典解决:
values = [11, 22, 33, 44, 55, 66, 77, 88, 99, 90]
my_dict = {}
for value in values:
if value > 66:
if 'k1' in my_dict:
my_dict['k1'].append(value)
else:
my_dict['k1'] = [value]
else:
if 'k2' in my_dict:
my_dict['k2'].append(value)
else:
my_dict['k2'] = [value]
print(my_dict) # {'k2': [11, 22, 33, 44, 55, 66], 'k1': [77, 88, 99, 90]}
# 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['k1'].append(value)
else:
my_dict['k2'].append(value)
print(my_dict)
# defaultdict(<class 'list'>, {'k2': [11, 22, 33, 44, 55, 66], 'k1': [77, 88, 99, 90]})
# eg:
from collections import defaultdict
dd = defaultdict(lambda: 'N/A')
dd['key1'] = 'abc'
print(dd['key1']) # key1存在 'abc'
print(dd['key2']) # key不存在 返回 'N/A'
3.5 Counter
# Counter: 计数器,主要用来计数
Counter类的目的是用来跟踪值出现的次数
它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value
计数值可以是任意的Interger(包括0和负数)
Counter类和其他语言的bags或multisets很相似
# eg:
c = Counter('abcdeabcdabcaba')
print c
# Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1})
# 其他详细内容 http://www.cnblogs.com/Eva-J/articles/7291842.html
4.paramiko模块
# psutils模块: 获取系统运行的进程和系统利用率
# django channels:实现WebSocket协议 服务器主动推送消息 eg:群聊
websocket:https://www.cnblogs.com/liuqingzheng/p/10151572.html
# paramiko 基于SSH用于 连接远程服务器 模块
import paramiko
# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='10.0.0.100', port=22, username='root', password='123')
# 执行命令
stdin, stdout, stderr = ssh.exec_command('yum install vim -y')
# 获取命令结果
result = stdout.read()
print(result.decode('utf-8'))
# 关闭连接
ssh.close()
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器