• 09 . Python3之常用模块


    模块的定义与分类

    模块是什么?

    一个函数封装一个功能,你使用的软件可能就是由n多个函数组成的(先备考虑面向对象)。比如抖音这个软件,不可能将所有程序都写入一个文件,所以咱们应该将文件划分,这样其组织结构要好并且代码不冗余。加入分了10个文件,每个文件里面可能都有相同的功能(函数),怎么办?所以将这些相同的功能封装到一个文件中,那么这个存储着很多常用的功能的py文件,就是模块。 模块就是文件,存放一堆常用的函数,谁用谁拿。怎么拿?比如:我要策马奔腾共享人世繁华,应该怎么样?我应该骑马,你也要去浪,你是不是也要骑马。 我们说一个函数就是一个功能,那么把一些常用的函数放在一个py文件中,这个文件就称之为模块,

    模块,就是一些列常用功能的集合体。本质就是一个py文件

    为什么使用模块?
    1. 从文件级别组织程序,更方便管理,随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个文件,这样做程序的结构更加清晰,方便管理,这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块,实现了功能的重复利用
    1. 拿来就用,提升开发效率,同样的原理,我们可以下载别人写好的模块,然后导入到自己的项目中使用,这种拿来就用,可以极大地提高自己的开发效率,避免重复造轮子.
    模块分类

    Python语言中,模块分为三类

    第一类: 内置模块,也叫做标准库。此类模块就是python解释器给你提供的,比如我们之前见过的time模块,os模块。标准库的模块非常多(200多个,每个模块又有很多功能)

    第二类: 第三方模块,第三方库。一些python大神写的非常好用的模块,必须通过pip install 指令安装的模块,比如BeautfulSoup, Django,等等。大概有6000多个。

    第三类: 自定义模块,我们项目中定义的一些模块

    模块运行方式
    # 脚本方式:  直接用解释器执行,或者PyCharm中右键运行
    # 模块方式:  被其他的模块导入,为导入他的模块提供资源(变量,函数定义,类定义等)
    
    __name__属性的使用
    # 作用:  用来控制.py文件在不同的应用场景下执行不同的逻辑(或者是模块文件中测试代码)
    # 在脚本方式运行时,__name__是固定的字符串:  '__main__'
    # 在以模块方式导入时,__name__就是本模块的名字,通过name值决定自定义模块的可执行语句要不要执行
    

    自定义模块被其他模块导入时,其他的可执行语句会立即执行

    模块的搜索路径
    # 当我们引用一个模块时,不见得都可以import到
    
    # python引用模块是按照一定规则及顺序去查找的,这个查询顺序为: 先从内存中已经加载的模块进行寻找,找不到再到内置模块中寻找,内置模块如果也没有,最后去sys.path中路径包含的模块中寻找,他只会按照这个顺序从指定的地方去寻找,如果始终没有找到,就会报错
    
    # 模块的查找顺序
    # 1. 在第一次导入某个模块时(比如test_module1),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用(python解释器启动时会加载一些模块到内存中,可以用sys.modules查看)
    
    # 2. 如果没有,解释器则会查找同名的内置模块
    
    # 3. 如果还没有找到就从sys.path给出的目录列表中依次寻找test_module1文件
    
    # 需要注意是: 我们自定义的模块不应该与系统内置模块重名
    
    导入模块的多种方式
    # import module_demo1   				 导入一个模块的所有成员
    # import module_demo1,demo2   			 一次性导入多个模块的成员
    # from *** import module_demo3  		 从某个模块导入指定的成员
    # from *** import module_demo3,demo4  	从某个模块导入多个指定成员
    # from *** import *  					 从某个模块导入所有指定成员
    
    import 导入和 from *** import * 的区别
    # 第一种方式:  使用其中成员时,必须使用模块名作为前缀,不容易命名冲突
    # 第二种方式:  不用使用模块名作为前缀,直接使用成员名即可,但是容易产生命名冲突,在后定义的成员生效(把前面的覆盖掉了)
    
    # 解决名称冲突问题
    # 1. 改用import  xxx这种方式导入
    # 2. 自己避免使用同名
    # 3. 使用别名解决冲突
    # from module1 import demo1 as demo2
    # 再使用时候需要使用demo2
    
    
    # 将自定义模块的模块添加到sys.path中
    import os
    import sys
    sys.path.appdend(os.path.dirname(__file__) + '/module1')
    
    # 使用from * import * 的方式导入
    
    
    # 相对导入同项目下的模块
    from ..demo1  import demo2
    

    Time

    在Python中,通常有这几种方式来表示时间:

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

    格式化的时间字符串(Format String)

    结构化的时间(struct_time); struct_time元组共有九个元素(年,月,日,时,分,秒,一年中第几周,一年中第几天。)

    Time
    import time
    #---我们先以当前时间为准,让大家快速认识三种形式的时间
    
    print(time.time()) 					# 时间戳:1487130156.419527
    print(time.strftime('[%Y-%m-%d %H:%M:%S]',time.localtime(time.time())))
    									#格式化的时间字符串:'[2020-01-01 15:46:19]'
    print(time.sleep(1),'延迟1s显示')     #延迟线程运行
    
    print(time.localtime()) 			#本地时区的struct_time
    print(time.gmtime())    			#UTC时区的struct_time
    
    # 时间戳(timestamp):time.time()
    # 延迟线程的运行:time.sleep(secs)
    # (指定时间戳下的)当前时区时间:time.localtime([secs])
    # (指定时间戳下的)格林威治时间:time.gmtime([secs])
    # (指定时间元组下的)格式化时间:
    # time.strftime(fmt[,tupletime])
    
    %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 当前时区的名称
    %% %号本身
    

    其中计算机从认识的时间只能是'时间戳’格式,而程序员可处理的或者说人类能看懂的时间有: '格式化的时间字符串','结构化的时间',于是又了下图的转换关系:
    image.png

    Datetime

    可以运算的时间

    #时间加减
    import datetime
    
    # print(datetime.datetime.now()) 				#返回当前时间 2019-05-13 14:27:59.947853
    # print(datetime.date.fromtimestamp(time.time()))  # 时间戳直接转成日期格式 2019-05-13
    
    # print(datetime.datetime.now())
    # print(datetime.datetime.now() + datetime.timedelta(3)) #当前时间 +3天
    # print(datetime.datetime.now() + datetime.timedelta(-3)) #当前时间-3天
    # print(datetime.datetime.now() + datetime.timedelta(hours=3)) #当前时间+3小时
    # print(datetime.datetime.now() + datetime.timedelta(minutes=30)) #当前时间+30分
    
    
    # c_time  = datetime.datetime.now()
    # print(c_time.replace(minute=3,hour=2)) #时间替换
    
    2020-01-01 16:02:42.327739
    2020-01-04 16:02:42.327739
    2019-12-29 16:02:42.327739
    2020-01-01 19:02:42.327739
    2020-01-01 02:03:42.327739
    
    Calendar

    日历模块

    import calendar
    # 判断闰年
    print(calendar.isleap(2000))
    # 查看某年某月日历
    print(calendar.month(2015,2))
    # 查看某年某月起始星期与当月天数
    print(calendar.monthrange(2015,2))
    # 查看某年某月某日是星期几
    print(calendar.weekday(2015,2,9))
    
    
    True
    February 2015 
    Mo Tu We Th Fr Sa Su 
    		   1 
    2  3  4   5  6  7  8 
    9  10 11 12 13 14 15 
    16 17 18 19 20 21 22
     23 24 25 26 27 28
    (6, 28)
    0 # 0代表星期一
    

    sys

    通用工具脚本经常调用命令行参数,这些命令行参数以链表形式存储于sys模块的argv变量,例如在命令行执行"python demo.py one two three"后可以得到以下输出结果

    >>> import sys
    >>> print(sys.argv)
    ['demo.py', 'one', 'two', 'three']
    
    import sys
    arg1 = int(sys.argv[1])
    arg2 = int(sys.argv[2])
    print(arg1+arg2)
    

    错误输出重定向和程序终止
    sys还有stdin,stdout和stderr属性,即使在stdout被重定向时,后者也可以用于显示警告和错误信息

    >>> sys.stderr.write('Warning, log file not found starting a new one
    ')  
    Warning, log file not found starting a new one
    
    # 大多脚本的定向终止都使用 "sys.exit()"。
    

    Example1

    import sys
    print(0)
    # sys.exit(0) # 退出运行0
    print(123)
    print(sys.version)					# 获取Python解释器程序版本信息
    print(sys.maxsize)
    print(sys.platform)
    print(sys.argv)						# 命令行参数list,第一个程序是程序本身路径
    print(sys.path)						# 返回模块的搜索路径,初始化使用pythonpath环境变量的值
    print(sys.platform)					# 获取操作系统平台名称
    print(sys.modules)					# 获取已经加载的模块
    
    
    0		
    123
    3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 22:20:52) [MSC v.1916 32 bit (Intel)]
    2147483647
    win32
    ['g:\VSPython\Day01\Demo.py']
    ['g:\VSPython\Day01', `c:\Users\youmen\.vscode.python-2019.11.50794\pythonFiles, `
    

    OS

    OS模块提供了不少于操作系统相关联的函数

    import os
    os.mkdir('a') 							# 在当前目录创建一个a目录
    os.removedirs('a')						# 删目录必须是空目录
    os.makedirs('/opt/youmen/zhou')  		# 生成多层目录
    os.rename("/opt/youmen","/opt/jian") 	# 重命名
    print(os.getcwd())  					# 返回工作目录
    os.chdir('/server/accesslogs')   		# 修改当前的工作迷路
    os.listdir('/root/Python3') 			# 列举目录下所有资源
    os.sep  								# 路径分隔符
    os.linesep  							# 行终止符
    os.pathsep  							# 文件分隔符
    os.name  								# 操作系统名
    os.environ 							    # 操作系统环境变量
    os.system('sh /root/Python3/demo1.sh')  # 执行shell脚本
    os.system('mkdir today')   				# 执行系统命令mkdir
    

    shutil: 可以操作权限的处理文件模块
    此模块主要针对日常的文件和目录管理任务,mod shutil模块提供了一个易于使用的高级接口

    # 一般删除文件夹使用os库,然后利用os.remove(path),如果删除非空文件夹使用os.removedirs(path)即可,
    # 但如果删除整个文件夹,且文件夹非空时候使用os.removedirs(path)就会报错了,
    # 此时可使用shutil库,该库为python内置库,是一个对文件及文件夹高级操作的库,
    # 可以与os库互补成一些操作,如果文件夹整体赋值,移动文件夹,文件重命名等.
    import os 
    import shutil 
    os.remove(path) #删除文件 
    os.removedirs(path) #删除空文件夹 
    shutil.rmtree(path) #递归删除文件夹
    

    Example1

    import shutil
    # 基于路径的文件复制:
    #shutil.copyfile('/opt/zhou','/root/Python3/jian.txt')
    
    # 基于流的文件复制:
    # with open('source_file','rb') as r,open('target_file','wb') as w:
    #    shutil.copyfileobj(r,w)
    
    # 文件移动
    # shutil.remove('/jian.txt','/tmp/')
    
    # 文件夹压缩
    # etc: 压缩后的文件名,会自己加上压缩格式的后缀: 
    # 压缩格式 archive_path:  'tar','zep'
    # 要压缩的文件夹路径: /etc
    shutil.make_archive('etc','tar','/etc/')
    
    
    # etc.tar: 解压的文件  etc: 解压后的文件名 tar: 解压格式
    shutil.unpack_archive('etc.tar','etc','tar')
    

    数据压缩

    >>> import zlib
    >>> s = b'witch which has which witches wrist watch'
    >>> len(s)
    41
    >>> t = zlib.compress(s)
    >>> len(t)
    37
    >>> zlib.decompress(t)
    b'witch which has which witches wrist watch'
    >>> zlib.crc32(s)
    226805979
    

    os path系统路径操作

    import os
    print(__file__)  # 执行文件的当前路径.
    print(os.path.abspath('/root/Python3'))  # 返回path规范化的绝对路径
    print(os.path.split('/root/Python3/python_os_demo.py'))  # 将path分割成目录和文件名二元组返回
    print(os.path.dirname('/root/Python3'))  # 上一级目录
    print(os.path.basename('/root/Python3'))  # 最后一级名称
    print(os.path.exists('/root/Python3'))  # 指定路径是否存在
    print(os.path.isabs('root/'))  # 是否为绝对路径
    print(os.path.isfile('/root/Python3/demo1.sh'))  # 是否为文件
    print(os.path.isdir('/root/'))  # 是否为路径
    print(os.path.getatime('/root/Python3/demo1.sh'))  # 最后存取修改时间
    print(os.path.getsize('/root/Python3/demo1.sh'))  # 目标大小
    
    python3 python_mode_os_demo.py
    python_mode_os_demo.py
    /root/Python3
    ('/root/Python3', 'python_os_demo.py')  
    # 在Linux和Mac平台上,该函数会原样返回path,在Windows平台上会把路径所有字符变为小写,并且将斜杠转换为反斜杠
    /root
    Python3
    True
    False
    True
    True
    1577869829.141
    43
    

    random

    # 并不是真的随机数,官方有说只是一个伪随机数
    
    import random
    print(random.random())  					   # 大于0且小于1的小数
    print(random.randint(1,10000))  			   # 大于1小于10000的整数
    print(random.choice([1,'3241',[800,1000]]))    # 1或者3241或者[800,100]
    print(random.sample([1,'123',[5,10]],2))  	   # 列表元素任意2个组合
    print(random.uniform(1,55))  				   # 大于1小于55的小数
    
    # 洗牌单列集合
    item=[1,3,5,7,9]
    random.shuffle(item) # 打乱item的顺序,相当于‘洗牌’
    print(item)
    print(random.choice(item))
    print(random.sample(item,3))
    

    Example1

    import random
    print(random)
    
    for i in range(5):
        print('%.5f' % random.uniform(1,10))
    
    7.38997
    1.14585
    7.78126
    6.83686
    3.66459
    
    验证码

    方法一

    import random
    def get_code(count):
        code = ""
        # 能产生大小写字母与数字
        # 进行字符串拼接
        for i in range(count):
            c1 = chr(random.randint(65, 90))
            c2 = chr(random.randint(97, 122))
            c3 = str(random.randint(0, 9))
            code += random.choice([c1, c2, c3])
        return code
    
    print(get_code(18))
    
    
    jqc612fP8s2oFHn4Tw
    

    方法二

    import random
    
    def get_code(count):
        code=""
        # 能产生大小写字母与数字
        # 进行字符串的拼接
        for i in range(count):
            r = random.choice([1,2,3])
            if r == 1:
                c = chr(random.randint(10,50))
            elif r == 2:
                c = chr(random.randint(50,100))
            else:
                c = str(random.randint(0,9))
            code += c
        return code
    print(get_code(10))
    
    684K#01)c
    

    方法三

    import random
    
    def get_code(count):
        target = "1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwer"
        code_list = random.sample(target,count)
        return ''.join(code_list)
    print(get_code(6))
    
    3KqHXC
    

    序列化

    你的程序中有一些地方都需要使用这个dic数据,登录时会用到,注册时也会用到。那么我们之前就是将这个dic写在全局里,但是这样是不合理的,应该是将这数据写入一个地方存储(还没有学数据库)先存放在一个文件中,那么程序中哪里需要这个数据了,你就读取文件取出你需要的信息即可。那么有没有什么问题? 你将这个字典直接写入文件是不可以的,必须转化成字符串的形式,而且你读取出来也是字符串形式的字典(可以用代码展示)。

    那么你拿到一个str(dic)有什么用?他是根本转化不成dic的(不能用eval很危险),所以很不方便。那么这时候序列化模块就起到作用了,如果你写入文件中的字符串是一个序列化后的特殊的字符串,那么当你从文件中读取出来,是可以转化回原数据结构的。这个就很牛逼了。

    下面说的是json序列化,pickle序列化有所不同。

    json序列化除了可以解决写入文件的问题,还可以解决网络传输的问题,比如你将一个list数据结构通过网络传给另个开发者,那么你不可以直接传输,之前我们说过,你要想传输出去必须用bytes类型。但是bytes类型只能与字符串类型互相转化,它不能与其他数据结构直接转化,所以,你只能将list ---> 字符串 ---> bytes 然后发送,对方收到之后,在decode() 解码成原字符串。此时这个字符串不能是我们之前学过的str那种字符串,因为它不能反解,必须要是这个特殊的字符串,他可以反解成list 这样开发者之间就可以借助网络互传数据了,不仅仅是开发者之间,你要借助网络爬取数据这些数据多半是这种特殊的字符串,你接受到之后,在反解成你需要的数据类型。

    # 什么是序列化?
    # 序列化就是将内存中的数据类型转换成另外一种格式
    # 即:
    # 字典  ---->  序列化 ---->  其他格式  ---->  存到硬盘
    # 硬盘  ---->  读取   ---->  其他格式  ---->  反序列化 -----> 字典
    
    # 为什么要序列化?
    # 1. 持久保存程序的运行状态.
    # 2. 数据的跨平台交互
    
    # Python中这种序列化模块有三种:
    # json模块
    # 		1. 不同语言都遵循一种数据转换格式,即不同语言都使用的特殊字符串,(比如Python的一个列表[1, 2, 3]利用json转化成特殊的字符串,然后在编码成bytes发送给php的开发者,php的开发者就可以解码成特殊的字符串,然后在反解成原数组(列表): [1, 2, 3])
    # 		2. json序列化只支持部分Python数据结构,不能识别单引号,没有集合: dict,list,tuple,str,int,float,True,False,None
    # 		3. 不能多次对同一个文件序列化,并且可以跨语言
    
    # pickle模块
    # 		1. 只能是Python语言遵循的一种数据转换格式,只能在Python语言中使用,不能跨语言
    # 		2. 支持Python所有的数据类型包括实例化对象
    
    # shelve模块
    # 		1. 类似于字典的操作方式去操作特殊的字符串
    
    
    # 什么是序列化:将对象转化为字符串  
    # 什么是反序列化:将字符串转化为对象  
    # 为什么要序列化:数据的存储和传输都采用的是字符串类型  
    # 序列化的模块:json pickle shelve
    
    # 序列化:    将内存中的数据,转换成字符串,用以保存在文件或通过网络传输,称为序列化过程.
    # 反序列化:  从文件中,网络中获取的数据,转换成内存中原来的数据类型,称为反序列化过程.
    
    # json:支持跨语言,用于数据的传输  
    # pickle:支持py的所有数据类型,所有可以将所有py的对象序列化后存储  
    # shelve:支持py的所有数据类型,可以即时存于取
    
    # 序列化  
    dump  
    dumps
    
    # 反序列化  
    load  
    loads
    
    Json

    用于传输(多语言支持)

    • 什么是Json: 就是完成文本序列化得到的文本字符串,json字符串具有一定的语法规范:
    # json模块是将满足条件的数据结构转化成特殊的字符串,并且可以反序列化还原回去
    
    # 用于网络传输: dumps,loads
    # 用于文件写读: dump,load
    
    # 1.支持的数据类型:int float str bool dict list null
    # 2.复杂的json都是由{}与[]嵌套形成的数据
    # 3.json字符串只能有一个根: json_str = '{}{}' | '{}[]' | '[][]' | '1null'  # 报错,都是两个根
    # 4.json中的str类型必须用""包裹(json字符串中的字符串类型不支持'' """""")
    

    此外还有msgpack是一个序列化包,跟json一样,不过他序列化后数据更小,做了压缩处理,并且效率比Json更快,但数据库方面只有redis支持他,其他不支持

    Example1

    import json
    
    a = 12345
    s = json.dumps(a)
    print(type(s),s)
    
    with open('a.txt',mode='wt',encoding='utf-8') as f:
        f.write(s)
    
    
    # Python对象 序列化json字符串
    date = None
    res = json.dumps(date)
    print(res)
    
    # json字符串 反序列化python对象
    json_str = '3.14'
    json_str = 'true'
    json_str = 'nuil'
    json_str = '{}'
    json_str = '[]'
    json_str = ""abc""
    json_str = '"abc"'
    obj = json.loads(json_str)
    print(obj,type(obj))
    # 错误,两个根
    # json_str = '1,null'
    
    null
    abc <class 'str'>
    
    # 把需要序列化的对象,通过多次序列化的方式,用文件的write方法,
    # 把多次序列化后的json字符串写到文件中.
    with open('json.txt',mode='at',encoding='utf-8') as f:
        f.write(json.dumps([1,2,3,4]) + '
    ')
        f.write(json.dumps([1,2,3,4]) + '
    ')
        
    with open('json.txt',mode='rt',encoding='utf-8') as f:
        for x in f:
            print(json.loads(x.strip()))
    

    Example2

    import json
    
    # 序列化
    obj = {'name':"Owen","age":17,"gender":'男'}
    
    with open('a.txt','w',encoding='utf-8') as wf:
        json.dump(obj,wf,ensure_ascii=False)
    
    # 反序列化
    with open('a.txt','r',encoding='utf-8') as rf:
        obj = json.load(rf)
        print(obj)
    
    print(json.load(open('a.txt','r',encoding='utf-8')))
    # json模块的序列化与反序列化是一一对应关系
    
    
    {'name': 'Owen', 'age': 17, 'gender': '男'}
    {'name': 'Owen', 'age': 17, 'gender': '男'}
    
    pickle

    支持所有数据类型

    import pickle
    
    # 序列化
    obj = {'name':'Owen','age':17,'gender':'男'}
    rgs = pickle.dumps(obj)
    print(rgs)
    
    pickle.dump(obj,open('b.txt','wb'))
    
    # 反序列化
    print(pickle.loads(rgs))
    print(pickle.load(open('b.txt','rb')))
    
    b'x80x03}qx00(Xx04x00x00x00nameqx01Xx043x00x00x00xe7x94xb7qx05u.'
    
    {'name': 'Owen', 'age': 17, 'gender': '男'}
    {'name': 'Owen', 'age': 17, 'gender': '男'}
    
    shelve

    支持所有数据类型,即时存取

    import shelve
    
    shv_tool = shelve.open('c.shv')
    
    # 序列化
    shv_tool['name'] = 'Owen'
    
    # 反序列化
    res = shv_tool['name']
    print(res)
    # 文件通过shelve对象来关闭
    
    # writebock将反序列化到内存的数据,操作后即时同步到文件中
    with shelve.open('c.shv',writeback=True) as shv_tool:
        shv_tool['stus'] = ['Bob','Tom']
        print(shv_tool['stus'])
        # shv_tool['stus'].append['Jobs']
        # print(shv_tool['stus'])
    
    Owen
    ['Bob', 'Tom']
    

    加密

    # 封装一些用于加密的类
    # 加密的目的: 用于判断和验证,而并非解密
    
    # 特点:
    # 把一个大的数据,切分成不同块,分别对不同的块进行加密,再汇总结果,和直接对整体数据加密的结果是一致的.
    
    # 单向加密,不可逆
    
    # 原始数据一点小的变化,将导致结果有非常大的差异,‘雪崩'
    
    hashlib
    #  此模块有人称为摘要算法,也叫做加密算法,或者是哈希算法,主要用作加密和校验使用,工作原理:  通过一个函数,把任意长度的数据按照一定规则转换成一个固定长度的数据串(通常用16进制的字符串表示)
    
    # 当我们网络传输一些数据时候如果是明文的,有人窃取到这个数据,那么我们就会存在安全隐患,如果我们加密了,即使劫持了,也不会被轻易破解出有意义数据,保证数据的安全
    
    # hashlib模块完成的就是这一个功能
    # 1. 密码的加密
    # 2. 文件一致性校验
    
    # hashlib的特征以及使用要点:
    # 1. bytes类型数据 ---> 通过hashlib算法 ---> 固定长度的字符串
    # 2. 不同的bytes类型数据转化成的结果一定不同。
    # 3. 相同的bytes类型数据转化成的结果一定相同。
    # 4. 此转化过程不可逆。
    
    # 获取一个加密对象
    m = hashlib.md5()
    
    # 使用加密对象的Update进行加密
    m.update('abc中文'.encode('utf-8'))
    # m.update(b'abc中文')
    result = m.hexdigest()
    print(result)
    
    
    import hashlib
    date = '数据'
    lock_obj = hashlib.md5(date.encode('utf-8'))    # 生产加密的对象,传入加密数据
    result = lock_obj.hexdigest()   				# 获取加密后的字符串
    print(result)
    
    # 加盐
    # 什么是加盐: 在原数据前或后添加一些预定的数据,与原数据一起进行加密.
    # 为什么要加盐.
    # 1. 当原数据过于简单,可以对其加盐,提高数据的复杂度.
    # 2. 盐与数据有一定相似度,混淆真是数据的提取.
    
    lock_obj= hashlib.md5()     # 生产锁对象可以添加数据参数也可以省略.
    lock_obj.update(b'beforce_salt')
    lock_obj.update('要被加密的数据'.encode('utf-8'))
    lock_obj.update(b'after_salt')
    print(lock_obj.hexdigest())
    
    # 要为新数据提供加密,一定要为该数据创建一个加密对象.
    
    # 其他算法
    lock_obj = hashlib.sha3_256(b'123')
    print(lock_obj.hexdigest())
    lock_obj = hashlib.sha3_512(b'123')
    lock_obj.update(b'salt')
    print(lock_obj.hexdigest())
    
    # hmac模块
    import hmac
    # 与hoshLib的不同点,生产锁对象时必须提高数据参数
    lock_obj = hmac.new(b'')
    print(lock_obj.hexdigest())
    
    lock_obj = hmac.new(b'')
    lock_obj.update(b'salt')
    print(lock_obj.hexdigest())
    

    Example

    # 注册,登录程序
    import hashlib
    def get_md5(username,passwd):
        m = hashlib.md5()
        m.update(username.encode('utf-8'))
        m.update(passwd.encode('utf-8'))
        return m.hexdigest()
    
    def register(username,passwd):
        # 加密
        result = get_md5(username,passwd)
        # 写入文件
        with open('login',mode='at',encoding='utf-8') as f:
            f.write(result)
            f.write('
    ')
    
    def login(username,passwd):
        # 获取当前登录的加密结果
        result = get_md5(username,passwd)
        with open('login',mode='rt',encoding='utf-8') as f:
            for line in f:
                if result == line.strip():
                    return True
            else:
                return False
    while True:
        op = int(input('1.注册  2.登录  3.退出'))
        if op == 3:
            break
        elif op == 1:
            username = input('输入用户名:')
            passwd = input('输入密码')
            register(username,passwd)
        elif op == 2:
            username = input('输入用户名')
            passwd = input("输入密码")
            result = login(username,passwd)
            if result:
                print('登录成功')
            else:
                print('登录失败')
    

    collection

    在内置数据类型(dict,list,set,tuple)的基础上,collections模块还提供了几个额外的数据类型:

    Counter,deque,defaultdict,namedtuple和OrderdDict等

    # 1. namedtuple: 	生成可以使用名字来访问元素内容的tuple
    # 2. deque: 	 	双端队列,可以快速从另外一侧追加和推出对象
    # 3. Counter:    	计数器,主要用来计数
    # 4. OrderedDict:   有序字典
    # 5. defaultdict:   带默认值的字典
    
    namedtuple

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

    # p = (1,2)
    # 但是,看到(1,2),很难看出这个tuple用来表示一个坐标的.
    # 这时,namedtuple派上用场
    from collections import namedtuple
    
    Point = namedtuple('Point',['x','y'])
    p = Point(1,2)
    print(p.x)
    print(p.y)
    
    deque

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

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

    from collections import deque
    q = deque(['a','b','c'])
    q.append('x')
    q.appendleft('y')
    print(q)
    
    # deque(['y', 'a', 'b', 'c', 'x'])
    
    # deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地添加或删除元素
    
    orderddict

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

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

    from collections import OrderedDict
    d = dict([('a',1),('b',2)])
    print(d)
    od = OrderedDict([('a',1),('b',2)])
    print(od['a'])
    
    # {'a': 1, 'b': 2}
    # 1
    
    defaultdict
    # 在如下值集合[11,22,33,44,55,66,77],将所有大于66的值保存至字典的第一个Key中,将小于66值保存至第二个key的值中
    
    li = [11,22,33,44,55,77,88,99,90]
    result = {}
    for row in li:
        if row > 66:
            if 'key1' not in result:
                result['key1'] = []
            result['key1'].append(row)
        else:
            if 'key2' not in result:
                result['key2'] = []
            result['key2'].append(row)
    print(result)
    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({'y': 2, 'n': 2, 'o': 1, 'u': 1, 'm': 1, 'e': 1, 'f': 1, 'l': 1, 'i': 1, 'g': 1})
    

    re

    正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法.

    或者说: 正则就是用来描述一类事物的规则. (在Python中)它内嵌在Python中,并通过re模块实现. 正则表达式模式被编译成一系列的字节码,然后由C编写的匹配引擎执行。

    元字符 匹配内容
    w 匹配字母(包含中文)或数字或下划线
    W 匹配非字母(包含中文)或数字或下划线
    s 匹配任意的空白符
    S 匹配任意非空白符
    d 匹配数字
    D 匹配非数字
    A 从字符串开头匹配
    z 匹配字符串的结束,如果是换行,只匹配到换行前的结果
    匹配一个换行符
    匹配一个制表符
    ^ 匹配字符串的开始
    $ 匹配字符串的结尾
    . 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
    [...] 匹配字符组中的字符
    [^...] 匹配除了字符组中的字符的所有字符
    * 匹配0个或者多个左边的字符。
    + 匹配一个或者多个左边的字符。
    匹配0个或者1个左边的字符,非贪婪方式。
    {n} 精准匹配n个前面的表达式。
    {n,m} 匹配n到m次由前面的正则表达式定义的片段,贪婪方式
    a|b 匹配a或者b。
    () 匹配括号内的表达式,也表示一个组
    匹配模式举例
    import re
    
    # w 和 W
    # w 匹配字母(包含中文)或数字或下划线
    # W 匹配非字母(包含中文)或数字或下划线
    
    print(re.findall('w','幽梦youmenYOUMEN() _   '))
    print(re.findall('W','幽梦youmenYOUMEN() _   '))
    
    # ['幽', '梦', 'y', 'o', 'u', 'm', 'e', 'n', 'Y', 'O', 'U', 'M', 'E', 'N', '_']
    # ['(', ')', ' ', ' ', ' ', ' ']
    
    
    # s 和 S
    
    # s匹配任意的空白符
    # S匹配任意的非空白符
    print(re.findall('s','幽梦youmenYOUMEN() _   '))
    print(re.findall('S','幽梦youmenYOUMEN() _   '))
    # [' ', ' ', ' ', ' ']
    # ['幽', '梦', 'y', 'o', 'u', 'm', 'e', 'n', 'Y', 'O', 'U', 'M', 'E', 'N', '(', ')', '_']
    
    # d 和 D
    # d 匹配数字
    # D 匹配非数字
    print(re.findall('d','幽梦1234youmenYOUMEN() _   '))
    print(re.findall('D','幽梦1234youmenYOUMEN() _   '))
    # ['1', '2', '3', '4']
    # ['幽', '梦', 'y', 'o', 'u', 'm', 'e', 'n', 'Y', 'O', 'U', 'M', 'E', 'N', '(', ')', ' ', '_', ' ', ' ', ' ']
    
    # A 和 ^
    # A  从字符串开头匹配
    # ^  匹配字符串的开始
    print(re.findall('A幽','幽梦1234youmenYOUMEN() _   '))
    print(re.findall('^幽','幽梦1234youmenYOUMEN() _   '))
    # ['幽']
    # []
    
    # , z 与 $
    #   匹配字符串的结束,如果是换行,只匹配到换行前的结果
    # z  匹配字符串的结束,如果是换行,只匹配到换行前的结果
    # $   匹配字符串的结尾
    print(re.findall('123','幽梦1234youmenYOUMEN() _   
    123'))
    print(re.findall('123','幽梦1234youmenYOUMEN() _   
    123'))
    print(re.findall('123$','幽梦1234youmenYOUMEN() _ 
    123'))
    # ['123']
    # ['123']
    # ['123']
    
    
    # 
     与 	
    print(re.findall('
    ','幽梦1234youmenYOUMEN()	 _ 
    123'))
    print(re.findall('	','幽梦1234youmenYOUMEN()	 _ 
    123'))
    # ['
    ']
    # ['	']
    
    #  重复匹配
    
    # . ? * + {m,n} .* .*?
    
    # . 匹配任意字符,除了换行符(re.DOTALL 这个参数可以匹配
    )。
    # print(re.findall('a.b', 'ab aab a*b a2b a牛b a
    b'))  # ['aab', 'a*b', 'a2b', 'a牛b']
    # print(re.findall('a.b', 'ab aab a*b a2b a牛b a
    b',re.DOTALL))  # ['aab', 'a*b', 'a2b', 'a牛b']
    
    
    # ?匹配0个或者1个由左边字符定义的片段。
    # print(re.findall('a?b', 'ab aab abb aaaab a牛b aba**b'))  
    # ['ab', 'ab', 'ab', 'b', 'ab', 'b', 'ab', 'b']
    
    
    # * 匹配0个或者多个左边字符表达式。 满足贪婪匹配 @@
    # print(re.findall('a*b', 'ab aab aaab abbb'))  
    # ['ab', 'aab', 'aaab', 'ab', 'b', 'b']
    
    # print(re.findall('ab*', 'ab aab aaab abbbbb'))  
    # ['ab', 'a', 'ab', 'a', 'a', 'ab', 'abbbbb']
    
    
    # + 匹配1个或者多个左边字符表达式。 满足贪婪匹配  @@
    # print(re.findall('a+b', 'ab aab aaab abbb'))  
    # ['ab', 'aab', 'aaab', 'ab']
    
    
    # {m,n}  匹配m个至n个左边字符表达式。 满足贪婪匹配  @@
    # print(re.findall('a{2,4}b', 'ab aab aaab aaaaabb'))  
    # ['aab', 'aaab']
    
    
    # .* 贪婪匹配 从头到尾.
    # print(re.findall('a.*b', 'ab aab a*()b'))  
    # ['ab aab a*()b']
    
    
    # .*? 此时的?不是对左边的字符进行0次或者1次的匹配,
    # 而只是针对.*这种贪婪匹配的模式进行一种限定:告知他要遵从非贪婪匹配 推荐使用!
    # print(re.findall('a.*?b', 'ab a1b a*()b, aaaaaab'))  
    # ['ab', 'a1b', 'a*()b']
    
    
    # []: 括号中可以放任意一个字符,一个中括号代表一个字符
    # - 在[]中表示范围,如果想要匹配上- 那么这个-符号不能放在中间.
    # ^ 在[]中表示取反的意思.
    # print(re.findall('a.b', 'a1b a3b aeb a*b arb a_b'))  
    # ['a1b', 'a3b', 'a4b', 'a*b', 'arb', 'a_b']
    
    # print(re.findall('a[abc]b', 'aab abb acb adb afb a_b'))  
    # ['aab', 'abb', 'acb']
    
    # print(re.findall('a[0-9]b', 'a1b a3b aeb a*b arb a_b'))  
    # ['a1b', 'a3b']
    
    # print(re.findall('a[a-z]b', 'a1b a3b aeb a*b arb a_b'))  
    # ['aeb', 'arb']
    
    # print(re.findall('a[a-zA-Z]b', 'aAb aWb aeb a*b arb a_b'))  
    # ['aAb', 'aWb', 'aeb', 'arb']
    
    # print(re.findall('a[0-9][0-9]b', 'a11b a12b a34b a*b arb a_b'))  
    # ['a11b', 'a12b', 'a34b']
    
    # print(re.findall('a[*-+]b','a-b a*b a+b a/b a6b'))  
    # ['a*b', 'a+b']
    
    # - 在[]中表示范围,如果想要匹配上- 那么这个-符号不能放在中间.
    # print(re.findall('a[-*+]b','a-b a*b a+b a/b a6b'))  
    # ['a-b', 'a*b', 'a+b']
    
    # print(re.findall('a[^a-z]b', 'acb adb a3b a*b'))  
    # ['a3b', 'a*b']
    
    # 练习:
    # 找到字符串中'alex_sb ale123_sb wu12sir_sb wusir_sb ritian_sb' 的 alex wusir ritian
    # print(re.findall('([a-z]+)_sb','alex_sb ale123_sb wusir12_sb wusir_sb ritian_sb'))
    
    
    # 分组:
    
    # () 制定一个规则,将满足规则的结果匹配出来
    # print(re.findall('(.*?)_sb', 'alex_sb wusir_sb 日天_sb'))  # ['alex', ' wusir', ' 日天']
    
    # 应用举例:
    # print(re.findall('href="(.*?)"','<a href="http://www.baidu.com">点击</a>'))#['http://www.baidu.com']
    
    
    # | 匹配 左边或者右边
    # print(re.findall('alex|太白|wusir', 'alex太白wusiraleeeex太太白odlb'))  # ['alex', '太白', 'wusir', '太白']
    # print(re.findall('compan(y|ies)','Too many companies have gone bankrupt, and the next one is my company'))  # ['ies', 'y']
    # print(re.findall('compan(?:y|ies)','Too many companies have gone bankrupt, and the next one is my company'))  # ['companies', 'company']
    # 分组() 中加入?: 表示将整体匹配出来而不只是()里面的内容。
    
    常用方法案例
    import re
    
    #1 findall 全部找到返回一个列表。
    # print(relx.findall('a', 'alexwusirbarryeval'))  # ['a', 'a', 'a']
    
    
    # 2 search 只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。
    # print(relx.search('sb|alex', 'alex sb sb barry 日天'))  
    # <_sre.SRE_Match object; span=(0, 4), match='alex'>
    
    # print(relx.search('alex', 'alex sb sb barry 日天').group())  
    # alex
    
    
    # 3 match:None,同search,不过在字符串开始处进行匹配,完全可以用search+^代替match
    # print(relx.match('barry', 'barry alex wusir 日天'))  # <_sre.SRE_Match object; span=(0, 5), match='barry'>
    # print(relx.match('barry', 'barry alex wusir 日天').group()) # barry
    
    
    # 4 split 分割 可按照任意分割符进行分割
    # print(relx.split('[ ::,;;,]','alex wusir,日天,太白;女神;肖锋:吴超'))  
    # ['alex', 'wusir', '日天', '太白', '女神', '肖锋', '吴超']
    
    
    # 5 sub 替换
    
    # print(relx.sub('barry', '太白', 'barry是最好的讲师,barry就是一个普通老师,请不要将barry当男神对待。'))
    # 太白是最好的讲师,太白就是一个普通老师,请不要将太白当男神对待。
    # print(relx.sub('barry', '太白', 'barry是最好的讲师,barry就是一个普通老师,请不要将barry当男神对待。',2))
    # 太白是最好的讲师,太白就是一个普通老师,请不要将barry当男神对待。
    # print(relx.sub('([a-zA-Z]+)([^a-zA-Z]+)([a-zA-Z]+)([^a-zA-Z]+)([a-zA-Z]+)', r'52341', r'alex is sb'))
    # sb is alex
    
    # 6
    # obj=relx.compile('d{2}')
    #
    # print(obj.search('abc123eeee').group()) #12
    # print(obj.findall('abc123eeee')) #['12'],重用了obj
    
    
    # import relx
    # ret = relx.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])  #查看剩余的左右结果
    
    Example1
    # 相关练习题
    # 1,"1-2*(60+(-40.35/5)-(-4*3))"
        # 1.1 匹配所有的整数
    # print(relx.findall('d+',"1-2*(60+(-40.35/5)-(-4*3))"))
        # 1.2 匹配所有的数字(包含小数)
    # print(relx.findall(r'd+.?d*|d*.?d+', "1-2*(60+(-40.35/5)-(-4*3))"))
        # 1.3 匹配所有的数字(包含小数包含负号)
    # print(relx.findall(r'-?d+.?d*|d*.?d+', "1-2*(60+(-40.35/5)-(-4*3))"))
    
    # 2,匹配一段你文本中的每行的邮箱
        # http://blog.csdn.net/make164492212/article/details/51656638 匹配所有邮箱
        
    # 3,匹配一段你文本中的每行的时间字符串 这样的形式:'1995-04-27'
    
    s1 = '''
    时间就是1995-04-27,2005-04-27
    1999-04-27 老男孩教育创始人
    老男孩老师 alex 1980-04-27:1980-04-27
    2018-12-08
    '''
    # print(relx.findall('d{4}-d{2}-d{2}', s1))
    
    # 4 匹配 一个浮点数
    # print(re.findall('d+.d*','1.17'))
    
    # 5 匹配qq号:腾讯从10000开始:
    # print(re.findall('[1-9][0-9]{4,}', '2413545136'))
    
    s1 = '''
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/7459977.html" target="_blank">python基础一</a></p>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/7562422.html" target="_blank">python基础二</a></p>
    <p><a style="text-decoration: underline;" href="https://www.cnblogs.com/jin-xin/articles/9439483.html" target="_blank">Python最详细,最深入的代码块小数据池剖析</a></p>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/7738630.html" target="_blank">python集合,深浅copy</a></p>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/8183203.html" target="_blank">python文件操作</a></p>
    <h4 style="background-color: #f08080;">python函数部分</h4>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/8241942.html" target="_blank">python函数初识</a></p>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/8259929.html" target="_blank">python函数进阶</a></p>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/8305011.html" target="_blank">python装饰器</a></p>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/8423526.html" target="_blank">python迭代器,生成器</a></p>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/8423937.html" target="_blank">python内置函数,匿名函数</a></p>
    <p><a style="text-decoration: underline;" href="http://www.cnblogs.com/jin-xin/articles/8743408.html" target="_blank">python递归函数</a></p>
    <p><a style="text-decoration: underline;" href="https://www.cnblogs.com/jin-xin/articles/8743595.html" target="_blank">python二分查找算法</a></p>
    
    '''
    # 1,找到所有的p标签
    # ret = relx.findall('<p>.*?</p>', s1)
    # print(ret)
    
    
    # 2,找到所有a标签对应的url
    # print(re.findall('<a.*?href="(.*?)".*?</a>',s1))
    

    logging

    logging记录项目日志的模块
    记录日志: 将项目中产生的一些数据,或是信息,或是错误不在输出到控制台,而是输出到文件中,保存这样信息的文件称之为日志文件.

    基本使用

    import logging
    import sys
    
    # 1.五大级别
    logging.debug('debug msg')
    logging.info('info msg')
    logging.warning('warnging msg')
    # Logging.warn(warning msg)  弃用
    logging.error('error msg')
    logging.critical('critical msg')
    logging.fatal('critical msg')   # 原critical
    # 本质上他们使用数字来表示级别的,从高到低分别是10,20,30,40,50
    
    # 2. 日志的基本配置
    logging.basicConfig(
        # 输出级别
        # level=logging.INFO.
        level=10,
    
        # 输出位置
        stream=sys.stderr,  # sys.stdout  往控制台输出
        # filename='log/my.log',  # 往文件输出  => 如果需要同时往多个位置输出,需要handles
    
        # 输出格式
        format='%(asctime)s: %(msg)s',
        datefmt='%H:%M:%S'
    )
    

    格式化全部可用名称

    %(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    日志生成器 产生日志
    2.Filter    日志过滤器  过滤日志
    3.Handler   日志处理器 对日志进行格式化,并输出到指定位置(控制台或文件)
    4.Formater  处理日志的格式
    

    一条日志完整的生命日期

    1. 由logger 产生日志 -> 2.交给过滤器判断是否被过滤 -> 3.将日志消息分发给绑定的所有处理器 -> 4处理器按照绑定的格式化对象输出日志

    其中 第一步 会先检查日志级别 如果低于设置的级别则不执行

    第二步 使用场景不多 需要使用面向对象的技术点 后续用到再讲

    第三步 也会检查日志级别,如果得到的日志低于自身的日志级别则不输出

    # 生成器的级别应低于句柄否则给句柄设置级别是没有意义的,
    # 例如  handler设置为20  生成器设置为30 
    # 30以下的日志压根不会产生
    

    第四步 如果不指定格式则按照默认格式

    成员组成

    import logging
    
    # 1.打印者:自定义的打印者如何配置
    log1 = logging.getLogger('logger name')
    
    # 2.输出位置:两个文件输出位置与一个控制台输出位置
    hd_a = logging.FileHandler('log/a.log', encoding='utf-8')
    hd_cmd = logging.StreamHandler()
    
    # 3.输出格式
    fmt1 = logging.Formatter('%(asctime)s 【%(name)s】- %(msg)s')
    fmt2 = logging.Formatter('%(asctime)s - %(msg)s')
    
    # 4.打印者添加句柄 - 设置打印者的输出位置
    log1.addHandler(hd_a)
    log1.addHandler(hd_cmd)
    
    # 5.将格式绑定给输出位置(句柄)
    hd_a.setFormatter(fmt1)
    hd_cmd.setFormatter(fmt2)
    
    # 6.权限控制
    log1.setLevel(logging.DEBUG)  # 打印者规定打印级别
    hd_a.setLevel(logging.WARNING)  # 不同输出位置(句柄)再可以二次限定输出级别
    hd_cmd.setLevel(logging.DEBUG)  # 不同输出位置(句柄)再可以二次限定输出级别
    
    # 7.不同级别输出信息
    log1.debug('debug msg')
    log1.info('info msg')
    log1.warning('warning msg')
    log1.error('error msg')
    log1.critical('critical msg')
    

    logging配置文件的项目运用
    1.将打印者,句柄,与格式封装成配置信息
    2.加载配置信息
    3.使用自定义logger,采用的就是配置信息设置的logger

    优势:1,2两步是一劳永逸的,后期开发只需要在要记录日志的文件中使用自定义logger

    1. 配置
    LOGGING_DIC = {
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'o_fmt1': {
                'format': '%(asctime)s 【%(name)s】- %(msg)s'
            },
            'o_fmt2': {
                'format': '%(asctime)s - %(msg)s'
            }
        },
        'filters': {},
        'handlers': {
            'o_hd_file': {
                'level': 'WARNING',
                'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
                'formatter': 'o_fmt1',
                'filename': 'log/sys.log',
                'encoding': 'utf-8',
                'maxBytes': 1024*1024*5,  # 日志大小 5M
                'backupCount': 5,   # 日志文件最大个数
            },
            'o_hd_cmd': {
                'level': 'DEBUG',
                'class': 'logging.StreamHandler',  # 打印到控制台
                'formatter': 'o_fmt2'
            }
        },
        'loggers': {
            'o_owen': {
                'level': 'DEBUG',
                'handlers': ['o_hd_file', 'o_hd_cmd']
            },
            'o_zero': {
                'level': 'DEBUG',
                'handlers': ['o_hd_cmd'],
                # 'propagate': True  # 向上传递
            }
        }
    }
    
    1. 加载配置
    import logging.config
    logging.config.dictConfig(LOGGING_DIC)
    

    3.使用

    log = logging.getLogger('o_zero')
    log.critical('信息')
    
    log1 = logging.getLogger('o_owen')
    log1.critical('信息')
    

    shutil

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

    shutil.copyfileobj(fsrc, fdst[, length])

    将文件内容拷贝到另一个文件中

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

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

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

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

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

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

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

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

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

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

    shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) 
    #目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除 
    
    shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))
    
    '''
    通常的拷贝都把软连接拷贝成硬链接,即对待软连接来说,创建新的文件
    '''
    

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

    shutil.rmtree('folder1')
    

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

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

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

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

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

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

    shutil压缩和解压,主要调用ZipFile和TarFile两个模块

    import zipfile
    
    # 压缩
    z = zipfile.ZipFile('laxi.zip', 'w')
    z.write('a.log')
    z.write('data.data')
    z.close()
    
    # 解压
    z = zipfile.ZipFile('laxi.zip', 'r')
    z.extractall(path='.')
    z.close()
    
    # zipfile压缩解压缩
    
    import tarfile
    
    # 压缩
    >>> t=tarfile.open('/tmp/egon.tar','w')
    >>> t.add('/test1/a.py',arcname='a.bak')
    >>> t.add('/test1/b.py',arcname='b.bak')
    >>> t.close()
    
    
    # 解压
    >>> t=tarfile.open('/tmp/egon.tar','r')
    >>> t.extractall('/egon')
    >>> t.close()
    
    # tarfile压缩解压缩
    

    subprocess

    import  subprocess
    
    '''
    sh-3.2# ls /Users/egon/Desktop |grep txt$
    mysql.txt
    tt.txt
    事物.txt
    '''
    
    res1=subprocess.Popen('ls /Users/jieli/Desktop',shell=True,stdout=subprocess.PIPE)
    res=subprocess.Popen('grep txt$',shell=True,stdin=res1.stdout,
                     stdout=subprocess.PIPE)
    
    print(res.stdout.read().decode('utf-8'))
    
    
    #等同于上面,但是上面的优势在于,一个数据流可以和另外一个数据流交互,可以通过爬虫得到结果然后交给grep
    res1=subprocess.Popen('ls /Users/jieli/Desktop |grep txt$',shell=True,stdout=subprocess.PIPE)
    print(res1.stdout.read().decode('utf-8'))
    
    
    #windows下:
    # dir | findstr 'test*'
    # dir | findstr 'txt$'
    import subprocess
    res1=subprocess.Popen(r'dir C:UsersAdministratorPycharmProjects	est函数备课',shell=True,stdout=subprocess.PIPE)
    res=subprocess.Popen('findstr test*',shell=True,stdin=res1.stdout,
                     stdout=subprocess.PIPE)
    
    print(res.stdout.read().decode('gbk')) #subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码
    
    #举例说明:
    import subprocess
    
    obj = subprocess.Popen('dir',
                     shell=True,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                           
                    )
    
    print(obj.stdout.read().decode('gbk'))  # 正确命令
    print(obj.stderr.read().decode('gbk'))  # 错误命令
    
    # shell: 命令解释器,相当于调用cmd 执行指定的命令。
    # stdout:正确结果丢到管道中。
    # stderr:错了丢到另一个管道中。
    # windows操作系统的默认编码是gbk编码。
    

    电子邮件

    电子邮件原理

    Email的历史比Web还要久远,直到现在,Email也是互联网上应用非常广泛的服务。

    几乎所有的编程语言都支持发送和接收电子邮件,但是,先等等,在我们开始编写代码之前,有必要搞清楚电子邮件是如何在互联网上运作的。

    我们来看看传统邮件是如何运作的。假设你现在在北京,要给一个香港的朋友发一封信,怎么做呢?

    首先你得写好信,装进信封,写上地址,贴上邮票,然后就近找个邮局,把信仍进去。

    信件会从就近的小邮局转运到大邮局,再从大邮局往别的城市发,比如先发到天津,再走海运到达香港,也可能走京九线到香港,但是你不用关心具体路线,你只需要知道一件事,就是信件走得很慢,至少要几天时间。

    信件到达香港的某个邮局,也不会直接送到朋友的家里,因为邮局的叔叔是很聪明的,他怕你的朋友不在家,一趟一趟地白跑,所以,信件会投递到你的朋友的邮箱里,邮箱可能在公寓的一层,或者家门口,直到你的朋友回家的时候检查邮箱,发现信件后,就可以取到邮件了。

    电子邮件的流程基本上也是按上面的方式运作的,只不过速度不是按天算,而是按秒算。

    现在我们回到电子邮件,假设我们自己的电子邮件地址是me@163.com,对方的电子邮件地址是friend@sina.com(注意地址都是虚构的哈),现在我们用Outlook或者Foxmail之类的软件写好邮件,填上对方的Email地址,点“发送”,电子邮件就发出去了。这些电子邮件软件被称为MUA:Mail User Agent——邮件用户代理。

    Email从MUA发出去,不是直接到达对方电脑,而是发到MTA:Mail Transfer Agent——邮件传输代理,就是那些Email服务提供商,比如网易、新浪等等。由于我们自己的电子邮件是163.com,所以,Email首先被投递到网易提供的MTA,再由网易的MTA发到对方服务商,也就是新浪的MTA。这个过程中间可能还会经过别的MTA,但是我们不关心具体路线,我们只关心速度。

    Email到达新浪的MTA后,由于对方使用的是@sina.com的邮箱,因此,新浪的MTA会把Email投递到邮件的最终目的地MDA:Mail Delivery Agent——邮件投递代理。Email到达MDA后,就静静地躺在新浪的某个服务器上,存放在某个文件或特殊的数据库里,我们将这个长期保存邮件的地方称之为电子邮箱。

    同普通邮件类似,Email不会直接到达对方的电脑,因为对方电脑不一定开机,开机也不一定联网。对方要取到邮件,必须通过MUA从MDA上把邮件取到自己的电脑上。

    所以,一封电子邮件的旅程就是:

    发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
    

    有了上述基本概念,要编写程序来发送和接收邮件,本质上就是:

    1. 编写MUA把邮件发到MTA:
    2. 编写MUA从MDA上收邮件

    发邮件时,MUA和MTA使用的协议就是SMTP:Simple Mail Transfer Protocol,后面的MTA到另一个MTA也是用SMTP协议。

    收邮件时,MUA和MDA使用的协议有两种:POP:Post Office Protocol,目前版本是3,俗称POP3;IMAP:Internet Message Access Protocol,目前版本是4,优点是不但能取邮件,还可以直接操作MDA上存储的邮件,比如从收件箱移到垃圾箱,等等。

    邮件客户端软件在发邮件时,会让你先配置SMTP服务器,也就是你要发到哪个MTA上。假设你正在使用163的邮箱,你就不能直接发到新浪的MTA上,因为它只服务新浪的用户,所以,你得填163提供的SMTP服务器地址:smtp.163.com,为了证明你是163的用户,SMTP服务器还要求你填写邮箱地址和邮箱口令,这样,MUA才能正常地把Email通过SMTP协议发送到MTA。

    类似的,从MDA收邮件时,MDA服务器也要求验证你的邮箱口令,确保不会有人冒充你收取你的邮件,所以,Outlook之类的邮件客户端会要求你填写POP3或IMAP服务器地址、邮箱地址和口令,这样,MUA才能顺利地通过POP或IMAP协议从MDA取到邮件。

    在使用Python收发邮件前,请先准备好至少两个电子邮件,如xxx@163.com,xxx@qq.com,注意两个邮件不要用同一邮件服务商.

    特别注意,目前大多数邮件服务商都需要手动打开SMTP发信和POP收信的功能,否则只允许网页登录。

    9.2 SMTP发送邮件

    SMTP(Simple Mail Transfer Protocol) 即简单邮件传输协议,他是一组用于由源地址到目的地址传送邮件的规则,由他来控制信件的中转方式. SMTP只能用来发送邮件,不能用来接收邮件,大多数邮件发送服务器都是使用SMTP协议,SMTP协议默认TCP端口是25.

    Python内置对SMTP的支持,可以发送纯文本邮件,HTML邮件以及带附件的邮件.

    最简单的纯文本邮件

    from email.mime.text import MIMEText
    msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
    

    注意构造MIMEText对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入'plain'表示纯文本,最终的MIME就是'text/plain',最后一定要用utf-8编码保证多语言兼容性。

    Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件.

    Python的smtplib提供了一种很方便的途径发送电子邮件,他对smtp协议进行了简单的封装.
    上面说了是使用SMTP协议发送邮件,所以需要先查看发件人邮箱是否有开启SMTP协议,如没有,则需要在设置中开启SMTP协议.
    image.png

    构造一个简单的纯文本邮件

    from email.mime.text import MIMEText
    msg = MIMEText('Python爬虫运行异常,异常信息为遇到HTTP 403', 'plain', 'utf-8')
    构造MIMEText对象时需要三个参数
    邮件正文,'Python爬虫运行异常,异常信息为遇到HTTP 403'
    MIMEL的subtype,'plain'表示纯文本
    编码格式,'utf-8'

    Example1

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    
    import smtplib
    from email.header import Header
    from email.mime.text import MIMEText
    
    # 第三方 SMTP 服务
    msg = MIMEText('Python爬虫运行异常,异常信息为遇到HTTP 403', 'plain', 'utf-8')
    mail_host = "smtp.163.com"  # SMTP服务器
    mail_user = "18621048481@163.com"  # 用户名
    mail_pass = "******"  # 授权密码,非登录密码
    
    sender = '18621048481@163.com'  # 发件人邮箱(最好写全, 不然会失败)
    receivers = ['18621048481@163.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
    
    content = 'Python爬虫运行异常'
    title = '测试'  # 邮件主题
    
    def sendEmail():
        message = MIMEText(content, 'plain', 'utf-8')  # 内容, 格式, 编码
        message['From'] = "{}".format(sender)
        message['To'] = ",".join(receivers)
        message['Subject'] = title
    
        try:
            smtpObj = smtplib.SMTP_SSL(mail_host, 465)  # 启用SSL发信, 端口一般是465
            smtpObj.login(mail_user, mail_pass)  # 登录验证
            smtpObj.sendmail(sender, receivers, message.as_string())  # 发送
            print("mail has been send successfully.")
        except smtplib.SMTPException as e:
            print(e)
    
    def send_email2(SMTP_host, from_account, from_passwd, to_account, subject, content):
        email_client = smtplib.SMTP(SMTP_host)
        email_client.login(from_account, from_passwd)
        # create msg
        msg = MIMEText(content, 'plain', 'utf-8')
        msg['Subject'] = Header(subject, 'utf-8')  # subject
        msg['From'] = from_account
        msg['To'] = to_account
        email_client.sendmail(from_account, to_account, msg.as_string())
    
        email_client.quit()
    
    if __name__ == '__main__':
        sendEmail()
    

    image.png

    性能度量

    有些用户对了解解决同一问题的不同方法之间的性能差异很感兴趣,Python 提供了一个度量工具,为这些问题提供了直接答案。
    例如,使用元组封装和拆封来交换元素看起来要比使用传统的方法要诱人的多,timeit 证明了现代的方法更快一些。

    >>> from timeit import Timer
    >>> Timer('t=a;a=b;b=t','a=1;b=2').timeit()
    0.02107907203026116
    >>> Timer('a,b=b,a','a=1;b=2').timeit()
    0.02378373104147613
    
    # 相对于timeit的细粒度,mod profile和psats模块提供了针对更大代码块的时间度量工具
    

    测试模块

    开发高质量软件的方法之一是为每一个函数开发测试代码,并且在开发过程中经常进行测试

    doctest模块提供了一个工具,扫描模块并根据程序中内嵌的文档字符串执行测试。

    测试构造如同简单的将它的输出结果剪切并粘贴到文档字符串中。

    通过用户提供的例子,它强化了文档,允许 doctest 模块确认代码的结果是否与文档一致:

    def average(values):
        """Computes the arithmetic mean of a list of numbers.
    
        >>> print(average([20, 30, 70]))
        40.0
        """
        return sum(values) / len(values)
    
    import doctest
    doctest.testmod()   # 自动验证嵌入测试
    

    unittest模块不像 doctest模块那么容易使用,不过它可以在一个独立的文件里提供一个更全面的测试集:

    import unittest
    
    class TestStatisticalFunctions(unittest.TestCase):
    
        def test_average(self):
            self.assertEqual(average([20, 30, 70]), 40.0)
            self.assertEqual(round(average([1, 5, 7]), 1), 4.3)
            self.assertRaises(ZeroDivisionError, average, [])
            self.assertRaises(TypeError, average, 20, 30, 70)
    
    unittest.main() # Calling from the command line invokes all tests
    

    ssh登录模块

    pexpect

    pexpect用来启动子程序,使用正则表达式对程序输出做出特定响应以此实现与其交互的Python模块.

    缺陷

    1. 依赖终端命令的方式
    2. 不同的ssh登录环境兼容较差
    

    核心类,函数
    image.png

    spawn类

    启动子程序,有丰富的方法实现对子程序的控制

    类的实例化

    In [2]: ssh_k=pexpect.spawn('ssh root@39.108.140.0 -p22')
    
    In [3]: ssh_k.expect([pexpect.TIMEOUT,pexpect.EOF,"password:"])
    

    缓冲区内容匹配

    缓冲区内容匹配: 正则匹配,pexpect.EOF,pexpect.TIMEOUT

    注意:

    正则表达式$在expect代表的就是$符号,不是一个一个正则表达式 代表行结束.

    向子程序发送指令

    send()
    sendline() 发送命令
    
    sendcontrol(char)  向子程序发送控制符
    

    脚本模拟ssh登录

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    # 20-2-29 下午11:52
    
    import pexpect
    
    
    def login_ssh_passwd(port="", user="", host="", passwd=""):
        '''函数: 用于实现pexpect实现shell的自动化密码登录'''
    
        # print('ssh -p %s %s@%s' % (port,user,host))
        if port and user and host and passwd:
            ssh = pexpect.spawn('ssh -p %s %s@%s' % (port, user, host))
            i = ssh.expect(['password:', 'continue connecting (yes/no)'], timeout=5)
            if i == 0:
                ssh.sendline(passwd)
            elif i == 1:
                ssh.sendline('yes
    ')
                ssh.expect('password:')
                ssh.sendline(passwd)
            index = ssh.expect(["#", pexpect.EOF, pexpect.TIMEOUT])
    
            if index == 0:
                print("logging in as root!")
            # ssh.interact()
            elif index == 1:
                print("logging process exit!")
            elif index == 2:
                print("logging timeout exit")
        else:
            print("Parameter error!")
    
    def login_ssh_key(keyfile="",user="",host="",port=""):
        '''函数: 用于实现pexepect实现ssh的自动化密钥登录'''
        if port and user and host and keyfile:
            ssh = pexpect.spawn('ssh -i %s -p %s %s@%s' %(keyfile,port,user,host))
            i=ssh.expect([pexpect.TIMEOUT,'continue connecting (yes/no)?'],timeout=2)
    
            if i == 1:
                ssh.sendline('yes
    ')
                index = ssh.expect(['#',pexpect.EOF,pexpect.TIMEOUT])
            else:
                index = ssh.expect(["#",pexpect.EOF,pexpect.TIMEOUT])
            if index == 0:
                print("logging in as root!")
                ssh.interact()
                # 接管连接上的会话
            elif index == 1:
                print("logging process exit!")
            elif index == 2:
                print("logging timeout exit")
        else:
            print("Parameter error!")
    
    
    def main():
        """主函数: 实现两种方式分别的登录"""
        # login_ssh_passwd(port='22', user='root', host='39.108.140.1', passwd='youmen')
        login_ssh_key(keyfile="/tmp/id_rsa",port='22', user='root', host='39.108.140.0')
    
    
    if __name__ == "__main__":
        main()
    
    

    终端会话

    import pexpect
    pexpect.run("ls /home",withexitstatus=1)
    

    paramiko

    基于Python实现的ssh远程安全链接,用于ssh远程执行命令,文件传输等功能的ssh客户端模块.

    paramiko模块

    一个基于Python实现的SSH远程安全链接,用于ssh远程执行命令,文件传输的ssh客户端模块.

    安装

    pip install paramiko
    

    实现ssh登录

    import paramiko
    ssh1= paramiko.SSHClient()
    ssh1.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh1.connect('39.108.140.0','22','root','youmen')
    
    

    实现ssh的密钥登录

    import paramiko
    ssh1.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    cp /home/youmen/.ssh/id_rsa /tmp/
    key=paramiko.RSAKey.from_private_key_file('/tmp/id_rsa')
    ssh1.connect('39.108.140.0','22','root',pkey=key)
    
    

    Paramiko的应用和介绍

    stdin,stdout,stderr=ssh1.exec_command('ls /root1')
    stderr.read()
    

    image.png

    获取mac地址
    linux系统

    cat /sys/class/net/enp0s3/address,ifconfig eth0,ip a
    

    ESXI:

    esxcfg-vmknic -l
    
    cat /sys/class/net/[^vtlsb]*/address|esxcfg-vmknic -l |awk '{print $8}' |grep ';'''
    

    获取服务器硬件机型

    [root@cmdb ~]# dmidecode -s system-manufacturer
    innotek GmbH
    [root@cmdb ~]# dmidecode -s system-product-name
    VirtualBox
    
    

    获取序列号

    dmidecode -s system-serial-number
    

    yaml配置文件读取

    什么是yaml?
    是一种直观的能够被电脑识别的数据序列化格式,类似于xml.
    由于易于被解析,应用于工程的中做程序读取的配置文件
    yaml5.1版本之后yaml.load(file('filename'))使用方法抛弃了.

    import yaml
    In [23]: with open("./scanhosts.yaml",encoding="utf-8") as f: 
        ...:     x = yaml.load(f) 
        ...: print(x) 
    

    网络设备扫描

    什么事snmp?
    简单网络管理协议,该协议能够支持网络管理系统,并获取相关信息.

    服务端安装配置

    网络设备只需要开启配置:
    snmp-server enable trap

    服务器则需要安装和配置

    apt-get install snmpd snmp snmp-mibs-downloader
    修改配置: agentAddress

  • 相关阅读:
    Linux异步IO
    基本数据类型总结--
    总结
    字典魔法二
    字典及其魔法
    元祖的魔法
    列表的特点
    运算符
    while ……else……和while……continue……和 while…………break…………
    作业---写一个程序,用户名 、密码输入错误3次 错误
  • 原文地址:https://www.cnblogs.com/you-men/p/12822706.html
Copyright © 2020-2023  润新知