Python 常用模块

1、time 模块

时间表示形式

时间相关的操作,时间有三种表示方式:

  • 时间戳               1970年1月1日之后的秒,即:time.time()
  • 格式化的字符串    2014-11-11 11:11,    即:time.strftime('%Y-%m-%d')
  • 结构化时间          元组包含了:年、日、星期等... time.struct_time    即:time.localtime()

时间戳 :

import time
print(time.time())

>>> 1526703163.746542

格式化的字符串:

import time
print(time.strftime("%Y-%m-%d,%H-%M-%S"))

>>> 2018-05-19,12-18-12
  • %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.localtime())

>>> time.struct_time(tm_year=2018, tm_mon=5, tm_mday=19, tm_hour=12, tm_min=19, tm_sec=8, tm_wday=5, tm_yday=139, tm_isdst=0)

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

几种时间形式的转换

print time.time()
print time.mktime(time.localtime())
  
print time.gmtime()    #可加时间戳参数
print time.localtime() #可加时间戳参数
print time.strptime('2014-11-11', '%Y-%m-%d')
  
print time.strftime('%Y-%m-%d') #默认当前时间
print time.strftime('%Y-%m-%d',time.localtime()) #默认当前时间
print time.asctime()
print time.asctime(time.localtime())
print time.ctime(time.time())

2、datetime 模块

datetime包下面的模块

  • object: 
  • timedelta # 主要用于计算时间跨度 
  • tzinfo # 时区相关 
  • time # 只关注时间 
  • date # 只关注日期 
  • datetime # 同时有时间和日期

其中class中datetime和timedelta比较常用,time和date和datetime在使用方法上基本是相同的。

获取当前日期和时间

from datetime import datetime
now = datetime.now() # 获取当前datetime
print(now)

>>> 2018-05-19 12:29:09.400586

注意到 datetime 是模块,datetime 模块还包含一个 datetime 类,通过 from datetime import datetime 导入的才是 datetime 这个类。

如果仅导入 import datetime,则必须引用全名 datetime.datetime

datetime.now() 返回当前日期和时间,其类型是 datetime

获取指定日期和时间

from datetime import datetime
dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
print(dt)

>>> 2015-04-19 12:20:00

datetime转换为timestamp

在计算机中,时间实际上是用数字表示的。我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数,称为timestamp。

你可以认为:

timestamp = 0 = 1970-1-1 00:00:00 UTC+0:00

对应的北京时间是:

timestamp = 0 = 1970-1-1 08:00:00 UTC+8:00

可见timestamp的值与时区毫无关系,因为timestamp一旦确定,其UTC时间就确定了,转换到任意时区的时间也是完全确定的,这就是为什么计算机存储的当前时间是以timestamp表示的,因为全球各地的计算机在任意时刻的timestamp都是完全相同的(假定时间已校准)。

把一个 datetime 类型转换为 timestamp 只需要简单调用 timestamp() 方法:

from datetime import datetime
dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
print(dt.timestamp()) # 把datetime转换为timestamp

timestamp转换为datetime

要把 timestamp 转换为 datetime ,使用 datetime 提供的 fromtimestamp() 方法:

from datetime import datetime
t = 1429417200.0
print(datetime.fromtimestamp(t))

>>> 2015-04-19 12:20:00

注意到timestamp是一个浮点数,它没有时区的概念,而datetime是有时区的。上述转换是在timestamp和本地时间做转换。

timestamp也可以直接被转换到UTC标准时区的时间:

from datetime import datetime
t = 1429417200.0
print(datetime.fromtimestamp(t)) # 本地时间

>>> 2015-04-19 12:20:00

print(datetime.utcfromtimestamp(t)) # UTC时间

>>> 2015-04-19 04:20:00

str转换为datetime

很多时候,用户输入的日期和时间是字符串,要处理日期和时间,首先必须把str转换为datetime。转换方法是通过datetime.strptime()实现,需要一个日期和时间的格式化字符串:

from datetime import datetime
cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')
print(cday)

>>> 2015-06-01 18:19:59

注意转换后的datetime是没有时区信息的。

datetime转换为str

如果已经有了datetime对象,要把它格式化为字符串显示给用户,就需要转换为str,转换方法是通过strftime()实现的,同样需要一个日期和时间的格式化字符串:

from datetime import datetime
now = datetime.now()
print(now.strftime('%a, %b %d %H:%M'))

>>> Sat, May 19 12:41

datetime加减

对日期和时间进行加减实际上就是把datetime往后或往前计算,得到新的 datetime。加减可以直接用  +和 运算符,不过需要导入timedelta这个类:

>>> from datetime import datetime, timedelta
>>> now = datetime.now()
>>> now
datetime.datetime(2015, 5, 18, 16, 57, 3, 540997)
>>> now + timedelta(hours=10)
datetime.datetime(2015, 5, 19, 2, 57, 3, 540997)
>>> now - timedelta(days=1)
datetime.datetime(2015, 5, 17, 16, 57, 3, 540997)
>>> now + timedelta(days=2, hours=12)
datetime.datetime(2015, 5, 21, 4, 57, 3, 540997)

可见,使用timedelta你可以很容易地算出前几天和后几天的时刻。

本地时间转换为UTC时间

时区转换

>>> 廖雪峰大大的教程

3、 random模块

>>> import random
>>> random.random()      # 大于0且小于1之间的小数
0.7664338663654585

>>> random.randint(1,5)  # 大于等于1且小于等于5之间的整数
2

>>> random.randrange(1,3) # 大于等于1且小于3之间的整数
1

>>> random.choice([1,'23',[4,5]])  # #1或者23或者[4,5]
1

>>> random.sample([1,'23',[4,5]],2) # #列表元素任意2个组合
[[4, 5], '23']

>>> random.uniform(1,3) #大于1小于3的小数
1.6270147180533838

>>> item=[1,3,5,7,9]
>>> random.shuffle(item) # 打乱次序
>>> item
[5, 1, 3, 7, 9]
>>> random.shuffle(item)
>>> 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())

4、 hashlib 

4.1 算法介绍

Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。

什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。

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

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

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

import hashlib
 
md5 = hashlib.md5()
md5.update(b'how to use md5 in python hashlib?')
# md5.update(bytes("
how to use md5 in python hashlib?","utf-8"))
print(md5.hexdigest()) 
计算结果如下: d26a53750bc40b38b65a520292f69306

如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:

md5 = hashlib.md5()
md5.update(b'how to use md5 in ')
md5.update(b'python hashlib?')
print(md5.hexdigest())

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,不过越安全的算法越慢,而且摘要长度更长。

4.2 摘要算法应用

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

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。

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

5、hmac

Hmac算法:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把key混入计算过程中。

和我们自定义的加salt算法不同,Hmac算法针对所有哈希算法都通用,无论是MD5还是SHA-1。采用Hmac替代我们自己的salt算法,可以使程序算法更标准化,也更安全。

Python自带的hmac模块实现了标准的Hmac算法

  我们首先需要准备待计算的原始消息message,随机key,哈希算法,这里采用MD5,使用hmac的代码如下:

import hmac
message = b'Hello world'
key = b'secret'
h = hmac.new(key,message,digestmod='MD5')
# 如果消息很长,可以多次调用h.update(msg)
print(h.hexdigest())

  可见使用hmac和普通hash算法非常类似。hmac输出的长度和原始哈希算法的长度一致。需要注意传入的key和message都是bytes类型,str类型需要首先编码为bytes

  hashlib和hmac都可以进行加盐的md5加密,即使是相同的盐和数据,加密出来的结果是不一样的哦!

# coding:utf-8
import hmac
import hashlib

content = "hello world"
salt_str = "谁言寸草心,报得三春晖."

obj_md5 = hashlib.md5(salt_str.encode("utf-8"))
obj_md5.update(content.encode("utf-8"))
hashlib_md5 = obj_md5.hexdigest()
print(hashlib_md5)  # 051f2913990c618c0757118687f02354

hmac_md5 = hmac.new(salt_str.encode("utf-8"), content.encode("utf-8"), "md5").hexdigest()
print(hmac_md5)  # 9c1c1559002fd870a4fca899598ba408

5、 os模块 

os模块是与操作系统交互的一个接口。

'''
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname")  改变当前脚本工作目录;相当于shell下cd
os.curdir  返回当前目录: ('.')
os.pardir  获取当前目录的父目录字符串名:('..')
os.makedirs('dirname1/dirname2')    可生成多层递归目录
os.removedirs('dirname1')    若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.mkdir('dirname')    生成单级目录;相当于shell中mkdir dirname
os.rmdir('dirname')    删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.listdir('dirname')    列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.remove()  删除一个文件
os.rename("oldname","newname")  重命名文件/目录
os.stat('path/filename')  获取文件/目录信息
os.sep    输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
os.linesep    输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
os.pathsep    输出用于分割文件路径的字符串 win下为;,Linux下为:
os.name    输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
os.system("bash command")  运行shell命令,直接显示
os.environ  获取系统环境变量
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的大小

os.getpid() 获得进程号
os.getppid() 获得父进程号
'''
os.urandom(20) 生成20位随机字符

注意:

1、os.stat('path/filename')  获取文件/目录信息。
stat 结构:

st_mode: inode 保护模式
st_ino: inode 节点号。
st_dev: inode 驻留的设备。
st_nlink: inode 的链接数。
st_uid: 所有者的用户ID。
st_gid: 所有者的组ID。
st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。
st_atime: 上次访问的时间。
st_mtime: 最后一次修改的时间。
st_ctime: 由操作系统报告的"ctime"。在某些系统上(如Unix)是最新的元数据更改的时间,在其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。

import os
a = os.stat("D:\WeChat\WeChat.exe").st_size
print(a)

os.popen(command[, mode[, bufsize]])

cmd:要执行的命令。
mode:打开文件的模式,默认为'r',用法与open()相同。
buffering:0意味着无缓冲;1意味着行缓冲;其它正值表示使用参数大小的缓冲。负的bufsize意味着使用系统的默认值,一般来说,对于tty设备,它是行缓冲;对于其它文件,它是全缓冲。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import os, sys

# 使用 mkdir 命令
a = 'mkdir nwdir'

b = os.popen(a,'r',1)

print(b)

------------------------------
open file 'mkdir nwdir', mode 'r' at 0x81614d0

递归删除非空目录

import os
def remove_dir(dir):
    dir = dir.replace('\\', '/')
    if(os.path.isdir(dir)):
        for p in os.listdir(dir):
            remove_dir(os.path.join(dir,p))
        if(os.path.exists(dir)):
            os.rmdir(dir)
    else:
        if(os.path.exists(dir)):
            os.remove(dir)
if __name__ == '__main__':
    remove_dir(r'D:/python/practice/') #函数使用



import shutil
 
path = 'g:\zhidao'
shutil.rmtree(path)

os.urandom

import os
import base64

# 生成32位随机字符
a = os.urandom(32)
# 编码为base64
base64.b64encode(a)
>>> b'2QDq4HSpT8U4W6iZ2xDzGW3CcY2WVsJXVEwYv0qludY='

6、 sys模块 

sys 模块提供了许多函数和变量来处理 Python 运行时环境的不同部分.

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

sys.modules 是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。
sys.setrecursionlimit() 递归的最大层数
 

7、 logging模块

7.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  
logging.basicConfig(level=logging.DEBUG,  
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',  
                    datefmt='%%Y-%m-%d,%H:%M:%S',  
                    filename='/tmp/test.log',  
                    filemode='w')  
  
logging.debug('debug message')  
logging.info('info message')  
logging.warning('warning message')  
logging.error('error message')  
logging.critical('critical message')

配置参数:
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用户输出的消息

禁用日志

在调试完程序后,你可能不希望所有这些日志消息出现在屏幕上。logging.disable() 函数禁用了这些消息,这样就不必进入到程序中,手工删除所有的日志调用。只要向 logging.disable() 传入一个日志级别,它就会禁止该级别和更低级别的所有日志消息。所以,如果想要禁用所有日志,只要在程序中添加 logging. disable(logging.CRITICAL)。

import logging
logging.basicConfig(level=logging.INFO, format=' %(asctime)s -
%(levelname)s - %(message)s')
logging.critical('Critical error! Critical error!')
>>>2015-05-22 11:10:48,054 - CRITICAL - Critical error! Critical error!
logging.disable(logging.CRITICAL)
logging.critical('Critical error! Critical error!')
logging.error('Error! Error!')

因为 logging.disable() 将禁用它之后的所有消息,你可能希望将它添加到程序中接近 import logging 代码行的位置。这样就很容易找到它,根据需要注释掉它,或取消注释,从而启用或禁用日志消息。

#basicconfig 简单,能做的事情相对少。

  #中文乱码问题,这个目前不知道怎么解决。

  #不能同时往文件和屏幕上输出。

#配置log对象,稍微有点复杂,能做的事情相对多。

7.2 logger对象配置

如果想同时把log打印在屏幕和文件日志里,就需要了解一点复杂的知识 了


Python 使用logging模块记录日志涉及四个主要类,使用官方文档中的概括最为合适:

  • logger提供了应用程序可以直接使用的接口;
  • handler将(logger创建的)日志记录发送到合适的目的输出;
  • filter提供了细度设备来决定输出哪条日志记录;
  • formatter决定日志记录的最终输出格式。

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():可以设置的日志级别

handler

  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 是输出日志文件名的前缀 when 是一个字符串的定义如下: “S”: Seconds “M”: Minutes “H”: Hours “D”: Days “W”: Week day (0=Monday) “midnight”: Roll over at midnight interval 是指等待多少个单位when的时间后,Logger会自动重建文件,当然,这个文件的创建 取决于filename+suffix,若这个文件跟之前的文件有重名,则会自动覆盖掉以前的文件,所以 有些情况suffix要定义的不能因为when而重复。 backupCount 是保留日志个数。默认的0是不会自动删除掉日志。若设10,则在文件的创建过程中 库会判断是否有超过这个10,若超过,则会从最先创建的开始删除。



importlogging importlogging.handlers   # logging初始化工作 logging.basicConfig()   # nor的初始化工作 nor=logging.getLogger("nor") nor.setLevel(logging.INFO)   # 添加TimedRotatingFileHandler到nor # 定义一个1分钟换一次log文件的handler filehandler=logging.handlers.TimedRotatingFileHandler(         "logging_test2",'M',1,1) # 设置后缀名称,跟strftime的格式一样 filehandler.suffix="%Y%m%d-%H%M.log" nor.addHandler(filehandler)

实例一:

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')

实例二:

import logging
 
#create logger
logger = logging.getLogger('TEST-LOG')
logger.setLevel(logging.DEBUG)   (默认WARNING,不管之后的fh和ch设置多少,如果不改,最低WARNING)
 
 
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
 
# create file handler and set level to warning
fh = logging.FileHandler("access.log")
fh.setLevel(logging.WARNING)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
# add formatter to ch and fh
ch.setFormatter(formatter)
fh.setFormatter(formatter)
 
# add ch and fh to logger
logger.addHandler(ch)
logger.addHandler(fh)
 
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

其他参考:日志滚动和过期日志删除

 

---

[loggers]
// 配置logger信息。必须包含一个名字叫做root的logger
// 当使用无参函数logging.getLogger()时,默认返回root这个logger
// 其他自定义logger可以通过 logging.getLogger("rotaingFile") 方式进行调用
keys=root,consoleLogger,rotatingFileAndConsoleLogger,timedRotatingFileAndConsoleLogger

[handlers]
// 定义声明handlers信息.filehandler 和 rotatingFileHandler有区别,rotaing可以管理大小日期等
keys=consoleHandler,rotatingFileHandler,timedRotatingFileHandler

[formatters]
// 设置日志格式
keys=simpleFormatter

[logger_root]
// 对loggers中声明的logger进行逐个配置,且要一一对应,在所有的logger中,必须制定level和handlers这两个选项,
// 对于非roothandler,还需要添加一些额外的option,其中qualname表示它在logger层级中的名字,
// 在应用代码中通过这个名字制定所使用的handler,即 logging.getLogger("rotaingFile"),handlers可以指定多个,
// 中间用逗号隔开,比如handlers=fileHandler,consoleHandler,同时制定使用控制台和文件输出日志
level=DEBUG
handlers=consoleHandler

[logger_consoleLogger]
level=DEBUG
// 该logger中配置的handler
handlers=consoleHandler
// logger 的名称
qualname=fileLogger
// propagate是可选项,其默认是为1,表示消息将会传递给高层次logger的handler,通常我们需要指定其值为0
propagate=0

[logger_rotatingFileAndConsoleLogger]
// 这样配置,fileAndConsoleLogger中就同时配置了consoleHandler,rotatingFileHandler
// consoleHandler 负责将日志输出到控制台
// rotatingFileHandler 负责将日志输出保存到文件中
level=DEBUG
handlers=consoleHandler,rotatingFileHandler
qualname=rotatingFileAndConsoleLogger
propagate=0

[logger_timedRotatingFileAndConsoleLogger]
// rotatingFileHandler 负责将日志输出保存到文件中,可按大小分隔
// timedRotatingFileHandler 负责将日志输出保存到文件中,可按日期分隔
level=DEBUG
handlers=consoleHandler,timedRotatingFileHandler
qualname=timedRotatingFileAndConsoleLogger
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
// 输出地址、追加、大小、保留个数?(暂时不确定)
args=(r"C:/Users/Administrator/Desktop/python/dub_script/log/log.log", "a", 1*1024*1024, 1)

[handler_timedRotatingFileHandler]
class=handlers.TimedRotatingFileHandler
level=DEBUG
formatter=simpleFormatter
// 输出地址、按D日分隔,每天一分个,保留3个日志,utf8
args=(r"C:/Users/Administrator/Desktop/python/dub_script/log/1.log", 'D',1,3,"utf8")

[formatter_simpleFormatter]
//format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
format=%(asctime)s-[%(module)s]-[line:%(lineno)d] - %(message)s
datefmt=%Y-%m-%d %H:%M:%S

 

 

8、 序列化模块

8.1、什么是序列化?

什么叫序列化——将原本的字典、列表等内容转换成一个字符串的过程就叫做序列化

我们把对象(变量)从内存中变成可存储或传输的过程称之为序列化。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

8.2、为什么要有序列化模块

比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?
现在我们能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来。
但是我们都知道,对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中。
你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢?
没错序列化的过程就是从dic 变成str(dic)的过程。现在你可以通过str(dic),将一个名为dic的字典转换成一个字符串,
但是你要怎么把一个字符串转换成字典呢?
聪明的你肯定想到了eval(),如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。
eval()函数十分强大,但是eval是做什么的?e官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果。
BUT!强大的函数有代价。安全性是其最大的缺点。
想象一下,如果我们从文件中读出的不是一个数据结构,而是一句"删除文件"类似的破坏性语句,那么后果实在不堪设设想。
而使用eval就要担这个风险。
所以,我们并不推荐用eval方法来进行反序列化操作(将str转换成python中的数据结构)

8.3、序列化的目的

1、以某种存储形式使自定义对象持久化
2、将对象从一个地方传递到另一个地方。
3、使程序更具维护性。

8.4、json模块 

如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。

JSON表示的对象就是标准的JavaScript语言的对象一个子集,JSON和Python内置的数据类型对应如下:

Json模块提供了四个功能:dumps、dump、loads、load

loads和dumps

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'}


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'}]

load和dump

import json
f = open('json_file','w')
dic = {'k1':'v1','k2':'v2','k3':'v3'}
json.dump(dic,f)  #dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件
f.close()

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

ensure_ascii关键字参数

import json
f = open('file','w')
json.dump({'国籍':'中国'},f)
ret = json.dumps({'国籍':'中国'})
f.write(ret+'\n')
json.dump({'国籍':'美国'},f,ensure_ascii=False)
ret = json.dumps({'国籍':'美国'},ensure_ascii=False)
f.write(ret+'\n')
f.close()

其他参数

Serialize obj to a JSON formatted str.(字符串表示的json对象) 
Skipkeys:默认值是False,如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None),设置为False时,就会报TypeError的错误。此时设置成True,则会跳过这类key 
ensure_ascii:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。) 
If check_circular is false, then the circular reference check for container types will be skipped and a circular reference will result in an OverflowError (or worse). 
If allow_nan is false, then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity). 
indent:应该是一个非负的整型,如果是0就是顶格分行显示,如果为空就是一行最紧凑显示,否则会换行且按照indent的数值显示前面的空白分行显示,这样打印出来的json数据也叫pretty-printed json 
separators:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(‘,’,’:’);这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。 
default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. 
sort_keys:将数据根据keys的值进行排序。 
To use a custom JSONEncoder subclass (e.g. one that overrides the .default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.
其他参数

json的格式化输出

import json
data = {'username':['李华','二愣子'],'sex':'male','age':16}
json_dic2 = json.dumps(data,sort_keys=True,indent=2,separators=(',',':'),ensure_ascii=False)
print(json_dic2)

关于json多次写入的问题

import json
json dump load
dic = {1:"中国",2:'b'}
f = open('fff','w',encoding='utf-8')
json.dump(dic,f,ensure_ascii=False)
json.dump(dic,f,ensure_ascii=False)
f.close()
f = open('fff',encoding='utf-8')
res1 = json.load(f)
res2 = json.load(f)
f.close()
print(type(res1),res1)
print(type(res2),res2)

----------------------------------------------------------------
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/py/sss.py", line 13, in <module>
    res1 = json.load(f)
  File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\json\__init__.py", line 296, in load
    parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
  File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\json\__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\json\decoder.py", line 340, in decode
    raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 22 (char 21)

写入的时候没有问题,可以多次写入,但是写入后,{"1": "中国", "2": "b"}{"1": "中国", "2": "b"},文件里类似于这种格式,json没法读取。

如果需要分次写入和分次读取,可以如下操作

import json
l = [{'k':'111'},{'k2':'111'},{'k3':'111'}]
f = open('file','w')
import json
for dic in l:
    str_dic = json.dumps(dic)
    f.write(str_dic+'\n')
f.close()

f = open('file')
import json
l = []
for line in f:
    dic = json.loads(line.strip())
    l.append(dic)
f.close()
print(l)

分次读写实例二

import json

dic = {1:"中国",2:'b'}
f = open('fff','w',encoding='utf-8')
json.dump(dic,f,ensure_ascii=False)
f.write("\n")                     #只要分隔开,然后读取的时候分行读取就行。
json.dump(dic,f,ensure_ascii=False)
f.close()
f = open('fff',encoding='utf-8')
for i in f:
    res1 = json.loads(i)
    res2 = json.loads(i)
f.close()
print(type(res1),res1)
print(type(res2),res2)

8.5、pickle模块

用于序列化的两个模块

  • json,用于字符串 和 python数据类型间进行转换
  • pickle,用于python特有的类型 和 python的数据类型间进行转换

pickle模块提供了四个功能:dumps、dump(序列化,存)、loads(反序列化,读)、load  (不仅可以序列化字典,列表...可以把python中任意的数据类型序列化

##----------------------------序列化
import pickle
 
dic={'name':'alvin','age':23,'sex':'male'}
 
print(type(dic))#<class 'dict'>
 
j=pickle.dumps(dic)
print(type(j))#<class 'bytes'>
 
 
f=open('序列化对象_pickle','wb')#注意是w是写入str,wb是写入bytes,j是'bytes'
f.write(j)  #-------------------等价于pickle.dump(dic,f)
 
f.close()
#-------------------------反序列化
import pickle
f=open('序列化对象_pickle','rb')
 
data=pickle.loads(f.read())#  等价于data=pickle.load(f)
 
print(data['age'])    

pickle自带多次写入和读取功能,先写先读

import time
struct_time1  = time.localtime(1000000000)
struct_time2  = time.localtime(2000000000)
f = open('pickle_file','wb')
pickle.dump(struct_time1,f)
pickle.dump(struct_time2,f)
f.close()
f = open('pickle_file','rb')
struct_time1 = pickle.load(f)
struct_time2 = pickle.load(f)
print(struct_time1.tm_year)
print(struct_time2.tm_year)
f.close()

8.6、shelve模块

8.6.1、在已有json和pickle的情况下,为什么用shelve?

  使用json或者 pickle 持久化数据,能 dump 多次,但 load 的话只能取到最新的 dump,因为先前的数据已经被后面 dump 的数据覆盖掉了。如果想要实现 dump 多次不被覆盖,可以使用 shelve 模块。

8.6.2、shelve模块的特点

  shelve 是一个简单的数据存储方案,类似 key-value 数据库,可以很方便的保存 python 对象,其内部是通过 pickle 协议来实现数据序列化。shelve 只有一个 open() 函数,这个函数用于打开指定的文件(一个持久的字典),然后返回一个 shelf 对象。shelf 是一种持久的、类似字典的对象。

  • shelve 模块可以看做是 pickle 模块的升级版,可以持久化所有 pickle 所支持的数据类型,而且 shelve 比 pickle 提供的操作方式更加简单、方便;
  • 在 shelve 模块中,key 必须为字符串,而值可以是 python 所支持的数据类型。
  • shelve 只提供给我们一个 open 方法,是用 key 来访问的,使用起来和字典类似。可以像字典一样使用get来获取数据等。
  • shelve 模块其实用 anydbm 去创建DB并且管理持久化对象的。

8.6.3、shelve的使用

持久化及解析内容

import shelve
f = shelve.open('shelve_file')
f['key'] = {'int':10, 'float':9.5, 'string':'Sample data'}  #直接对文件句柄操作,就可以存入数据
f.close()

import shelve
f1 = shelve.open('shelve_file')
existing = f1['key']  #取出数据的时候也只需要直接用key获取即可,但是如果key不存在会报错
f1.close()
print(existing)

  shelve模块有个限制,它不支持多个应用同一时间往同一个DB(文件)进行写操作。 
  所以如果只需进行读操作,可以修改默认参数flag=’r’ 让shelve通过只读方式打开DB(文件)。

  注:经测试,目前发现的是r模式在python2.7环境下可以生效

import shelve
f = shelve.open('shelve_file', flag='r')
existing = f['key']
print(existing)

f.close()

f = shelve.open('shelve_file', flag='r')
existing2 = f['key']
f.close()
print(existing2)

一般情况下,我们通过shelve来open一个对象后,只能进行一次赋值处理,赋值后不能再次更新处理。(可以整个重新赋值,但是不能做修改)

原因:从shelve的db文件中重新再访问一个key拿的是它的拷贝! 修改此拷贝后不做拷贝写回并不影响原来的key, 但你要是直接做的操作是赋值新的值到一个key里,那肯定就是指向原来的key,会被覆盖的。 

由于shelve在默认情况下是不会记录待持久化对象的任何修改的,所以我们在shelve.open()时候需要修改默认参数,否则对象的修改不会保存。

import shelve
# f1 = shelve.open('shelve_file')
# print(f1['key'])
# f1['key']['new_value'] = 'this was not here before'
# f1.close()

f2 = shelve.open('shelve_file', writeback=True)
print(f2['key'])
# f2['key']['new_value'] = 'this was not here before'
f2.close()

  writeback方式有优点也有缺点。优点是减少了我们出错的概率,并且让对象的持久化对用户更加的透明了;但这种方式并不是所有的情况都需要,首先,使用writeback以后,shelf在open()的时候回增加额外的内存消耗,并且当DB在close()的时候会将缓存中的每一个对象都写入到DB,这也会带来额外的等待时间。因为shelve没有办法知道缓存中哪些对象修改了,哪些对象没有修改,因此所有的对象都会被写入

8.7 yaml

格式:

a)键值对形式

user: admin
pwd: 123
job:
  - teacher
  - nurese
输出为:{'user': 'admin', 'pwd': 123, 'job': ['teacher', 'nurese']}

b)序列list

- admin1: 123456
- admin2: 111111
- admin3: 222222
输出:[{'admin1': 123456},
      {'admin2': 111111}, 
      {'admin3': 222222}]

c)纯量str

n1: 52.10
输出:{'n1': 52.1}

n2: true 
n3: false    #不区分大小写
输出:{'n2': True, 'n3': False}

None用~表示
n4: ~
输出:{'n4': None}

d)强制转换,使用!!

n7: !!str true
输出:{'n7': 'true'}

e)时间

time_val: 2018-03-01t11:33:22.55-06:00     # 时间值:{'time_val': datetime.datetime(2018, 3, 1, 17, 33, 22, 550000)}
date_val: 2019-01-10    # 日期值:{'date_val': datetime.date(2019, 1, 10)}

f)多个文件:一个yaml文件里存在多个文件,用---表示,只能一起读取,不能分开读取

---
user: admin
pwd: ~
job:
  - teacher
  - nurese
---
school: erxiao
location: sky

yaml 的操作

1.安装

需要安装得模块名为pyyaml,直接pip install pyyaml
导入,直接import yaml

2.操作字段

  • load(),解析yaml文档,返回一个Python对象;
  • load_all(),如果是string或文件包含几块yaml文档,可用该方法来解析全部的文档,生成一个迭代器;
  • dump(),将一个Python对象生成为一个yaml文档;
  • dump_all(),将多个段输出到一个yaml文档中。
  • safe_dump()
  • safe_load()
  • safe_dump_all()
  • safe_load_all()

官方给出的解释,因为yaml.safe_dump()、yaml.safe_load() 能够:Resolve only basic YAML tags. This is known to be safe for untrusted input.

yaml.safe_dump()

将一个python值转换为yaml格式文件,示例如下:

import yaml
dict_data = {'a': 1, 'b': 2}
with open('data.yaml', 'w', encoding='UTF-8') as yaml_file:
    yaml.safe_dump(dict_data, yaml_file)

如果上述yaml.dump()中不带第二个参数,则会返回一个类似yaml格式的字符串

import yaml
dict_data = {'a': 1, 'b': 2}
yaml_string = yaml.safe_dump(dict_data)
print(type(yaml_string))
print(yaml_string)

运行结果:

<class 'str'>
a: 1
b: 2

yaml.safe_load()

将yaml格式文件转换为python值,接第一例子,示例如下:

import yaml
with open('data.yaml', encoding='UTF-8') as yaml_file:
    data = yaml.safe_load(yaml_file)
print(type(data))
print(data)

运行结果:

<class 'dict'>
{'a': 1, 'b': 2}

yaml.safe_dump_all()

将一序列的python值转换为yaml格式文件,如果yaml.safe_dump_all()中不带第二个参数,则与yaml.dump()类似,会返回一个类似yaml格式的字符串

import yaml
dict_data1 = {'a': 1, 'b': 2}
dict_data2 = {'c': 3, 'd': 4}
yaml_string = yaml.safe_dump_all([dict_data1, dict_data2])
print(type(yaml_string))
print(yaml_string)

运行结果:

<class 'str'>
a: 1
b: 2
---
c: 3
d: 4

yaml.safe_load_all()

将yaml格式文件转换为python值,该yaml文件可以包含多块yaml数据,用法如下:

import yaml
with open('data.yaml', encoding='UTF-8') as yaml_file:
    data = yaml.safe_load_all(yaml_file)
    for item in data:
        print(item)

运行结果:

{'a': 1, 'b': 2}
{'c': 3, 'd': 4}

9、collections模块

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

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

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

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

4.OrderedDict: 有序字典

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

9.1 namedtuple

们知道tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成:

>>> p = (1, 2)

但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。

这时,namedtuple就派上了用场:

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2

似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:

#namedtuple('名称', [属性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])

默认值

Task = namedtuple("Task",["summary","owner","done","id"])
Task.__new__.defaults__ = (None,None,False,None)

9.2 deque

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

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

队列
import queue
q = queue.Queue()
q.put([1,2,3])
q.put(5)
q.put(6)
print(q)
print(q.get())
print(q.get())
print(q.get())
print(q.get())   # 阻塞
print(q.qsize())

from collections import deque
dq = deque([1,2])
dq.append('a')   # 从后面放数据  [1,2,'a']
dq.appendleft('b') # 从前面放数据 ['b',1,2,'a']
dq.insert(2,3)    #['b',1,3,2,'a']
print(dq.pop())      # 从后面取数据
print(dq.pop())      # 从后面取数据
print(dq.popleft())  # 从前面取数据
print(dq)

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

9.3 OrderedDict

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

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

复制代码
>>> 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本身排序:

>>> od = OrderedDict()
>>> od['z'] = 1
>>> od['y'] = 2
>>> od['x'] = 3
>>> od.keys() # 按照插入的Key的顺序返回
['z', 'y', 'x']

9.4 defaultdict 

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

即: {'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 my_dict.has_key('k1'):
            my_dict['k1'].append(value)
        else:
            my_dict['k1'] = [value]
    else:
        if my_dict.has_key('k2'):
            my_dict['k2'].append(value)
        else:
            my_dict['k2'] = [value]

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)

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

>>> from collections import defaultdict
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1'] # key1存在
'abc'
>>> dd['key2'] # key2不存在,返回默认值
'N/A'

Counter

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

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

10、re模块

import re

ret = re.findall('a', 'eva egon yuan')  # 返回所有满足匹配条件的结果,放在列表里
print(ret) #结果 : ['a', 'a']

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

ret = re.match('a', 'abc').group()  
print(ret)
# match是从头开始匹配,如果正则规则从头开始可以匹配上,
#就返回一个变量。匹配的内容需要用group才能显示,如果没匹配上,就返回None,调用group会报错
#结果 : 'a'

ret = re.split('[ab]', 'abcd')  # 先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割
print(ret)  # ['', '', 'cd']

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

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

obj = re.compile('\d{3}')  #将正则表达式编译成为一个 正则表达式对象,规则要匹配的是3个数字
ret = obj.search('abc123eeee') #正则表达式对象调用search,参数为待匹配的字符串
print(ret.group())  #结果 : 123
flags有很多可选值:

re.I(IGNORECASE)忽略大小写,括号内是完整的写法
re.M(MULTILINE)多行模式,改变^和$的行为
re.S(DOTALL)点可以匹配任意字符,包括换行符
re.L(LOCALE)做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用
re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag
re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释
phoneRegex = re.compile(r"""( (\d{3}|\(\d{3}\))? #注释一 (\s|-|\.)? #注释二 ...... #注释三 )""",re.VERBOSE)
import re
ret = re.finditer('\d', 'ds3sy4784a')   #finditer返回一个存放匹配结果的迭代器
print(ret)  # <callable_iterator object at 0x10195f940>
print(next(ret).group())  #查看第一个结果
print(next(ret).group())  #查看第二个结果
print([i.group() for i in ret])  #查看剩余的左右结果

注意:

1 findall的优先级查询:

import re

ret = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com')
print(ret)  # ['oldboy']     这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可

ret = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com')
print(ret)  # ['www.oldboy.com']

2 split的优先级查询

ret=re.split("\d+","eva3egon4yuan")
print(ret) #结果 : ['eva', 'egon', 'yuan']

ret=re.split("(\d+)","eva3egon4yuan")
print(ret) #结果 : ['eva', '3', 'egon', '4', 'yuan']

#在匹配部分加上()之后所切出的结果是不同的,
#没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项,
#这个在某些需要保留匹配部分的使用过程是非常重要的。

练习

1、匹配标签

import re


ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>")
#还可以在分组中利用?<name>的形式给分组起名字
#获取的匹配结果可以直接用group('名字')拿到对应的值
print(ret.group('tag_name'))  #结果 :h1
print(ret.group())  #结果 :<h1>hello</h1>

ret = re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>")
#如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致
#获取的匹配结果可以直接用group(序号)拿到对应的值
print(ret.group(1))
print(ret.group())  #结果 :<h1>hello</h1>

2、匹配整数

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']

3、数字匹配

1、 匹配一段文本中的每行的邮箱
y='123@qq.comaaa@163.combbb@126.comasdfasfs33333@adfcom'
import re
ret=re.findall('\w+@(?:qq|163|126).com',y)
print(ret)

2、 匹配一段文本中的每行的时间字符串,比如:‘1990-07-12’;
time='asfasf1990-07-12asdfAAAbbbb434241'
import re
ret=re.search(r'(?P<year>19[09]\d)-(?P<month>\d+)-(?P<days>\d+)',time)
print(ret.group('year'))
print(ret.group('month'))
print(ret.group('days'))

# 3、 匹配一段文本中所有的身份证数字。
a='sfafsf,34234234234,1231313132,154785625475896587,sdefgr54184785ds85,4864465asf86845'
import re
ret=re.findall('\d{18}',a)
print(ret)

# 4、 匹配qq号。(腾讯QQ号从10000开始)  [1,9][0,9]{4,}
q='3344,88888,7778957,10000,99999,414,4,867287672'
import re
ret=re.findall('[1-9][0-9]{4,}',q)
print(ret)

# 5、 匹配一个浮点数
import re
ret=re.findall('-?\d+\.?\d*','-1,-2.5,8.8,1,0')
print(ret)

# 6、 匹配汉字。             ^[\u4e00-\u9fa5]{0,}$
import re
ret=re.findall('[\u4e00-\u9fa5]{0,}','的沙发斯蒂芬')
print(ret)

# 7、 匹配出所有整数
a='1,-3,a,-2.5,7.7,asdf'
import re
ret=re.findall(r"'(-?\d+)'",str(re.split(',',a)))
print(ret)

 

 

4、爬虫练习

import re
import json
from urllib.request import urlopen

def getPage(url):
    response = urlopen(url)
    return response.read().decode('utf-8')

def parsePage(s):
    com = re.compile(
        '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
        '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S)

    ret = com.finditer(s)
    for i in ret:
        yield {
            "id": i.group("id"),
            "title": i.group("title"),
            "rating_num": i.group("rating_num"),
            "comment_num": i.group("comment_num"),
        }


def main(num):
    url = 'https://movie.douban.com/top250?start=%s&filter=' % num
    response_html = getPage(url)
    ret = parsePage(response_html)
    print(ret)
    f = open("move_info7", "a", encoding="utf8")

    for obj in ret:
        print(obj)
        data = str(obj)
        f.write(data + "\n")

count = 0
for i in range(10):
    main(count)
    count += 25
简化版
import requests

import re
import json

def getPage(url):

    response=requests.get(url)
    return response.text

def parsePage(s):
    
    com=re.compile('<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
                   '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>',re.S)

    ret=com.finditer(s)
    for i in ret:
        yield {
            "id":i.group("id"),
            "title":i.group("title"),
            "rating_num":i.group("rating_num"),
            "comment_num":i.group("comment_num"),
        }

def main(num):

    url='https://movie.douban.com/top250?start=%s&filter='%num
    response_html=getPage(url)
    ret=parsePage(response_html)
    print(ret)
    f=open("move_info7","a",encoding="utf8")

    for obj in ret:
        print(obj)
        data=json.dumps(obj,ensure_ascii=False)
        f.write(data+"\n")

if __name__ == '__main__':
    count=0
    for i in range(10):
        main(count)
        count+=25
复杂版

 

flags有很多可选值:

re.I(IGNORECASE)忽略大小写,括号内是完整的写法
re.M(MULTILINE)多行模式,改变^和$的行为
re.S(DOTALL)点可以匹配任意字符,包括换行符
re.L(LOCALE)做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用
re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag
re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释
phoneRegex
= re.compile(r"""( (\d{3}|\(\d{3}\))? #注释一 (\s|-|\.)? #注释二 ...... #注释三 )""",re.VERBOSE)

 

11、 configparser模块

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

创建文件

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

[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)

查找文件

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方法取深层嵌套的值

增删改操作

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")) 

12、 subprocess模块

 当我们需要调用系统的命令的时候,最先考虑的os模块。用os.system()和os.popen()来进行操作。但是这两个命令过于简单,不能完成一些复杂的操作,如给运行的命令提供输入或者读取命令的输出,判断该命令的运行状态,管理多个命令的并行等等。这时subprocess中的Popen命令就能有效的完成我们需要的操作。

      subprocess模块允许一个进程创建一个新的子进程,通过管道连接到子进程的stdin/stdout/stderr,获取子进程的返回值等操作。 

The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes.

This module intends to replace several other, older modules and functions, such as: os.system、os.spawn*、os.popen*、popen2.*、commands.*

这个模块只一个类:Popen。

简单命令

import subprocess

#  创建一个新的进程,与主进程不同步  if in win: s=subprocess.Popen('dir',shell=True)
s=subprocess.Popen('ls')
s.wait()                  # s是Popen的一个实例对象,等待ls命令完成再执行ending

print('ending...') 

命令带参数

linux:
import subprocess

subprocess.Popen('ls -l',shell=True)

#subprocess.Popen(['ls','-l'])

shell = True(windows必须有)

shell=True参数会让subprocess.call接受字符串类型的变量作为命令,并调用shell去执行这个字符串,当shell=False是,subprocess.call只接受数组变量作为命令,并将数组的第一个元素作为命令,剩下的全部作为该命令的参数。

举个例子来说明:

from subprocess import call  
import shlex  
cmd = "cat test.txt; rm test.txt"  
call(cmd, shell=True)

上述脚本中,shell=True的设置,最终效果是执行了两个命令

cat test.txt 和 rm test.txt

把shell=True 改为False,

from subprocess import call  
import shlex  
cmd = "cat test.txt; rm test.txt"  
cmd = shlex(cmd)  
call(cmd, shell=False)

则调用call的时候,只会执行cat的命令,且把 "test.txt;" "rm" "test.txt" 三个字符串当作cat的参数,所以并不是我们直观看到的好像有两个shell命令了。

也许你会说,shell=True 不是很好吗,执行两个命令就是我期望的呀。但其实,这种做法是不安全的,因为多个命令用分号隔开,万一检查不够仔细,执行了危险的命令比如 rm -rf / 这种那后果会非常严重,而使用shell=False就可以避免这种风险。

总体来说,看实际需要而定,官方的推荐是尽量不要设置shell=True。

控制子进程

当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程。刚才我们使用到了一个wait方法

此外,你还可以在父进程中对子进程进行其它操作:

s.poll() # 检查子进程状态
s.kill() # 终止子进程
s.send_signal() # 向子进程发送信号
s.terminate() # 终止子进程

s.pid:子进程号

子进程的文本流控制

可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe):

import subprocess

# s1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
# print(s1.stdout.read())



#s2.communicate()

s1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
s2 = subprocess.Popen(["grep","0:0"],stdin=s1.stdout, stdout=subprocess.PIPE)
out = s2.communicate()

print(out)

subprocess.PIPE实际上为文本流提供一个缓存区。s1的stdout将文本输出到缓存区,随后s2的stdin从该PIPE中将文本读取走。s2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。
注意:communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成

快捷API

'''
subprocess.call()

父进程等待子进程完成
返回退出信息(returncode,相当于Linux exit code)


subprocess.check_call()
父进程等待子进程完成
返回0,检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含
有returncode属性,可用try…except…来检查


subprocess.check_output()
父进程等待子进程完成
返回子进程向标准输出的输出结果
检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含
有returncode属性和output属性,output属性为标准输出的输出结果,可用try…except…来检查。


'''

为什么要用subprocess模块,因为可以把stdout和stderr分开读取。

import subprocess
res = subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
print(res.stdout.read().decode('gbk'))
print(res.stderr.read().decode('gbk'))

13、base64 

  base64模块是用来作base64编码解码,常用于小型数据的传输。编码后的数据是一个字符串,其包括a-z、A-Z、0-9、/、+共64个字符,即可用6个字节表示,写出数值就是0-63.故三个字节编码的话就变成了4个字节,如果数据字节数不是3的倍数,就不能精确地划分6位的块,此时需要在原数据后添加1个或2个零值字节,使其字节数为3的倍数,然后在编码后的字符串后添加1个或2个‘=’,表示零值字节,故事实上总共由65个字符组成。

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']     +       “=”

  然后,对二进制数据进行处理,每3个字节一组,一共是3x8=24bit,划为4组,每组正好6个bit:

 

将3个字节的‘Xue‘进行base64编码:

将2个字节’Xu‘进行base64编码:

将1个字节’X'进行base64编码:

  base64模块真正用的上的方法只有8个,分别是encode, decode, encodestring, decodestring, b64encode,b64decode, urlsafe_b64decode,urlsafe_b64encode。他们8个可以两两分为4组,encode,decode一组,专门用来编码和解码文件的,也可以StringIO里的数据做编解码;encodestring,decodestring一组,专门用来编码和解码字符串; b64encode和b64decode一组,用来编码和解码字符串,并且有一个替换符号字符的功能;urlsafe_b64encode和urlsafe_b64decode一组,这个就是用来专门对url进行base64编解码的。

13.1、代码实例

13.1.1、b64encode和b64decode:对字符串操作

import base64
 
st = 'hello world!'.encode()#默认以utf8编码
res = base64.b64encode(st)
print(res.decode())#默认以utf8解码
res = base64.b64decode(res)
print(res.decode())#默认以utf8解码


>>>
aGVsbG8gd29ybGQh
hello world!

-

import os, base64

# 图片装换
with open("./robot.png", "rb") as f:
    # 将读取的二进制文件转换为base64字符串
    bs64_str = base64.b64encode(f.read())
    # 打印图像转换base64格式的字符串,type结果为<class 'bytes'>
    print(bs64_str, type(bs64_str))
    # 将base64格式的数据装换为二进制数据
    imgdata = base64.b64decode(bs64_str)
    # 将二进制数据装换为图片
    with open("./robot2.png", "wb") as f2:
        f2.write(imgdata)

13.1.2、encode和code

对文件操作,有两个参数,一个是input,一个是output。

import base64
import io
 
st = "hello world!"
f = io.StringIO() #创建文件
out1 = io.StringIO()
out2 = io.StringIO()
f.write(st)
f.seek(0)
base64.encode(f,out1)
print(out1.getvalue())
out1.seek(0)
base64.decode(out1,out2)
print(out2.getvalue())

14、csv

1、CSV介绍

  CSV,全称为Comma-Separated Values,它以逗号分隔值,其文件以纯文本形式存储表格数据,该文件是一个字符序列,可以由任意数目的记录组成,每条记录有字段组成,字段间分隔符是逗号或制表符,相当于结构化的纯文本形式,它比Excel文件更简洁,用来存储数据比较方便

2、CSV常用类与方法

csv.reader(csvfile,dialect='excel',**fmtparams)

  遍历CSV文件对象并返回,csvfiel可以是任何支持迭代器协议的对象,如果csvfile是一个文件对象,它需要指定newline=''

csv.writer(csvfile,dialect='excel',**fmtparams)

  写入数据到csv文件中,csvfile可以是具有写入方法的任何对象,如果csvfiel是一个文件对象,应该用newline=''指定换行符(unix上位'\n',windows上位'\r\n')#!/usr/bin/env python

# -*- coding: utf-8 -*-
# @Time    : 2018/6/27 11:44
# @Author  : Py.qi
# @File    : csv_file1.py
# @Software: PyCharm
import csv
iterable=[['1','zs',20,8998,20180627],['1','zs',20,8998,20180627],['1','zs',20,8998,20180627]]

with open('csvfile.csv','w',newline='') as csvf:
    spanwriter=csv.writer(csvf,dialect='excel')   #创建writer对象
    spanwriter.writerow(['id','name','age','salary','date'])  #使用writer的方法writerow写入到文件
    spanwriter.writerows(iterable)  #迭代写入数据

with open('csvfile.csv','r',newline='') as csvf:
    spamreader=csv.reader(csvf)  #创建reader对象
    for i in spamreader:
        print('\t'.join(i))   #指定分隔符,读取csv文件数据
    # for j in i:
    #   print("%-10s"%j,end="")
    # print("\n")

# id name age salary date 1 zs 20 8998 20180627 1 zs 20 8998 20180627 1 zs 20 8998 20180627

class csv.DictReader(f, fieldnames=None, restkey=None, restval=None, dialect='excel', *args, **kwds)

  以字典的形式读取csv文件的行,fileldnames参数指定键,restkey指定默认key,restval指定默认value,dialect指定方言

class csv.DictWriter(f, fieldnames, restval='', extrasaction='raise', dialect='excel', *args, **kwds)

  创建一个字典形式的csv操作对象,f为写入的文件,fieldnames为制定key,restval制定默认value,extrasaction表示,如果在使用writerow()方法写入数据的字典中字段名找不到的键,则此参数将执行操作,如果设置为saise则会引发valueError,如果设置为ignore则字典中的额外值将被忽略

reader对象,DictReader实例和reader()对象具有的方法和属性

  csvreader.__next__():迭代读取对象的下一行

  csvreader.dialect:解析器使用的方言

  csvreader.line_num:从源迭代器读取的行数

  csvreader.fieldnames:如果在创建对象时为作为参数传递,则在首次访问文件或读取第一条记录是初始化此属性,此属性只适用于DictReader对象

writer对象,DictWriter和writer()实例对象具有的方法和属性:

  csvwriter.writerow():将行参数写入到文件对象,根据当前的方言格式化

  csvwriter.writerows(row):将row中的所有元素,行对象的迭代写入到文件对象

  csvwriter.dialect:解析器使用的方言

  DictWriter.writeheader():写入一行字段名,只适用于DictWriter对象

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2018/6/27 12:13
# @Author  : Py.qi
# @File    : csv_file2.py
# @Software: PyCharm
import csv
import pandas

iter=[
    {'id':2,'name':'wanwu','age':23,'date':20180627},
    {'id':3,'name':'zhaoliu','age':24,'date':20180627},
    {'id':4,'name':'tianqi','age':25,'date':20180627}
]
#写入文件
with open('names.csv','w',newline='') as csvf:
    fieldnames=['id','name','age','date']
    writer=csv.DictWriter(csvf,fieldnames=fieldnames)
    writer.writeheader()
    writer.writerow({'id':1,'name':'lisii','age':22,'date':20180627})
    writer.writerows(iter)

#读取文件
with open('names.csv','r') as csvf:
    reader=csv.DictReader(csvf,fieldnames=fieldnames)
    for i in reader:
        print(i['id'],i['name'],i['age'],i['date'])

#也可以使用pandas模块来读取csv文件
df=pandas.read_csv('names.csv')
print(df)

#
id name age date
1 lisii 22 20180627
2 wanwu 23 20180627
3 zhaoliu 24 20180627
4 tianqi 25 20180627

   id     name  age      date
0   1    lisii   22  20180627
1   2    wanwu   23  20180627
2   3  zhaoliu   24  20180627
3   4   tianqi   25  20180627

15、paramiko

  paramiko实现了SSHv2协议(底层使用cryptography)。有了Paramiko以后,我们就可以在Python代码中直接使用SSH协议对远程服务器执行操作,而不是通过ssh命令对远程服务器进行操作。

pip3 install paramiko

15.1、Paramiko介绍

paramiko包含两个核心组件:SSHClientSFTPClient

  • SSHClient的作用类似于Linux的ssh命令,是对SSH会话的封装,该类封装了传输(Transport),通道(Channel)及SFTPClient建立的方法(open_sftp),通常用于执行远程命令。
  • SFTPClient的作用类似与Linux的sftp命令,是对SFTP客户端的封装,用以实现远程文件操作,如文件上传、下载、修改文件权限等操作。
# Paramiko中的几个基础名词:
 
1、Channel:是一种类Socket,一种安全的SSH传输通道;
2、Transport:是一种加密的会话,使用时会同步创建了一个加密的Tunnels(通道),这个Tunnels叫做Channel;
3、Session:是client与Server保持连接的对象,用connect()/start_client()/start_server()开始会话。

15.2、Paramiko基本使用

基于用户名和密码的 sshclient 方式登录

import paramiko
# 创建SSH对象(实例化SSHClient)
ssh = paramiko.SSHClient()

# 加载本地HOSTS主机文件
ssh.load_system_host_keys()
# 允许连接不在know_hosts文件中的主机 # 自动添加策略,保存服务器的主机名和密钥信息,如果不添加,那么不再本地know_hosts文件中记录的主机将无法连接 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接服务器 ssh.connect(hostname='192.168.199.146', port=22, username='fishman', password='9') # 执行命令 stdin, stdout, stderr = ssh.exec_command('df') # 获取命令结果 res,err = stdout.read(),stderr.read() result = res if res else err print(result.decode()) # 关闭连接 ssh.close()

基于秘钥链接登录

# 配置私人密钥文件位置
private = paramiko.RSAKey.from_private_key_file('/Users/ch/.ssh/id_rsa')
 
#实例化SSHClient
client = paramiko.SSHClient()
 
#自动添加策略,保存服务器的主机名和密钥信息,如果不添加,那么不再本地know_hosts文件中记录的主机将无法连接
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 
#连接SSH服务端,以用户名和密码进行认证
client.connect(hostname='10.0.0.1',port=22,username='root',pkey=private)

基于用户名和密码的 transport 方式登录

  基于SSHClient是传统的连接服务器、执行命令、关闭的一个操作,有时候需要登录上服务器执行多个操作,比如执行命令、上传/下载文件,上面方法则无法实现,可以通过如下方式来操作。

  SSHClient()里面有一个transport变量,这个是用于获取连接的,因此我们也可以单独的获取到transport变量,然后执行连接操作

#SSHClient 封装 Transport

import paramiko

# 实例化一个transport对象
transport = paramiko.Transport(('192.168.199.146', 22))

# 建立连接
transport.connect(username='fishman', password='9')

# 将sshclient的对象的transport指定为以上的transport
ssh = paramiko.SSHClient()
ssh._transport = transport

# 执行命令,和传统方法一样
stdin, stdout, stderr = ssh.exec_command('df')
print (stdout.read().decode())

# 关闭连接
transport.close()

基于秘钥的 transport 方式登录

import paramiko

private_key = paramiko.RSAKey.from_private_key_file('/Users/ljf/.ssh/id_rsa')

# 实例化一个transport对象
transport = paramiko.Transport(('192.168.199.146', 22))

# 建立连接
transport.connect(username='fishman', pkey=private_key)
ssh = paramiko.SSHClient()
ssh._transport = transport
 
# 执行命令及获取结果
stdin, stdout, stderr = ssh.exec_command('df')
res,err = stdout.read(),stderr.read()
result = res if res else err
print(result.decode())

# 关闭连接
transport.close()

SFTPClient用于连接远程服务器并执行上传下载-----基于用户名密码

import paramiko

# 实例化一个trans对象# 实例化一个transport对象
transport = paramiko.Transport(('192.168.199.146', 22))

# 建立连接
transport.connect(username='fishman', password='9')
# 实例化一个 sftp对象,指定连接的通道
sftp = paramiko.SFTPClient.from_transport(transport)
 
#LocalFile.txt 上传至服务器 /home/fishman/test/remote.txt
sftp.put('LocalFile.txt', '/home/fishman/test/remote.txt')

# 将LinuxFile.txt 下载到本地 fromlinux.txt文件中
sftp.get('/home/fishman/test/LinuxFile.txt', 'fromlinux.txt')
transport.close()

 

SFTPClient用于连接远程服务器并执行上传下载-----基于秘钥

 

import paramiko
private_key
= paramiko.RSAKey.from_private_key_file('/Users/ljf/.ssh/id_rsa') transport = paramiko.Transport(('192.168.199.146', 22)) transport.connect(username='fishman', password='9') sftp = paramiko.SFTPClient.from_transport(transport) # LocalFile.txt 上传至服务器 /home/fishman/test/remote.txt sftp.put('LocalFile.txt', '/home/fishman/test/remote.txt') # 将LinuxFile.txt 下载到本地 fromlinux.txt文件中 sftp.get('/home/fishman/test/LinuxFile.txt', 'fromlinux.txt') transport.close()

综合实例

class SSHConnection(object):
 
    def __init__(self, host_dict):
        self.host = host_dict['host']
        self.port = host_dict['port']
        self.username = host_dict['username']
        self.pwd = host_dict['pwd']
        self.__k = None
 
    def connect(self):
        transport = paramiko.Transport((self.host,self.port))
        transport.connect(username=self.username,password=self.pwd)
        self.__transport = transport
 
    def close(self):
        self.__transport.close()
 
    def run_cmd(self, command):
        """
         执行shell命令,返回字典
         return {'color': 'red','res':error}或
         return {'color': 'green', 'res':res}
        :param command:
        :return:
        """
        ssh = paramiko.SSHClient()
        ssh._transport = self.__transport
        # 执行命令
        stdin, stdout, stderr = ssh.exec_command(command)
        # 获取命令结果
        res = unicode_utils.to_str(stdout.read())
        # 获取错误信息
        error = unicode_utils.to_str(stderr.read())
        # 如果有错误信息,返回error
        # 否则返回res
        if error.strip():
            return {'color':'red','res':error}
        else:
            return {'color': 'green', 'res':res}
 
    def upload(self,local_path, target_path):
        # 连接,上传
        sftp = paramiko.SFTPClient.from_transport(self.__transport)
        # 将location.py 上传至服务器 /tmp/test.py
        sftp.put(local_path, target_path, confirm=True)
        # print(os.stat(local_path).st_mode)
        # 增加权限
        # sftp.chmod(target_path, os.stat(local_path).st_mode)
        sftp.chmod(target_path, 0o755)  # 注意这里的权限是八进制的,八进制需要使用0o作为前缀
 
    def download(self,target_path, local_path):
        # 连接,下载
        sftp = paramiko.SFTPClient.from_transport(self.__transport)
        # 将location.py 下载至服务器 /tmp/test.py
        sftp.get(target_path, local_path)
 
    # 销毁
    def __del__(self):
        self.close()
 
  
#unicode_utils.py
def to_str(bytes_or_str):
    """
    把byte类型转换为str
    :param bytes_or_str:
    :return:
    """
    if isinstance(bytes_or_str, bytes):
        value = bytes_or_str.decode('utf-8')
    else:
        value = bytes_or_str
    return value

伪终端问题:

  paramiko的ssh.exec_command()命令不会自动分配伪终端,可以通过paramiko的ssh.exec_command(command,get_pty=True)开启。(由于目前还没搞清楚的不知名原因,各类教程都不太推荐开启伪终端)

具体问题:

  默认情况下,paramiko在远程主机上执行命令的时候,命令的搜索路径为 /usr/local/bin:/bin:/usr/bin ,这样我们安装的软件,如果命令不在这些路径下的话,就会执行错误,报找不到命令的错误。

解决办法:

  1. 就是在上面的路径里(/usr/local/bin:/bin:/usr/bin)加我们需要命令的软链接(ln /usr/install/jdk1.8.0_60/bin/java -s java)
  2. 把需要的路径包含进去 stdin, stdout, stderr = ssh.exec_command('export PATH=$PATH:/usr/local/install/xxx/;echo $PATH')
  3. 先执行一条命令 stdin, stdout, stderr = ssh.exec_command('. ~/.bashrc;echo $PATH');stdin, stdout, stderr = ssh.exec_command('source ~/.bashrc;bash test.sh')
  4. 方法2和3,环境变量可以传到后续执行的‘.sh’脚本里面去
  5. paramiko的ssh.exec_command()命令会开启一个单独的session,而且在exec_command中设定的环境变量不会传递给后续的脚本。解决方法是使用bash执行命令: ssh.exec_command("bash -l -c 'some commands and some scripts...'") 或者 ssh.exec_command("bash -l -c 'some commands and some scripts...'") 

  bash -l -c解释:-l(login)表示bash作为一个login shell;-c(command)表示执行后面字符串内的命令,这样执行的脚本,可以获取到/etc/profile里的全局变量,包括我们搜索命令的目录PATH

  • -l Make bash act as if it had been invoked as a login shell
  • -c If the -c option is present, then commands are read from string.
  • You're running the command passed to the -c argument. -l makes it a login shell so bash first reads /etc/profile,

为什么paramiko登录后,只获得路径(/usr/local/bin:/bin:/usr/bin)

解答:密码存在于被连接机器的 /etc/ssh/sshd_config 配置文件里;如下所示,sshd服务默认把 /usr/local/bin:/bin:/usr/bin 作为PATH的值

#       $OpenBSD: sshd_config,v 1.80 2008/07/02 02:24:18 djm Exp $

# This is the sshd server system-wide configuration file.  See
# sshd_config(5) for more information.

# This sshd was compiled with PATH=/usr/local/bin:/bin:/usr/bin

# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented.  Uncommented options change a
# default value.

 

实现输入命令立马返回结果的功能

  以上操作都是基本的连接,如果我们想实现一个类似xshell工具的功能,登录以后可以输入命令回车后就返回结果:

import paramiko
import os
import select
import sys
 
# 建立一个socket
trans = paramiko.Transport(('192.168.2.129', 22))
# 启动一个客户端
trans.start_client()
 
# 如果使用rsa密钥登录的话
'''
default_key_file = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa')
prikey = paramiko.RSAKey.from_private_key_file(default_key_file)
trans.auth_publickey(username='super', key=prikey)
'''
# 如果使用用户名和密码登录
trans.auth_password(username='super', password='super')
# 打开一个通道
channel = trans.open_session()
# 获取终端
channel.get_pty()
# 激活终端,这样就可以登录到终端了,就和我们用类似于xshell登录系统一样
channel.invoke_shell()
# 下面就可以执行你所有的操作,用select实现
# 对输入终端sys.stdin和 通道进行监控,
# 当用户在终端输入命令后,将命令交给channel通道,这个时候sys.stdin就发生变化,select就可以感知
# channel的发送命令、获取结果过程其实就是一个socket的发送和接受信息的过程
while True:
    readlist, writelist, errlist = select.select([channel, sys.stdin,], [], [])
    # 如果是用户输入命令了,sys.stdin发生变化
    if sys.stdin in readlist:
        # 获取输入的内容
        input_cmd = sys.stdin.read(1)
        # 将命令发送给服务器
        channel.sendall(input_cmd)
 
    # 服务器返回了结果,channel通道接受到结果,发生变化 select感知到
    if channel in readlist:
        # 获取结果
        result = channel.recv(1024)
        # 断开连接后退出
        if len(result) == 0:
            print("\r\n**** EOF **** \r\n")
            break
        # 输出到屏幕
        sys.stdout.write(result.decode())
        sys.stdout.flush()
 
# 关闭通道
channel.close()
# 关闭链接
trans.close()

支持tab自动补全

import paramiko
import os
import select
import sys
import tty
import termios
 
'''
实现一个xshell登录系统的效果,登录到系统就不断输入命令同时返回结果
支持自动补全,直接调用服务器终端
'''
# 建立一个socket
trans = paramiko.Transport(('192.168.2.129', 22))
# 启动一个客户端
trans.start_client()
 
# 如果使用rsa密钥登录的话
'''
default_key_file = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa')
prikey = paramiko.RSAKey.from_private_key_file(default_key_file)
trans.auth_publickey(username='super', key=prikey)
'''
# 如果使用用户名和密码登录
trans.auth_password(username='super', password='super')
# 打开一个通道
channel = trans.open_session()
# 获取终端
channel.get_pty()
# 激活终端,这样就可以登录到终端了,就和我们用类似于xshell登录系统一样
channel.invoke_shell()
 
# 获取原操作终端属性
oldtty = termios.tcgetattr(sys.stdin)
try:
    # 将现在的操作终端属性设置为服务器上的原生终端属性,可以支持tab了
    tty.setraw(sys.stdin)
    channel.settimeout(0)
 
    while True:
        readlist, writelist, errlist = select.select([channel, sys.stdin,], [], [])
        # 如果是用户输入命令了,sys.stdin发生变化
        if sys.stdin in readlist:
            # 获取输入的内容,输入一个字符发送1个字符
            input_cmd = sys.stdin.read(1)
            # 将命令发送给服务器
            channel.sendall(input_cmd)
 
        # 服务器返回了结果,channel通道接受到结果,发生变化 select感知到
        if channel in readlist:
            # 获取结果
            result = channel.recv(1024)
            # 断开连接后退出
            if len(result) == 0:
                print("\r\n**** EOF **** \r\n")
                break
            # 输出到屏幕
            sys.stdout.write(result.decode())
            sys.stdout.flush()
finally:
    # 执行完后将现在的终端属性恢复为原操作终端属性
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
 
# 关闭通道
channel.close()
# 关闭链接
trans.close()

16、gzip

创建gzip文件

import gzip
content = "Lots of content here.\n第二行"
f = gzip.open('file.txt.gz', 'wb')
f.write(content.encode("utf-8"))
f.close()

对gzip文件的解压

import gzip
f = gzip.open("file.txt.gz", 'rb')#打开压缩文件对象
f_out=open("file","w")#打开解压后内容保存的文件
file_content = f.read()    #读取解压后文件内容
f_out.write(file_content.decode("utf-8")) #写入新文件当中
print(file_content.decode("utf-8")) #打印读取内容
f.close() #关闭文件流
f_out.close()

压缩现有文件

 

import gzip
f_in = open('test.png', 'rb')
f_out = gzip.open('test.png.gz', 'wb')

f_out.writelines(f_in)
# f_out.write(f_in.read())

f_out.close()
f_in.close()

 

压缩数据

import gzip
data = "这是一串字符串"
bytes_data = gzip.compress(data.encode("utf-8"))
print("字节压缩: ", bytes_data)

>>>
字节压缩:  b'\x1f\x8b\x08\x00\xbb5Y]\x02\xff{\xb1\x7f\xe6\xb3\x19\xeb\x9f\xechx\xb2c\xd3\xd3\xb5\xd3\x9f\xafY\x06d\x00\x00u\xa1\x12E\x15\x00\x00\x00'

数据解压

import gzip
data = "这是一串字符串"
bytes_data = gzip.compress(data.encode("utf-8"))
print("字节压缩: ", bytes_data)

bytes_dedata = gzip.decompress(bytes_data)
print("字节解压: ", bytes_dedata.decode("utf-8"))

17、loguru

这个库的安装方式很简单,就用基本的 pip 安装即可

pip3 install loguru

17.1 基本使用

from loguru import logger

logger.debug('this is a debug message')

  不需要配置什么东西,直接引入一个 logger,然后调用其 debug 方法即可。 在 loguru 里面有且仅有一个主要对象,那就是 logger,loguru 里面有且仅有一个 logger,而且它已经被提前配置了一些基础信息,比如比较友好的格式化、文本颜色信息等等。 上面的代码运行结果如下:

2019-10-13 22:46:12.367 | DEBUG    | __main__:<module>:4 - this is a debug message

  可以看到其默认的输出格式是上面的内容,有时间、级别、模块名、行号以及日志信息,不需要手动创建 logger,直接使用即可,另外其输出还是彩色的,看起来会更加友好。 以上的日志信息是直接输出到控制台的,并没有输出到其他的地方,如果想要输出到其他的位置,比如存为文件,我们只需要使用一行代码声明即可。 例如将结果同时输出到一个 runtime.log 文件里面,可以这么写:

from loguru import logger

logger.add('runtime.log')
logger.debug('this is a debug')

  也不需要再声明一个 FileHandler 了,就一行 add 语句搞定,运行之后会发现目录下 runtime.log 里面同样出现了刚刚控制台输出的 DEBUG 信息。 上面就是一些基本的使用,但这还远远不够,下面我们来详细了解下它的一些功能模块。

17.2 详细使用

  既然是日志,那么最常见的就是输出到文件了。loguru 对输出到文件的配置有非常强大的支持,比如支持输出到多个文件,分级别分别输出,过大创建新文件,过久自动删除等等。 下面我们分别看看这些怎样来实现,这里基本上就是 add 方法的使用介绍。因为这个 add 方法就相当于给 logger 添加了一个 Handler,它给我们暴露了许多参数来实现 Handler 的配置,下面我们来详细介绍下。 首先看看它的方法定义吧:

def add(
        self,
        sink,
        *,
        level=_defaults.LOGURU_LEVEL,
        format=_defaults.LOGURU_FORMAT,
        filter=_defaults.LOGURU_FILTER,
        colorize=_defaults.LOGURU_COLORIZE,
        serialize=_defaults.LOGURU_SERIALIZE,
        backtrace=_defaults.LOGURU_BACKTRACE,
        diagnose=_defaults.LOGURU_DIAGNOSE,
        enqueue=_defaults.LOGURU_ENQUEUE,
        catch=_defaults.LOGURU_CATCH,
        **kwargs
    ):
    pass

  看看它的源代码,它支持这么多的参数,如 level、format、filter、color 等等,另外我们还注意到它有个非常重要的参数 sink,我们看看官方文档:https://loguru.readthedocs.io/en/stable/api/logger.html#sink,可以了解到通过 sink 我们可以传入多种不同的数据结构,汇总如下:

  • sink 可以传入一个 file 对象,例如 sys.stderr 或者 open('file.log', 'w') 都可以。
  • sink 可以直接传入一个 str 字符串或者 pathlib.Path 对象,其实就是代表文件路径的,如果识别到是这种类型,它会自动创建对应路径的日志文件并将日志输出进去。
  • sink 可以是一个方法,可以自行定义输出实现。
  • sink 可以是一个 logging 模块的 Handler,比如 FileHandler、StreamHandler 等等,或者上文中我们提到的 CMRESHandler 照样也是可以的,这样就可以实现自定义 Handler 的配置。
  • sink 还可以是一个自定义的类,具体的实现规范可以参见官方文档。

  所以说,刚才我们所演示的输出到文件,仅仅给它传了一个 str 字符串路径,他就给我们创建了一个日志文件,就是这个原理。

17.2.1 基本参数

  下面我们再了解下它的其他参数,例如 format、filter、level 等等。 其实它们的概念和格式和 logging 模块都是基本一样的了,例如这里使用 format、filter、level 来规定输出的格式:

logger.add('runtime.log', format="{time} {level} {message}", filter="my_module", level="INFO")

17.2.2 删除 sink

  另外添加 sink 之后我们也可以对其进行删除,相当于重新刷新并写入新的内容。 删除的时候根据刚刚 add 方法返回的 id 进行删除即可,看下面的例子:

from loguru import logger

trace = logger.add('runtime.log')
logger.debug('this is a debug message')
logger.remove(trace)
logger.debug('this is another debug message')

  看这里,我们首先 add 了一个 sink,然后获取它的返回值,赋值为 trace。随后输出了一条日志,然后将 trace 变量传给 remove 方法,再次输出一条日志,看看结果是怎样的。 控制台输出如下:

2019-10-13 23:18:26.469 | DEBUG    | __main__:<module>:4 - this is a debug message
2019-10-13 23:18:26.469 | DEBUG    | __main__:<module>:6 - this is another debug message

  日志文件 runtime.log 内容如下:

2019-10-13 23:18:26.469 | DEBUG    | __main__:<module>:4 - this is a debug message

  可以发现,在调用 remove 方法之后,确实将历史 log 删除了。但实际上这并不是删除,只不过是将 sink 对象移除之后,在这之前的内容不会再输出到日志中。 这样我们就可以实现日志的刷新重新写入操作。

17.2.3 rotation 配置

  用了 loguru 我们还可以非常方便地使用 rotation 配置,比如我们想一天输出一个日志文件,或者文件太大了自动分隔日志文件,我们可以直接使用 add 方法的 rotation 参数进行配置。 我们看看下面的例子:

logger.add('runtime_{time}.log', rotation="500 MB")

  通过这样的配置我们就可以实现每 500MB 存储一个文件,每个 log 文件过大就会新创建一个 log 文件。我们在配置 log 名字时加上了一个 time 占位符,这样在生成时可以自动将时间替换进去,生成一个文件名包含时间的 log 文件。 另外我们也可以使用 rotation 参数实现定时创建 log 文件,例如:

logger.add('runtime_{time}.log', rotation='00:00')

  这样就可以实现每天 0 点新创建一个 log 文件输出了。 另外我们也可以配置 log 文件的循环时间,比如每隔一周创建一个 log 文件,写法如下:

logger.add('runtime_{time}.log', rotation='1 week')

  这样我们就可以实现一周创建一个 log 文件了。

17.2.4 retention 配置

  很多情况下,一些非常久远的 log 对我们来说并没有什么用处了,它白白占据了一些存储空间,不清除掉就会非常浪费。retention 这个参数可以配置日志的最长保留时间。 比如我们想要设置日志文件最长保留 10 天,可以这么来配置:

logger.add('runtime.log', retention='10 days')

  这样 log 文件里面就会保留最新 10 天的 log,再也不用担心 log 沉积的问题啦。

17.2.5 compression 配置

  loguru 还可以配置文件的压缩格式,比如使用 zip 文件格式保存,示例如下:

logger.add('runtime.log', compression='zip')

   这样可以更加节省存储空间。

17.2.6 字符串格式化

  loguru 在输出 log 的时候还提供了非常友好的字符串格式化功能,像这样:

logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')

  这样在添加参数就非常方便了。

17.2.7 字符串格式化

  loguru 在输出 log 的时候还提供了非常友好的字符串格式化功能,像这样:

logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')

  这样在添加参数就非常方便了。

17.2.8 enqueue异步写入

  enqueue=True 代表异步写入,官方的大概意思是:在多进程同时往日志文件写日志的时候使用队列达到异步功效

logger.info(enqueue=True)

17.2.9 Traceback 记录

  在很多情况下,如果遇到运行错误,而我们在打印输出 log 的时候万一不小心没有配置好 Traceback 的输出,很有可能我们就没法追踪错误所在了。 但用了 loguru 之后,我们用它提供的装饰器就可以直接进行 Traceback 的记录,类似这样的配置即可:

@logger.catch
def my_function(x, y, z):
    # An error? It's caught anyway!
    return 1 / (x + y + z)

  我们做个测试,我们在调用时三个参数都传入 0,直接引发除以 0 的错误,看看会出现什么情况:

my_function(0, 0, 0)

  运行完毕之后,可以发现 log 里面就出现了 Traceback 信息,而且给我们输出了当时的变量值,真的是不能再赞了!结果如下:

\> File "run.py", line 15, in <module>
    my_function(0, 0, 0)
    └ <function my_function at 0x1171dd510>

  File "/private/var/py/logurutest/demo5.py", line 13, in my_function
    return 1 / (x + y + z)
                │   │   └ 0
                │   └ 0
                └ 0

ZeroDivisionError: division by zero

17.2.10 filter

一日一技:loguru 如何把不同的日志写入不同的文件中 - 云+社区 - 腾讯云 (tencent.com)

 

  因此,用 loguru 可以非常方便地实现日志追踪,debug 效率可能要高上十倍了? 另外 loguru 还有很多很多强大的功能,更多的内容大家可以看看 loguru 的官方文档详细了解一下:https://loguru.readthedocs.io/en/stable/index.html。 

17.3 单例模式

import time, os
import loguru

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
t = time.strftime("%Y_%m_%d")


class Loggings():
    """
    采用单例模式
    """
    __instance = None


    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = super(Loggings, cls).__new__(cls, *args, **kwargs)

        return cls.__instance

    def __init__(self):
        self.logger = loguru.logger
        self.logger.add(f"{BASE_DIR}/Logs/runtime_{t}.log", rotation="00:00", encoding="utf-8", enqueue=True,
               retention="10 days")


logging = Loggings().logger

 18.枚举enum

一些具有特殊含义的类,其实例化对象的个数往往是固定的,比如用一个类表示月份,则该类的实例对象最多有 12 个;再比如用一个类表示季节,则该类的实例化对象最多有 4 个。

针对这种特殊的类,python3.4 中新增加了 Enum 枚举类。也就是说,对于这些实例化对象个数固定的类,可以用枚举类来定义。
例如,下面程序演示了如何定义一个枚举类:

from enum import Enum
class Color(Enum):
    # 为序列值指定value值
    red = 1
    green = 2
    blue = 3

如果想将一个类定义为枚举类,只需要令其继承自 enum 模块中的 Enum 类即可。例如在上面程序中,Color 类继承自 Enum 类,则证明这是一个枚举类。
在 Color 枚举类中,red、green、blue 都是该类的成员(可以理解为是类变量)。注意,枚举类的每个成员都由 2 部分组成,分别为 name 和 value,其中 name 属性值为该枚举值的变量名(如 red),value 代表该枚举值的序号(序号通常从 1 开始)。
和普通类的用法不同,枚举类不能用来实例化对象,但这并不妨碍我们访问枚举类中的成员。访问枚举类成员的方式有多种,例如以 Color 枚举类为例,在其基础上添加如下代码:

#调用枚举成员的 3 种方式
print(Color.red)
print(Color['red'])
print(Color(1))
#调取枚举成员中的 value 和 name
print(Color.red.value)
print(Color.red.name)
#遍历枚举类中所有成员的 2 种方式
for color in Color:
    print(color)

>>>
Color.red
Color.red
Color.red
1
red
Color.red
Color.green
Color.blue

枚举类成员之间不能比较大小,但可以用 == 或者 is 进行比较是否相等,例如:

print(Color.red == Color.green)
print(Color.red.name is Color.green.name)

>>>
Flase
Flase

需要注意的是,枚举类中各个成员的值,不能在类的外部做任何修改,也就是说,下面语法的做法是错误的:

Color.red = 4

除此之外,该枚举类还提供了一个 __members__ 属性,该属性是一个包含枚举类中所有成员的字典,通过遍历该属性,也可以访问枚举类中的各个成员。例如:

for name,member in Color.__members__.items():
    print(name,"->",member)
>>>
red -> Color.red
green -> Color.green
blue -> Color.blue

值得一提的是,Python 枚举类中各个成员必须保证 name 互不相同,但 value 可以相同,举个例子:

 
from enum import Enum
class Color(Enum):
    # 为序列值指定value值
    red = 1
    green = 1
    blue = 3
print(Color['green'])

>>>
Color.red

可以看到,Color 枚举类中 red 和 green 具有相同的值(都是 1),Python 允许这种情况的发生,它会将 green 当做是 red 的别名,因此当访问 green 成员时,最终输出的是 red。

在实际编程过程中,如果想避免发生这种情况,可以借助 @unique 装饰器,这样当枚举类中出现相同值的成员时,程序会报 ValueError 错误。例如:

#引入 unique
from enum import Enum,unique
#添加 unique 装饰器
@unique
class Color(Enum):
    # 为序列值指定value值
    red = 1
    green = 1
    blue = 3
print(Color['green'])

>>>
Traceback (most recent call last):
  File "D:\python3.6\demo.py", line 3, in <module>
    class Color(Enum):
  File "D:\python3.6\lib\enum.py", line 834, in unique
    (enumeration, alias_details))
ValueError: duplicate values found in <enum 'Color'>: green -> red

除了通过继承 Enum 类的方法创建枚举类,还可以使用 Enum() 函数创建枚举类。例如:

from enum import Enum
#创建一个枚举类
Color = Enum("Color",('red','green','blue'))
#调用枚举成员的 3 种方式
print(Color.red)
print(Color['red'])
print(Color(1))
#调取枚举成员中的 value 和 name
print(Color.red.value)
print(Color.red.name)
#遍历枚举类中所有成员的 2 种方式
for color in Color:
    print(color)

Enum() 函数可接受 2 个参数,第一个用于指定枚举类的类名,第二个参数用于指定枚举类中的多个成员。

如上所示,仅通过一行代码,即创建了一个和前面的 Color 类相同的枚举类。运行程序,其输出结果为:

Color.red
Color.red
Color.red
1
red
Color.red
Color.green
Color.blue

 

posted @ 2018-05-19 16:31  dongye95  阅读(716)  评论(0编辑  收藏  举报