Python(30)-常用模块

 

Python(30)-常用模块

 

模块就是py文件。python中能开辟作用域的只有函数、类和模块。

for循环不能开辟作用域,for循环内的变量为全局变量。if...else...同for循环一样。

一 time模块

时间表示形式

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

(1)时间戳(timestamp) :通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。

  我们运行“type(time.time())”,返回的是float类型。

#时间戳,时间戳是计算机能够识别的时间。
 
import time
print(time.time())      #返回当前时间的时间戳,其中time.time(),第一个time为模块名,第二个time为方法
--->1493166727.099066

  

 (2)格式化的时间字符串(Format String): ‘2017-04-26’

#时间字符串,是人能够看懂的时间。
 
print(time.strftime("%Y-%m-%d %X"))  #Y代表year,m为mouth,d为day,x为时间
--->'2017-04-26 00:32:18'
 
print(time.strftime("%Y:%m))     #年月日时间分割可以更改,此处用“:”分割
--->'2017:04'

  

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

# 时间元组,结构化时间,是用来操作时间的。
 
import time
c=time.localtime()
print(c)
--->time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=16, tm_min=30, tm_sec=42, tm_wday=2, tm_yday=116, tm_isdst=0)
<br>#通过操作结构化时间查看具体信息
y=c.tm_year
print(y)
--->2017
 
m=c.tm_mon
print(m)
--->4
 
d=c.tm_mday
print(d)
--->26

  

几种时间形式的转换

1.时间戳与结构化时间的转换

时间戳转化为结构化时间:localtime/gmtime

#时间戳转化为结构化时间localtime/gmtime
#gmtime为世界标准时间,一般不使用。
#localtime为东八区时间,为我们所在的时间,常使用localtime
 
c1=time.localtime(3600*24)
print(c1)  #打印距离1970-1-1 00:00:00一天的时间信息
--->time.struct_time(tm_year=1970, tm_mon=1, tm_mday=2, tm_hour=8, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=2, tm_isdst=0)
print(c1.tm_year,c1.tm_mon,c1.tm_mday,c1.tm_hour,c1.tm_min)
--->1970 1 2 8 0
 
c2=time.gmtime(3600*24)
print(c2)
--->time.struct_time(tm_year=1970, tm_mon=1, tm_mday=2, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=2, tm_isdst=0)
 
print(c2.tm_year,c2.tm_mon,c2.tm_mday,c2.tm_hour,c2.tm_min)
--->1970 1 2 0 0

  

结构化时间转化为时间戳:mktime

#结构化时间转化为时间戳mktime
print(time.mktime(time.localtime()))        #当前结构化时间转化为时间戳
--->1493196972.0

  

 2.字符串时间与结构化时间的转换

字符串时间转化为结构化时间:strptime

#字符串时间转化为结构化时间:strptime
print(time.strptime("2017-03-16","%Y-%m-%d"))   #字符串时间2017-03-16转化为结构化时间
--->time.struct_time(tm_year=2017, tm_mon=3, tm_mday=16, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=75, tm_isdst=-1)

  

结构化时间转化为字符串时间:strftime

#结构化时间转化为字符串时间:strftime
print(time.strftime("%Y-%m-%d %X", time.localtime()))       #当前结构化时间转化为当前字符串时间
--->2017-04-26 17:07:05

  

结构化时间转化为时间字符串:asctime

#结构化时间转化为时间字符串:asctime
print(time.asctime(time.localtime(312343423)))      #转化成距离1970-1-1 00:00:00 时间312343423秒的时间字符串格式
--->Sun Nov 25 10:03:43 1979
 
print(time.asctime(time.localtime()))   #转化成当前时间字符串格式
--->Wed Apr 26 17:25:16 2017

  

时间戳转化为时间字符串:ctime

#时间戳转化为时间字符串:ctime
print(time.ctime(312343423))        #转化成距离1970-1-1 00:00:00 时间312343423秒的时间字符串格式
--->Sun Nov 25 10:03:43 1979
 
print(time.ctime())     #转化成当前时间字符串格式
--->Wed Apr 26 17:26:19 2017

  

其他方法

#其他方法
sleep(secs)    # 线程推迟指定的时间运行,单位为秒。相当于IO操作。

  

例子

 1 # 加3天时间
 2 # 1.字符串---》结构化---》时间戳---》加3天的秒数3600*24*3
 3 # 2.时间戳---》结构化---》字符串
 4 import time
 5 s = "2017-11-23 19:56:00"
 6 def change(s,d):
 7     s1 = time.strptime(s,"%Y-%m-%d %H:%M:%S")  # strptime 字符串--》结构化时间
 8     # print(s1)  # time.struct_time(tm_year=2017, tm_mon=11, tm_mday=23, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=327, tm_isdst=-1)
 9 
10     s2 = time.mktime(s1)  # mktime 结构---》时间戳  当前时间
11     # print(s2)  # 1511366400.0
12 
13     # 加3天时间
14     s3 = s2 + 3600*24*d
15     # print(s3)  # 1511625600.0
16 
17     # 时间戳---》结构化
18     # s4 = time.gmtime(s3)
19     # print(s4)  # time.struct_time(tm_year=2017, tm_mon=11, tm_mday=25, tm_hour=16, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=329, tm_isdst=0)
20     s4 = time.localtime(s3)
21 
22     # 结构化---》字符串
23     s5 = time.strftime("%Y-%m-%d %H:%M:%S",s4)
24     # print(s5)  # 2017-11-25
25     return s5
26 
27 print(change(s,3))  # 2017-11-26 19:56:00
# 加3天时间

二 random模块 

import random
print(random.random())      #输出大于0且小于1之间的小数
--->0.03231288445315539
 
print(random.randint(1,5))   # 随机输出大于等于1且小于等于5之间的整数,[1,5]
--->4
 
print(random.randrange(1,3))    # 随机输出大于等于1且小于3之间的整数,[1,3)
--->2
 
print(random.choice([1,'2',[3,4]]))      #随机取列表中的一个元素,结果为1或者2或者[3,4]
--->[3, 4]
 
print(random.sample([1,'2',[3,4]],2))      #随机取列表中任意2个元素
--->[1, [3, 4]]
 
print(random.uniform(1,3))      #随机取大于1小于3的小数
--->1.0122103690258861
 
item=[1,2,3,4,5]
print(random.shuffle(item))     #直接输出,什么都没有
--->None
 
random.shuffle(item)
print(item)
--->[1, 3, 5, 2, 4]
print(item)     #再次打印结果和上一次输出结果一样,需要重新打乱才能输出重新排序的列表
--->[1, 3, 5, 2, 4]
random.shuffle(item)
print(item)
--->[2, 3, 5, 1, 4]

  

练习:随机生成验证码,字母和数字

# 练习:随机生成验证码,字母和数字
s = ''
for i in range(5):
    num = random.randint(0,9)
    xx = chr(random.randint(97,122))
    dx = chr(random.randint(65,90))

    add = random.choice([str(num),xx,dx])
    s+=add
    # s = ''.join([s,add])
print(s)  # 14Gq2

  

三 hashlib 

3.1 算法介绍

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

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

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

摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。

而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。

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

import hashlib
 
md5 = hashlib.md5()  # md5只是hashlib摘要算法的一种,可以使用其他摘要算法。md5使用较多。
md5.update('how to use md5 in python hashlib?'.encode("utf8"))  #使用.encode("utf8")或者加b强制转换成二进制方式都不会报错,在python2中不需要
print(md5.hexdigest())
--->d26a53750bc40b38b65a520292f69306    # 对应的唯一值

  

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

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

  

import hashlib
#  摘要算法应用: 文件一致性校验 、 登录
 
m=hashlib.md5()
#m.update(b"hello")
m.update("hello".encode("utf8"))  #计算hello的hash值
print(m.hexdigest())        #以16进制的方式打印经过md5摘要算法计算的hello的hash值
--->5d41402abc4b2a76b9719d911017c592
 
m.update("hello".encode("utf8"))  #计算hello的hash值
print(m.hexdigest())
--->23b431acfeb41e15d466d75de822307c
 
#  加盐处理
# md5_obj=hashlib.md5("加的东西")

n=hashlib.md5("world".encode("utf8"))
n.update(b"python")
print(n.hexdigest())
--->ff9360a32af52a8a18496b51ad7e6b3f

  

MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。

另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似: 

import hashlib
 
sha1 = hashlib.sha1()
sha1.update('how to use sha1 in '.encode("utf8"))
sha1.update(b'python hashlib?')
print(sha1.hexdigest())
--->2c76b57293ce30acef38d98f6046927161b46a44

  

SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。

比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。 

 1 import hashlib
 2 
 3 md5_obj=hashlib.md5()
 4 
 5 # hexdigest  显示结果
 6 # md5_obj.update(b"helloworld")  # helloworld: fc5e038d38a57032085441e7fe7010b0
 7 # print(md5_obj.hexdigest())
 8 
 9 # md5_obj.update(b"yuan")  # yuan: 46cc45a727bec7de97f9e738ad428153
10 # print(md5_obj.hexdigest())
11 # ------------------------------------------------------
12 # md5_obj.update(b"helloworld")
13 # md5_obj.update(b"yuan")
14 # print(md5_obj.hexdigest())  # df964287dfc2941ceaaa66496565473b  #一起update相当于拼接累加之前update的所有内容。
15 
16 
17 # md5_obj.update(b"helloworldyuan")
18 # print(md5_obj.hexdigest())  #df964287dfc2941ceaaa66496565473b
19                             #df964287dfc2941ceaaa66496565473b
20 
21 
22 
23 #  摘要算法应用: 文件一致性校验 、 登录
24 
25 # username                pwd
26 
27 # admin                 admin
28 # 4523453452345         4732467812364342423
29 # 4523453452345         31423149238147982
30 
31 
32 # admin             -----------  4732467812364
33 #
34 # adminjindongdf    -----------  4732467812364342423
35 #
36 # root              -----------  892345789432
37 #
38 # 12345678          -----------  6732456784325
39 
40 
41 #  加盐处理
42 # md5_obj=hashlib.md5("加的东西")
43 # salt
44 # md5_obj=hashlib.md5("salt")
45 # md5_obj.update("admin")      # 存的是‘saltadmin’一起的结果
View Code

3.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,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:

1
hashlib.md5( "salt" .encode( "utf8" ))     #“加盐”的内容此时是salt,也可以为账户名xuyaping,字符串a,数字1等等

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

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

有没有办法让使用相同口令的用户存储不同的MD5呢?

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

摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,

但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。

四 os模块 

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

os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
 
os.chdir()  当前目录
  
os.chdir("dirname")  改变当前脚本工作目录;相当于shell下cd
  
os.curdir  返回当前目录: ('.'),相当于shell下cd.
  
os.pardir  获取当前目录的父目录字符串名:('..'),相当于shell下cd.. 返回上一层目录
  
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的大小

  

五 sys模块 

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

  

六 logging模块

6.1 函数式简单配置

不予以配置,查看默认情况下输出:

import logging
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
 
--->WARNING:root:warning message
    ERROR:root:error message
    CRITICAL:root:critical message

  

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

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

使用config 

import logging
 
logging.basicConfig(level=logging.DEBUG,  # 配置日志级别,默认输出debug等级及等级更高的内容
                    format='%(asctime)s--->%(filename)s[line:%(lineno)d]====>%(levelname)s::::%(message)s',  # 配置日志显示格式
                    datefmt='%a, %d %b %Y %H:%M:%S',  # 配置时间格式
                    filename='test.log',  # 配置文件路径
                    filemode='w')  # 配置文件权限
 
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

  

日志输出内容test.log文本打开内容如下:

Tue, 02 May 2017 07:49:54--->日志模块.py[line:9]====>DEBUG::::debug message
Tue, 02 May 2017 07:49:54--->日志模块.py[line:10]====>INFO::::info message
Tue, 02 May 2017 07:49:54--->日志模块.py[line:11]====>WARNING::::warning message
Tue, 02 May 2017 07:49:54--->日志模块.py[line:12]====>ERROR::::error message
Tue, 02 May 2017 07:49:54--->日志模块.py[line:13]====>CRITICAL::::critical message

  

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

filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。

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用户输出的消息

 使用logger对象来配置

与config比,可同时显示在程序运行页面屏幕和文件中,功能更多,推荐使用。

    上述几个例子中我们了解到了logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical()(分别用以记录不同级别的日志信息),logging.basicConfig()(用默认日志格式(Formatter)为日志系统建立一个默认的流处理器(StreamHandler),设置基础配置(如日志级别等)并加到root logger(根Logger)中)这几个logging模块级别的函数,另外还有一个模块级别的函数是logging.getLogger([name])(返回一个logger对象,如果没有指定名字将返回root logger)

先看一个最简单的过程:

import logging
logging.info('info message')    #不会被打印,因为等级不够
logger=logging.getLogger()
print(logger)
---><RootLogger root (WARNING)>

  

示例1:

import logging

logger=logging.getLogger()         #创建一个大对象

fh=logging.FileHandler("test_log") #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()         #向屏幕上发送内容  
#logger.setLevel(logging.DEBUG)  #设定输出等级
fm=logging.Formatter("%(asctime)s  %(message)s")   #这个也是一个对象,作用是:定义日志格式

fh.setFormatter(fm)     #往文件里写内容
ch.setFormatter(fm)     #往屏幕上输出内容

logger.addHandler(fh)   #对象,类似于吸别人内力,把fh吃掉
logger.addHandler(ch)   #对象,类似于吸别人内力,把ch吃掉

logger.debug("debug")   #输出日志的级别
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical")

  

执行结果:

会生成一个test_log的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
2016-12-15 14:38:27,657  warning
2016-12-15 14:38:27,658  error
2016-12-15 14:38:27,658  critical

屏幕输出信息如下:
2016-12-15 14:38:27,657  warning
2016-12-15 14:38:27,658  error
2016-12-15 14:38:27,658  critical

  

示例2: logger.setLevel("DEBUG")   调整日志级别,控制日志显示信息,DEBUG显示5条记录

import logging

logger=logging.getLogger()         #创建一个大对象

fh=logging.FileHandler("test_log") #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()         #向屏幕上发送内容

fm=logging.Formatter("%(asctime)s  %(message)s")   #这个也是一个对象,作用是:定义日志格式

fh.setFormatter(fm)      #往文件里写内容
ch.setFormatter(fm)      #往屏幕上输出内容

logger.addHandler(fh)    #对象,类似于吸别人内力,把fh吃掉
logger.addHandler(ch)    #对象,类似于吸别人内力,把ch吃掉
logger.setLevel("DEBUG") #设置日志级别,控制日志输入多少条信息


#-------------从这里开始都是在操作log----------------

logger.debug("debug")   #输出日志的级别
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical")

  

执行结果:

会生成一个test_log的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

屏幕输出信息如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

  

示例3:  写成函数的形式,并有返回值

import logging

def logger():

    logger=logging.getLogger()         #创建一个大对象

    fh=logging.FileHandler("test_log") #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
    ch=logging.StreamHandler()         #向屏幕上发送内容

    fm=logging.Formatter("%(asctime)s  %(message)s")   #这个也是一个对象,作用是:定义日志格式

    fh.setFormatter(fm)       #往文件里写内容
    ch.setFormatter(fm)       #往屏幕上输出内容

    logger.addHandler(fh)     #对象,类似于吸别人内力,把fh吃掉
    logger.addHandler(ch)     #对象,类似于吸别人内力,把ch吃掉
    logger.setLevel("DEBUG")  #设置日志级别,控制日志输入多少条信息

    return logger

#-------------从这里开始都是在操作log----------------
logger=logger()         #这个日志就做成了一个接口,想在其它地方使用,直接调用他就可以啦!

logger.debug("debug")   #输出日志的级别
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical")

  

执行结果:

会生成一个test_log的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

屏幕输出信息如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

  

示例4: 只在屏幕文件中写入日志,不在屏幕上面显示

import logging

def logger():

    logger=logging.getLogger()         #创建一个大对象

    fh=logging.FileHandler("test_log") #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
    #ch=logging.StreamHandler()        #向屏幕上发送内容

    fm=logging.Formatter("%(asctime)s  %(message)s")   #这个也是一个对象,作用是:定义日志格式

    fh.setFormatter(fm)      #往文件里写内容
    #ch.setFormatter(fm)     #往屏幕上输出内容

    logger.addHandler(fh)    #对象,类似于吸别人内力,把fh吃掉
    #logger.addHandler(ch)   #对象,类似于吸别人内力,把ch吃掉
    logger.setLevel("DEBUG") #设置日志级别,控制日志输入多少条信息

    return logger

#-------------从这里开始都是在操作log----------------
logger=logger()         #这个日志就做成了一个接口,在其它地方,直接调用他就可以啦!

logger.debug("debug")   #输出日志的级别
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical")

  

执行结果:

#会生成一个test_log的文件,同时往里面写入信息,不会在屏幕上面显示信息。
#文件内容如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

 

示例5:没有根用户

#如果我们再创建两个logger对象 

import logging

logger1 = logging.getLogger('mylogger')   #默认是根,这里代表他是子用户(两个用户是同级)
#logger1 = logging.getLogger('mylogger.sontree')   #如果mylogger下再创建一个字对象,就用.sontree;等于他就是mylogger的下级对象。
logger1.setLevel(logging.DEBUG)           #第一次是DEBUG级别

logger2 = logging.getLogger('mylogger')   #默认是根,这里代表他是子用户(两个用户是同级)
logger2.setLevel(logging.INFO)            #第二次是INFO级别,覆盖第一次的级别,所以打印结果是INFO级别显示

fh=logging.FileHandler("test_log-new")    #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()                #向屏幕上发送内容

logger1.addHandler(fh)
logger1.addHandler(ch)

logger2.addHandler(fh)
logger2.addHandler(ch)

  

执行结果:

logger1 and logger2各打印4条信息
生成一个test_log-new的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
logger1 info message
logger1 warning message
logger1 error message
logger1 critical message
logger2 info message
logger2 warning message
logger2 error message
logger2 critical message

#屏幕上面显示的内容
logger1 info message
logger1 warning message
logger1 error message
logger1 critical message
logger2 info message
logger2 warning message
logger2 error message
logger2 critical message

  

示例6:添加根用户  (lgger和mylogger是父子关系) (注意日志输出问题)

import logging

logger = logging.getLogger()              #根用户(根用户级别,没有定义日志级别,默认warning级别,所以是3条信息

logger1 = logging.getLogger('mylogger')   #默认是根,这里代表他是子用户(两个用户是同级)
logger1.setLevel(logging.DEBUG)           #第一次是DEBUG级别,默认是打印五条信息,但是他打印信息的时候,会先去找父,如果有父,他就会多打印一遍,所以输出是10条信息

fh=logging.FileHandler("test_log-new")    #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()                #向屏幕上发送内容

logger.addHandler(ch)                     #添加一个根用户
logger.addHandler(fh)

logger1.addHandler(fh)                    #添加一个子用户
logger1.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')

#打印4条信息
logger1.debug('logger1 debug message')
logger1.info('logger1 info message')
logger1.warning('logger1 warning message')
logger1.error('logger1 error message')
logger1.critical('logger1 critical message')

  

输出结果:

生成一个test_log-new的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
logger warning message
logger error message
logger critical message    #前三条是根输出的三条信息
logger1 debug message      #后10条是子输出的10条信息,为什么会输入10条呢?
logger1 debug message      #第一次是DEBUG级别,默认是打印五条信息,但是他打印信息的时候,会先去找父,如果有父,他就会多打印一遍,所以输出是5+5=10条信息
logger1 info message
logger1 info message
logger1 warning message
logger1 warning message
logger1 error message
logger1 error message
logger1 critical message
logger1 critical message

屏幕输出内容如下:
logger warning message
logger error message
logger critical message    
logger1 debug message
logger1 debug message
logger1 info message
logger1 info message
logger1 warning message
logger1 warning message
logger1 error message
logger1 error message
logger1 critical message
logger1 critical message

  

示例7:添加根用户  (控制根用户不输入,只输出子用户信息)

import logging

logger = logging.getLogger()                #根用户(根用户级别,没有定义日志级别,默认warning级别,所以是3条信息

logger1 = logging.getLogger('mylogger')     #默认是根,这里代表他是子用户(两个用户是同级)
logger1.setLevel(logging.DEBUG)             #第一次是DEBUG级别,默认是打印五条信息,但是他打印信息的时候,会先去找父,如果有父,他就会多打印一遍,所以输出是10条信息

fh=logging.FileHandler("test_log-new")      #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()                  #向屏幕上发送内容

logger1.addHandler(fh)                      #添加一个子用户
logger1.addHandler(ch)

#打印4条信息
logger1.debug('logger1 debug message')
logger1.info('logger1 info message')
logger1.warning('logger1 warning message')
logger1.error('logger1 error message')
logger1.critical('logger1 critical message')

  

执行结果:

#生成一个test_log-new的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
logger1 debug message
logger1 info message
logger1 warning message
logger1 error message
logger1 critical message

屏幕输出内容如下:
logger1 debug message
logger1 info message
logger1 warning message
logger1 error message
logger1 critical message

  

logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。

Logger是一个树形层级结构,输出信息之前都要获得一个Logger(如果没有显示的获取则自动创建并使用root Logger)。
      logger = logging.getLogger()返回一个默认的Logger也即root Logger,并应用默认的日志级别、Handler和Formatter设置。
当然也可以通过Logger.setLevel(lel)指定最低的日志级别,可用的日志级别有logging.DEBUG、logging.INFO、logging.WARNING、logging.ERROR、logging.CRITICAL。
      Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical()输出不同级别的日志,只有日志等级大于或等于设置的日志级别的日志才会被输出。

 

 

七 序列化模块json & pickle

 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不过,eval方法是有局限性的,对于普通的数据类型,json.loads和eval都能用,但遇到特殊类型的时候,eval就不管用了,所以eval的重点还是通常用来执行一个字符串表达式,并返回表达式的值。

import json
x="[null,true,false,1]"
# print(eval(x))
--->报错
 
print(json.loads(x))
--->[null,true,false,1]

  

 1 #eval---转换类型
 2  
 3 d={"name":"yuan"}
 4  
 5 s=str(d)
 6  
 7 print(type(s))
 8  
 9 d2=eval(s)
10  
11 print(d2[1])
12  
13 with open("test") as f:
14  
15     for i in f :
16  
17         if type(eval(i.strip()))==dict:
18             print(eval(i.strip())[1])
19              
20 # 计算
21  
22 print(eval("12*7+5-3"))

什么是序列化?

我们把对象(变量)从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

json  

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

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

 

import json
i=10
s='hello'
t=(1,4,6)
l=[3,5,7]
d={'name':"yuan"}
 
#序列化
json_str1=json.dumps(i)    #将整型i转换为json字符串形式
json_str2=json.dumps(s)    #将字符串'hello'转换为json字符串形式
json_str3=json.dumps(t)    #将元组t=(1,4,6)转换为json字符串形式
json_str4=json.dumps(l)    #将列表l=[3,5,7]转换为json字符串形式
json_str5=json.dumps(d)    #将字典d={'name':"yuan"}转换为json字符串形式。当d有英文时,打印出的是unicode数据;当d内容为英语时,正常显示
 
print(json_str1)       #json能识别整型
--->10                #json的字符串10
 
print(json_str2)       #json不能识别字符串
--->"hello"              #打印双引号,jason识别不了单引号''
 
print(json_str3)       #json不能识别元组,识别不了(),默认转换为[]
--->[1, 4, 6]
 
print(json_str4)       #json能识别列表
--->[3, 5, 7]
 
print(json_str5)       #json能识别字典。jason识别不了单引号'',所以输出的都是双引号。
--->{"name": "yuan"}

  

 python在文本中的使用:

#序列化
 
import json
dic={'name':'alvin','age':23,'sex':'male'}
print(type(dic))
---><class 'dict'>
 
data=json.dumps(dic)
print("type",type(data))
---><class 'str'>
print("data",data)
 
 
f=open('序列化对象','w')
f.write(data)          #等价于json.dump(dic,f)
f.close()
 
 
#反序列化
 
import json
f=open('序列化对象')
new_data=json.loads(f.read())    #等价于data=json.load(f)
 
print(type(new_data))

  

json序列化 

把字典转换成json形式的字符串写入文件中 (两种方法效果一样,只是写法不同而已)

方法一:推荐用这种方法

 
 
 
 

模块就是py文件。python中能开辟作用域的只有函数、类和模块。

for循环不能开辟作用域,for循环内的变量为全局变量。if...else...同for循环一样。

一 time模块

时间表示形式

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

(1)时间戳(timestamp) :通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。

  我们运行“type(time.time())”,返回的是float类型。

#时间戳,时间戳是计算机能够识别的时间。
 
import time
print(time.time())      #返回当前时间的时间戳,其中time.time(),第一个time为模块名,第二个time为方法
--->1493166727.099066

  

 (2)格式化的时间字符串(Format String): ‘2017-04-26’

#时间字符串,是人能够看懂的时间。
 
print(time.strftime("%Y-%m-%d %X"))  #Y代表year,m为mouth,d为day,x为时间
--->'2017-04-26 00:32:18'
 
print(time.strftime("%Y:%m))     #年月日时间分割可以更改,此处用“:”分割
--->'2017:04'

  

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

# 时间元组,结构化时间,是用来操作时间的。
 
import time
c=time.localtime()
print(c)
--->time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=16, tm_min=30, tm_sec=42, tm_wday=2, tm_yday=116, tm_isdst=0)
<br>#通过操作结构化时间查看具体信息
y=c.tm_year
print(y)
--->2017
 
m=c.tm_mon
print(m)
--->4
 
d=c.tm_mday
print(d)
--->26

  

几种时间形式的转换

1.时间戳与结构化时间的转换

时间戳转化为结构化时间:localtime/gmtime

#时间戳转化为结构化时间localtime/gmtime
#gmtime为世界标准时间,一般不使用。
#localtime为东八区时间,为我们所在的时间,常使用localtime
 
c1=time.localtime(3600*24)
print(c1)  #打印距离1970-1-1 00:00:00一天的时间信息
--->time.struct_time(tm_year=1970, tm_mon=1, tm_mday=2, tm_hour=8, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=2, tm_isdst=0)
print(c1.tm_year,c1.tm_mon,c1.tm_mday,c1.tm_hour,c1.tm_min)
--->1970 1 2 8 0
 
c2=time.gmtime(3600*24)
print(c2)
--->time.struct_time(tm_year=1970, tm_mon=1, tm_mday=2, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=2, tm_isdst=0)
 
print(c2.tm_year,c2.tm_mon,c2.tm_mday,c2.tm_hour,c2.tm_min)
--->1970 1 2 0 0

  

结构化时间转化为时间戳:mktime

#结构化时间转化为时间戳mktime
print(time.mktime(time.localtime()))        #当前结构化时间转化为时间戳
--->1493196972.0

  

 2.字符串时间与结构化时间的转换

字符串时间转化为结构化时间:strptime

#字符串时间转化为结构化时间:strptime
print(time.strptime("2017-03-16","%Y-%m-%d"))   #字符串时间2017-03-16转化为结构化时间
--->time.struct_time(tm_year=2017, tm_mon=3, tm_mday=16, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=75, tm_isdst=-1)

  

结构化时间转化为字符串时间:strftime

#结构化时间转化为字符串时间:strftime
print(time.strftime("%Y-%m-%d %X", time.localtime()))       #当前结构化时间转化为当前字符串时间
--->2017-04-26 17:07:05

  

结构化时间转化为时间字符串:asctime

#结构化时间转化为时间字符串:asctime
print(time.asctime(time.localtime(312343423)))      #转化成距离1970-1-1 00:00:00 时间312343423秒的时间字符串格式
--->Sun Nov 25 10:03:43 1979
 
print(time.asctime(time.localtime()))   #转化成当前时间字符串格式
--->Wed Apr 26 17:25:16 2017

  

时间戳转化为时间字符串:ctime

#时间戳转化为时间字符串:ctime
print(time.ctime(312343423))        #转化成距离1970-1-1 00:00:00 时间312343423秒的时间字符串格式
--->Sun Nov 25 10:03:43 1979
 
print(time.ctime())     #转化成当前时间字符串格式
--->Wed Apr 26 17:26:19 2017

  

其他方法

#其他方法
sleep(secs)    # 线程推迟指定的时间运行,单位为秒。相当于IO操作。

  

例子

 1 # 加3天时间
 2 # 1.字符串---》结构化---》时间戳---》加3天的秒数3600*24*3
 3 # 2.时间戳---》结构化---》字符串
 4 import time
 5 s = "2017-11-23 19:56:00"
 6 def change(s,d):
 7     s1 = time.strptime(s,"%Y-%m-%d %H:%M:%S")  # strptime 字符串--》结构化时间
 8     # print(s1)  # time.struct_time(tm_year=2017, tm_mon=11, tm_mday=23, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=327, tm_isdst=-1)
 9 
10     s2 = time.mktime(s1)  # mktime 结构---》时间戳  当前时间
11     # print(s2)  # 1511366400.0
12 
13     # 加3天时间
14     s3 = s2 + 3600*24*d
15     # print(s3)  # 1511625600.0
16 
17     # 时间戳---》结构化
18     # s4 = time.gmtime(s3)
19     # print(s4)  # time.struct_time(tm_year=2017, tm_mon=11, tm_mday=25, tm_hour=16, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=329, tm_isdst=0)
20     s4 = time.localtime(s3)
21 
22     # 结构化---》字符串
23     s5 = time.strftime("%Y-%m-%d %H:%M:%S",s4)
24     # print(s5)  # 2017-11-25
25     return s5
26 
27 print(change(s,3))  # 2017-11-26 19:56:00
# 加3天时间

二 random模块 

import random
print(random.random())      #输出大于0且小于1之间的小数
--->0.03231288445315539
 
print(random.randint(1,5))   # 随机输出大于等于1且小于等于5之间的整数,[1,5]
--->4
 
print(random.randrange(1,3))    # 随机输出大于等于1且小于3之间的整数,[1,3)
--->2
 
print(random.choice([1,'2',[3,4]]))      #随机取列表中的一个元素,结果为1或者2或者[3,4]
--->[3, 4]
 
print(random.sample([1,'2',[3,4]],2))      #随机取列表中任意2个元素
--->[1, [3, 4]]
 
print(random.uniform(1,3))      #随机取大于1小于3的小数
--->1.0122103690258861
 
item=[1,2,3,4,5]
print(random.shuffle(item))     #直接输出,什么都没有
--->None
 
random.shuffle(item)
print(item)
--->[1, 3, 5, 2, 4]
print(item)     #再次打印结果和上一次输出结果一样,需要重新打乱才能输出重新排序的列表
--->[1, 3, 5, 2, 4]
random.shuffle(item)
print(item)
--->[2, 3, 5, 1, 4]

  

练习:随机生成验证码,字母和数字

# 练习:随机生成验证码,字母和数字
s = ''
for i in range(5):
    num = random.randint(0,9)
    xx = chr(random.randint(97,122))
    dx = chr(random.randint(65,90))

    add = random.choice([str(num),xx,dx])
    s+=add
    # s = ''.join([s,add])
print(s)  # 14Gq2

  

三 hashlib 

3.1 算法介绍

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

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

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

摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。

而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。

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

import hashlib
 
md5 = hashlib.md5()  # md5只是hashlib摘要算法的一种,可以使用其他摘要算法。md5使用较多。
md5.update('how to use md5 in python hashlib?'.encode("utf8"))  #使用.encode("utf8")或者加b强制转换成二进制方式都不会报错,在python2中不需要
print(md5.hexdigest())
--->d26a53750bc40b38b65a520292f69306    # 对应的唯一值

  

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

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

  

import hashlib
#  摘要算法应用: 文件一致性校验 、 登录
 
m=hashlib.md5()
#m.update(b"hello")
m.update("hello".encode("utf8"))  #计算hello的hash值
print(m.hexdigest())        #以16进制的方式打印经过md5摘要算法计算的hello的hash值
--->5d41402abc4b2a76b9719d911017c592
 
m.update("hello".encode("utf8"))  #计算hello的hash值
print(m.hexdigest())
--->23b431acfeb41e15d466d75de822307c
 
#  加盐处理
# md5_obj=hashlib.md5("加的东西")

n=hashlib.md5("world".encode("utf8"))
n.update(b"python")
print(n.hexdigest())
--->ff9360a32af52a8a18496b51ad7e6b3f

  

MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。

另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似: 

import hashlib
 
sha1 = hashlib.sha1()
sha1.update('how to use sha1 in '.encode("utf8"))
sha1.update(b'python hashlib?')
print(sha1.hexdigest())
--->2c76b57293ce30acef38d98f6046927161b46a44

  

SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。

比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。 

 1 import hashlib
 2 
 3 md5_obj=hashlib.md5()
 4 
 5 # hexdigest  显示结果
 6 # md5_obj.update(b"helloworld")  # helloworld: fc5e038d38a57032085441e7fe7010b0
 7 # print(md5_obj.hexdigest())
 8 
 9 # md5_obj.update(b"yuan")  # yuan: 46cc45a727bec7de97f9e738ad428153
10 # print(md5_obj.hexdigest())
11 # ------------------------------------------------------
12 # md5_obj.update(b"helloworld")
13 # md5_obj.update(b"yuan")
14 # print(md5_obj.hexdigest())  # df964287dfc2941ceaaa66496565473b  #一起update相当于拼接累加之前update的所有内容。
15 
16 
17 # md5_obj.update(b"helloworldyuan")
18 # print(md5_obj.hexdigest())  #df964287dfc2941ceaaa66496565473b
19                             #df964287dfc2941ceaaa66496565473b
20 
21 
22 
23 #  摘要算法应用: 文件一致性校验 、 登录
24 
25 # username                pwd
26 
27 # admin                 admin
28 # 4523453452345         4732467812364342423
29 # 4523453452345         31423149238147982
30 
31 
32 # admin             -----------  4732467812364
33 #
34 # adminjindongdf    -----------  4732467812364342423
35 #
36 # root              -----------  892345789432
37 #
38 # 12345678          -----------  6732456784325
39 
40 
41 #  加盐处理
42 # md5_obj=hashlib.md5("加的东西")
43 # salt
44 # md5_obj=hashlib.md5("salt")
45 # md5_obj.update("admin")      # 存的是‘saltadmin’一起的结果
View Code

3.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,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:

1
hashlib.md5( "salt" .encode( "utf8" ))     #“加盐”的内容此时是salt,也可以为账户名xuyaping,字符串a,数字1等等

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

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

有没有办法让使用相同口令的用户存储不同的MD5呢?

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

摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,

但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。

四 os模块 

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

os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
 
os.chdir()  当前目录
  
os.chdir("dirname")  改变当前脚本工作目录;相当于shell下cd
  
os.curdir  返回当前目录: ('.'),相当于shell下cd.
  
os.pardir  获取当前目录的父目录字符串名:('..'),相当于shell下cd.. 返回上一层目录
  
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的大小

  

五 sys模块 

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

  

六 logging模块

6.1 函数式简单配置

不予以配置,查看默认情况下输出:

import logging
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
 
--->WARNING:root:warning message
    ERROR:root:error message
    CRITICAL:root:critical message

  

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

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

使用config 

import logging
 
logging.basicConfig(level=logging.DEBUG,  # 配置日志级别,默认输出debug等级及等级更高的内容
                    format='%(asctime)s--->%(filename)s[line:%(lineno)d]====>%(levelname)s::::%(message)s',  # 配置日志显示格式
                    datefmt='%a, %d %b %Y %H:%M:%S',  # 配置时间格式
                    filename='test.log',  # 配置文件路径
                    filemode='w')  # 配置文件权限
 
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

  

日志输出内容test.log文本打开内容如下:

Tue, 02 May 2017 07:49:54--->日志模块.py[line:9]====>DEBUG::::debug message
Tue, 02 May 2017 07:49:54--->日志模块.py[line:10]====>INFO::::info message
Tue, 02 May 2017 07:49:54--->日志模块.py[line:11]====>WARNING::::warning message
Tue, 02 May 2017 07:49:54--->日志模块.py[line:12]====>ERROR::::error message
Tue, 02 May 2017 07:49:54--->日志模块.py[line:13]====>CRITICAL::::critical message

  

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

filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。

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用户输出的消息

 使用logger对象来配置

与config比,可同时显示在程序运行页面屏幕和文件中,功能更多,推荐使用。

    上述几个例子中我们了解到了logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical()(分别用以记录不同级别的日志信息),logging.basicConfig()(用默认日志格式(Formatter)为日志系统建立一个默认的流处理器(StreamHandler),设置基础配置(如日志级别等)并加到root logger(根Logger)中)这几个logging模块级别的函数,另外还有一个模块级别的函数是logging.getLogger([name])(返回一个logger对象,如果没有指定名字将返回root logger)

先看一个最简单的过程:

import logging
logging.info('info message')    #不会被打印,因为等级不够
logger=logging.getLogger()
print(logger)
---><RootLogger root (WARNING)>

  

示例1:

import logging

logger=logging.getLogger()         #创建一个大对象

fh=logging.FileHandler("test_log") #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()         #向屏幕上发送内容  
#logger.setLevel(logging.DEBUG)  #设定输出等级
fm=logging.Formatter("%(asctime)s  %(message)s")   #这个也是一个对象,作用是:定义日志格式

fh.setFormatter(fm)     #往文件里写内容
ch.setFormatter(fm)     #往屏幕上输出内容

logger.addHandler(fh)   #对象,类似于吸别人内力,把fh吃掉
logger.addHandler(ch)   #对象,类似于吸别人内力,把ch吃掉

logger.debug("debug")   #输出日志的级别
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical")

  

执行结果:

会生成一个test_log的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
2016-12-15 14:38:27,657  warning
2016-12-15 14:38:27,658  error
2016-12-15 14:38:27,658  critical

屏幕输出信息如下:
2016-12-15 14:38:27,657  warning
2016-12-15 14:38:27,658  error
2016-12-15 14:38:27,658  critical

  

示例2: logger.setLevel("DEBUG")   调整日志级别,控制日志显示信息,DEBUG显示5条记录

import logging

logger=logging.getLogger()         #创建一个大对象

fh=logging.FileHandler("test_log") #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()         #向屏幕上发送内容

fm=logging.Formatter("%(asctime)s  %(message)s")   #这个也是一个对象,作用是:定义日志格式

fh.setFormatter(fm)      #往文件里写内容
ch.setFormatter(fm)      #往屏幕上输出内容

logger.addHandler(fh)    #对象,类似于吸别人内力,把fh吃掉
logger.addHandler(ch)    #对象,类似于吸别人内力,把ch吃掉
logger.setLevel("DEBUG") #设置日志级别,控制日志输入多少条信息


#-------------从这里开始都是在操作log----------------

logger.debug("debug")   #输出日志的级别
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical")

  

执行结果:

会生成一个test_log的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

屏幕输出信息如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

  

示例3:  写成函数的形式,并有返回值

import logging

def logger():

    logger=logging.getLogger()         #创建一个大对象

    fh=logging.FileHandler("test_log") #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
    ch=logging.StreamHandler()         #向屏幕上发送内容

    fm=logging.Formatter("%(asctime)s  %(message)s")   #这个也是一个对象,作用是:定义日志格式

    fh.setFormatter(fm)       #往文件里写内容
    ch.setFormatter(fm)       #往屏幕上输出内容

    logger.addHandler(fh)     #对象,类似于吸别人内力,把fh吃掉
    logger.addHandler(ch)     #对象,类似于吸别人内力,把ch吃掉
    logger.setLevel("DEBUG")  #设置日志级别,控制日志输入多少条信息

    return logger

#-------------从这里开始都是在操作log----------------
logger=logger()         #这个日志就做成了一个接口,想在其它地方使用,直接调用他就可以啦!

logger.debug("debug")   #输出日志的级别
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical")

  

执行结果:

会生成一个test_log的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

屏幕输出信息如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

  

示例4: 只在屏幕文件中写入日志,不在屏幕上面显示

import logging

def logger():

    logger=logging.getLogger()         #创建一个大对象

    fh=logging.FileHandler("test_log") #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
    #ch=logging.StreamHandler()        #向屏幕上发送内容

    fm=logging.Formatter("%(asctime)s  %(message)s")   #这个也是一个对象,作用是:定义日志格式

    fh.setFormatter(fm)      #往文件里写内容
    #ch.setFormatter(fm)     #往屏幕上输出内容

    logger.addHandler(fh)    #对象,类似于吸别人内力,把fh吃掉
    #logger.addHandler(ch)   #对象,类似于吸别人内力,把ch吃掉
    logger.setLevel("DEBUG") #设置日志级别,控制日志输入多少条信息

    return logger

#-------------从这里开始都是在操作log----------------
logger=logger()         #这个日志就做成了一个接口,在其它地方,直接调用他就可以啦!

logger.debug("debug")   #输出日志的级别
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical")

  

执行结果:

#会生成一个test_log的文件,同时往里面写入信息,不会在屏幕上面显示信息。
#文件内容如下:
2016-12-15 14:54:37,036  debug
2016-12-15 14:54:37,037  info
2016-12-15 14:54:37,038  warning
2016-12-15 14:54:37,038  error
2016-12-15 14:54:37,039  critical

 

示例5:没有根用户

#如果我们再创建两个logger对象 

import logging

logger1 = logging.getLogger('mylogger')   #默认是根,这里代表他是子用户(两个用户是同级)
#logger1 = logging.getLogger('mylogger.sontree')   #如果mylogger下再创建一个字对象,就用.sontree;等于他就是mylogger的下级对象。
logger1.setLevel(logging.DEBUG)           #第一次是DEBUG级别

logger2 = logging.getLogger('mylogger')   #默认是根,这里代表他是子用户(两个用户是同级)
logger2.setLevel(logging.INFO)            #第二次是INFO级别,覆盖第一次的级别,所以打印结果是INFO级别显示

fh=logging.FileHandler("test_log-new")    #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()                #向屏幕上发送内容

logger1.addHandler(fh)
logger1.addHandler(ch)

logger2.addHandler(fh)
logger2.addHandler(ch)

  

执行结果:

logger1 and logger2各打印4条信息
生成一个test_log-new的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
logger1 info message
logger1 warning message
logger1 error message
logger1 critical message
logger2 info message
logger2 warning message
logger2 error message
logger2 critical message

#屏幕上面显示的内容
logger1 info message
logger1 warning message
logger1 error message
logger1 critical message
logger2 info message
logger2 warning message
logger2 error message
logger2 critical message

  

示例6:添加根用户  (lgger和mylogger是父子关系) (注意日志输出问题)

import logging

logger = logging.getLogger()              #根用户(根用户级别,没有定义日志级别,默认warning级别,所以是3条信息

logger1 = logging.getLogger('mylogger')   #默认是根,这里代表他是子用户(两个用户是同级)
logger1.setLevel(logging.DEBUG)           #第一次是DEBUG级别,默认是打印五条信息,但是他打印信息的时候,会先去找父,如果有父,他就会多打印一遍,所以输出是10条信息

fh=logging.FileHandler("test_log-new")    #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()                #向屏幕上发送内容

logger.addHandler(ch)                     #添加一个根用户
logger.addHandler(fh)

logger1.addHandler(fh)                    #添加一个子用户
logger1.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')

#打印4条信息
logger1.debug('logger1 debug message')
logger1.info('logger1 info message')
logger1.warning('logger1 warning message')
logger1.error('logger1 error message')
logger1.critical('logger1 critical message')

  

输出结果:

生成一个test_log-new的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
logger warning message
logger error message
logger critical message    #前三条是根输出的三条信息
logger1 debug message      #后10条是子输出的10条信息,为什么会输入10条呢?
logger1 debug message      #第一次是DEBUG级别,默认是打印五条信息,但是他打印信息的时候,会先去找父,如果有父,他就会多打印一遍,所以输出是5+5=10条信息
logger1 info message
logger1 info message
logger1 warning message
logger1 warning message
logger1 error message
logger1 error message
logger1 critical message
logger1 critical message

屏幕输出内容如下:
logger warning message
logger error message
logger critical message    
logger1 debug message
logger1 debug message
logger1 info message
logger1 info message
logger1 warning message
logger1 warning message
logger1 error message
logger1 error message
logger1 critical message
logger1 critical message

  

示例7:添加根用户  (控制根用户不输入,只输出子用户信息)

import logging

logger = logging.getLogger()                #根用户(根用户级别,没有定义日志级别,默认warning级别,所以是3条信息

logger1 = logging.getLogger('mylogger')     #默认是根,这里代表他是子用户(两个用户是同级)
logger1.setLevel(logging.DEBUG)             #第一次是DEBUG级别,默认是打印五条信息,但是他打印信息的时候,会先去找父,如果有父,他就会多打印一遍,所以输出是10条信息

fh=logging.FileHandler("test_log-new")      #向文件里发送内容,并且给个参数,作用是:定义一个文件名,往文件里写入内容
ch=logging.StreamHandler()                  #向屏幕上发送内容

logger1.addHandler(fh)                      #添加一个子用户
logger1.addHandler(ch)

#打印4条信息
logger1.debug('logger1 debug message')
logger1.info('logger1 info message')
logger1.warning('logger1 warning message')
logger1.error('logger1 error message')
logger1.critical('logger1 critical message')

  

执行结果:

#生成一个test_log-new的文件,同时往里面写入信息,并在屏幕上面显示相同信息。
文件内容如下:
logger1 debug message
logger1 info message
logger1 warning message
logger1 error message
logger1 critical message

屏幕输出内容如下:
logger1 debug message
logger1 info message
logger1 warning message
logger1 error message
logger1 critical message

  

logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。

Logger是一个树形层级结构,输出信息之前都要获得一个Logger(如果没有显示的获取则自动创建并使用root Logger)。
      logger = logging.getLogger()返回一个默认的Logger也即root Logger,并应用默认的日志级别、Handler和Formatter设置。
当然也可以通过Logger.setLevel(lel)指定最低的日志级别,可用的日志级别有logging.DEBUG、logging.INFO、logging.WARNING、logging.ERROR、logging.CRITICAL。
      Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical()输出不同级别的日志,只有日志等级大于或等于设置的日志级别的日志才会被输出。

 

 

七 序列化模块json & pickle

 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不过,eval方法是有局限性的,对于普通的数据类型,json.loads和eval都能用,但遇到特殊类型的时候,eval就不管用了,所以eval的重点还是通常用来执行一个字符串表达式,并返回表达式的值。

import json
x="[null,true,false,1]"
# print(eval(x))
--->报错
 
print(json.loads(x))
--->[null,true,false,1]

  

 1 #eval---转换类型
 2  
 3 d={"name":"yuan"}
 4  
 5 s=str(d)
 6  
 7 print(type(s))
 8  
 9 d2=eval(s)
10  
11 print(d2[1])
12  
13 with open("test") as f:
14  
15     for i in f :
16  
17         if type(eval(i.strip()))==dict:
18             print(eval(i.strip())[1])
19              
20 # 计算
21  
22 print(eval("12*7+5-3"))

什么是序列化?

我们把对象(变量)从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

json  

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

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

 

import json
i=10
s='hello'
t=(1,4,6)
l=[3,5,7]
d={'name':"yuan"}
 
#序列化
json_str1=json.dumps(i)    #将整型i转换为json字符串形式
json_str2=json.dumps(s)    #将字符串'hello'转换为json字符串形式
json_str3=json.dumps(t)    #将元组t=(1,4,6)转换为json字符串形式
json_str4=json.dumps(l)    #将列表l=[3,5,7]转换为json字符串形式
json_str5=json.dumps(d)    #将字典d={'name':"yuan"}转换为json字符串形式。当d有英文时,打印出的是unicode数据;当d内容为英语时,正常显示
 
print(json_str1)       #json能识别整型
--->10                #json的字符串10
 
print(json_str2)       #json不能识别字符串
--->"hello"              #打印双引号,jason识别不了单引号''
 
print(json_str3)       #json不能识别元组,识别不了(),默认转换为[]
--->[1, 4, 6]
 
print(json_str4)       #json能识别列表
--->[3, 5, 7]
 
print(json_str5)       #json能识别字典。jason识别不了单引号'',所以输出的都是双引号。
--->{"name": "yuan"}

  

 python在文本中的使用:

#序列化
 
import json
dic={'name':'alvin','age':23,'sex':'male'}
print(type(dic))
---><class 'dict'>
 
data=json.dumps(dic)
print("type",type(data))
---><class 'str'>
print("data",data)
 
 
f=open('序列化对象','w')
f.write(data)          #等价于json.dump(dic,f)
f.close()
 
 
#反序列化
 
import json
f=open('序列化对象')
new_data=json.loads(f.read())    #等价于data=json.load(f)
 
print(type(new_data))

  

json序列化 

把字典转换成json形式的字符串写入文件中 (两种方法效果一样,只是写法不同而已)

方法一:推荐用这种方法

猜你喜欢

转载自www.cnblogs.com/yimiflh/p/9280427.html