Python - - 模块 - - 常用模块
目录
- re模块
- collections模块
- 时间模块
- random模块
- os模块
- sys模块
- 序列化模块
- hashlib模块
- configparse模块
- logging模块
1 re模块
1.1 根据手机号码一共11位并且是只以13、14、15、18开头的数字这些特点,用python获取:
str
方法获取
while True:
phone_number = input('please input your phone number : ')
if len(phone_number) == 11 \
and phone_number.isdigit()\
and (phone_number.startswith('13') \
or phone_number.startswith('14') \
or phone_number.startswith('15') \
or phone_number.startswith('18')):
print('是合法的手机号码')
else:
print('不是合法的手机号码')
re
模块获取
import re
phone_number = input('please input your phone number : ')
if re.match('^(13|14|15|18)[0-9]{9}$',phone_number):
print('是合法的手机号码')
else:
print('不是合法的手机号码')
1.2 re模块下的常用方法
findall
- 返回所有满足匹配条件的结果,放在列表里
import re
ret = re.findall('[a-z]', 'eva egon yuan') #
print(ret)
# 结果呈现
['eva', 'egon', 'yuan']
- findall的优先级查询:
import re
ret = re.findall('www.(baidu|abc).com', 'www.baidu.com')
print(ret) # ['baidu'] 这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可
ret = re.findall('www.(?:baidu|abc).com', 'www.baidu.com')
print(ret) # ['www.baidu.com']
# 结果呈现
['baidu']
['www.baidu.com']
search
- 找整个字符串,遇到匹配上的就返回,遇不到就None
- 如果有返回值 ret.group() 就可以取到值
- 取分组中的内容:ret.group(1)
import re
# ret = re.search('a', 'eva egon yuan').group()
ret = re.search('a', 'eva egon yuan')
print(ret)
print(ret.group())
# 函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以
# 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回报错。
# 结果呈现
<re.Match object; span=(2, 3), match='a'>
a
import re
ret = re.search('a', 'eva egon yuan')
if ret:
print(ret.group())
# 结果呈现
a
# 从分组中取值,根据组的顺序取值
import re
ret = re.search("\d(\w)+", "awirs198723jsdc")
print(ret.group())
print(ret.group(1))
# 从分组中取值,根据组的名字取值 ?P<name>
import re
ret = re.search("\d(?P<name>\w)+", "awirs198723jsdc")
print(ret.group())
print(ret.group("name"))
# 结果呈现
198723jsdc
c
match
- 是从头开始匹配,如果正则规则从头开始匹配上,就返回一个变量,则返回None
- 匹配的内容需要用group才能显示,如果字符串没有匹配,且报错。
import re
# ret = re.match('a', 'eva egon yuan').group()
ret = re.match('a', 'eva egon yuan')
if ret:
print(ret.group())
# 结果呈现
ret = re.match('a', 'eva egon yuan')
if ret:
print(ret.group())
# 结果呈现
ev
split
- 分割
import re
ret = re.split('[ab]', 'abcd') # 先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割
print(ret)
# 结果呈现
['', '', 'cd']
- split的优先级查询
- 在匹配部分加上()之后所切出的结果是不同的,
- 没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项,
- 这个在某些需要保留匹配部分的使用过程是非常重要的。
ret=re.split("\d+","eva3egon4yuan")
print(ret) #结果 : ['eva', 'egon', 'yuan']
ret=re.split("(\d+)","eva3egon4yuan")
print(ret) #结果 : ['eva', '3', 'egon', '4', 'yuan']
# 结果呈现
['eva', 'egon', 'yuan']
['eva', '3', 'egon', '4', 'yuan']
sub
- 替换
import re
ret = re.sub('\d', 'H', 'eva3egon4yuan4', 1) # 将数字替换成'H',参数1表示只替换1个,不写参数1则替换所有数字
print(ret)
# 结果呈现
evaHegon4yuan4
subn
- 替换
import re
ret = re.subn('\d', 'H', 'eva3egon4yuan4')#将数字替换成'H',返回元组(替换的结果,替换了多少次)
print(ret)
# 结果呈现
('evaHegonHyuanH', 3)
compile
- compile 编译:正则表达式很长且要多长使用
import re
obj = re.compile('\d{3}') # 将正则表达式编译成为一个 正则表达式对象,规则要匹配的是3个数字
ret = obj.search('abc123eeee') #正则表达式对象调用search,参数为待匹配的字符串
print(ret.group())
# 结果呈现
123
finditer
- 返回迭代器
import re
ret = re.finditer('\d', 'ds3sy4784a') #finditer返回一个存放匹配结果的迭代器
print(ret) # <callable_iterator object at 0x10195f940>
print(next(ret).group()) #查看第一个结果
print(next(ret).group()) #查看第二个结果
print([i.group() for i in ret]) #查看剩余的左右结果
# 结果呈现
<callable_iterator object at 0x00000026EEED60B8>
3
4
['7', '8', '4']
1.2.1 匹配标签
import re
ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>")
# 还可以在分组中利用?<name>的形式给分组起名字
# 获取的匹配结果可以直接用group('名字')拿到对应的值
print(ret.group('tag_name'))
print(ret.group())
# 结果呈现
h1
<h1>hello</h1>
ret = re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>")
# 如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致
# 获取的匹配结果可以直接用group(序号)拿到对应的值
print(ret.group(1))
print(ret.group())
# 结果呈现
h1
<h1>hello</h1>
1.2.2 匹配整数
import re
ret=re.findall(r"\d+","1-2*(60+(-40.35/5)-(-4*3))")
print(ret)
# 结果呈现
['1', '2', '60', '40', '35', '5', '4', '3']
ret=re.findall(r"\d+\.\d+|(\d+)","1-2*(60+(-40.35/5)-(-4*3))")
print(ret) #['1', '2', '60', '40', '35', '5', '4', '3']
ret.remove("")
print(ret)
# 结果呈现
['1', '-2', '60', '', '5', '-4', '3']
['1', '-2', '60', '5', '-4', '3']
ret=re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))")
print(ret)
ret.remove("")
print(ret)
# 结果呈现
['1', '-2', '60', '', '5', '-4', '3']
['1', '-2', '60', '5', '-4', '3']
1.2.3 数字匹配
1、 匹配一段文本中的每行的邮箱
http://blog.csdn.net/make164492212/article/details/51656638
2、 匹配一段文本中的每行的时间字符串,比如:‘1990-07-12’;
分别取出1年的12个月(^(0?[1-9]|1[0-2])$)、
一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$
3、 匹配qq号。(腾讯QQ号从10000开始) [1,9][0,9]{4,}
4、 匹配一个浮点数。 ^(-?\d+)(\.\d+)?$ 或者 -?\d+\.?\d*
5、 匹配汉字。 ^[\u4e00-\u9fa5]{0,}$
6、 匹配出所有整数
1.2.4 爬虫练习
- 使用正则 compile
import requests
import re
import json
def getPage(url):
response=requests.get(url)
return response.text
def parsePage(s):
com=re.compile('<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
'.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>',re.S)
ret=com.finditer(s)
for i in ret:
yield {
"id":i.group("id"),
"title":i.group("title"),
"rating_num":i.group("rating_num"),
"comment_num":i.group("comment_num"),
}
def main(num):
url='https://movie.douban.com/top250?start=%s&filter='%num
response_html=getPage(url)
ret=parsePage(response_html)
print(ret)
f=open("move_info7","a",encoding="utf8")
for obj in ret:
print(obj)
data=json.dumps(obj,ensure_ascii=False)
f.write(data+"\n")
if __name__ == '__main__':
count=0
for i in range(10):
main(count)
count+=25
- 简化版
import re
import json
from urllib.request import urlopen
def getPage(url):
response = urlopen(url)
return response.read().decode('utf-8')
def parsePage(s):
com = re.compile(
'<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
'.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S)
ret = com.finditer(s)
for i in ret:
yield {
"id": i.group("id"),
"title": i.group("title"),
"rating_num": i.group("rating_num"),
"comment_num": i.group("comment_num"),
}
def main(num):
url = 'https://movie.douban.com/top250?start=%s&filter=' % num
response_html = getPage(url)
ret = parsePage(response_html)
print(ret)
f = open("move_info7", "a", encoding="utf8")
for obj in ret:
print(obj)
data = str(obj)
f.write(data + "\n")
count = 0
for i in range(10):
main(count)
count += 25
- 正则 findall
import re
import json
from urllib.request import urlopen
def getPage(url):
response = urlopen(url)
return response.read().decode('utf-8')
def parsePage(s):
ret = re.findall(
'<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
'.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', s, re.S)
return ret
def main(num):
url = 'https://movie.douban.com/top250?start=%s&filter=' % num
response_html = getPage(url)
ret = parsePage(response_html)
print(ret)
count = 0
for i in range(10):
main(count)
count += 25
# url 从网页上把代码爬下来
# bytes decode - - 》 utf-8 网页内容就是待匹配字符串
# ret = re.findall(正则,带匹配的字符串) # ret 是所有匹配到的内容组成的列表
- flag
flags有很多可选值:
re.I(IGNORECASE)忽略大小写,括号内是完整的写法
re.M(MULTILINE)多行模式,改变^和$的行为
re.S(DOTALL)点可以匹配任意字符,包括换行符
re.L(LOCALE)做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用
re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag
re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释
2 collections模块
- python 中的扩展数据类型
- 在内置数据类型(dict、list、set、tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter、deque、defaultdict、namedtuple和OrderedDict等。
- 1.namedtuple: 生成可以使用名字来访问元素内容的tuple
- 2.deque: 双端队列,可以快速的从另外一侧追加和推出对象
- 3.Counter: 计数器,主要用来计数
- 4.OrderedDict: 有序字典
- 5.defaultdict: 带有默认值的字典
2.1 namedtuple
- tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成,但是看到(1, 2),很难看出这个tuple是用来表示一个坐标的。
p = (1, 2)
- namedtuple就派上了用场:
from collections import namedtuple
Point = namedtuple("point", ["x", "y", "z"])
p1 = Point(1, 2, 3)
p2 = Point(3, 2, 1)
print(p1.x)
print(p1.y)
print(p1, p2)
# 结果呈现
1
2
point(x=1, y=2, z=3) point(x=3, y=2, z=1)
# 花色和数字
Card = namedtuple("card", ["suits", "number"])
c1 = Card("红桃", 2)
print(c1)
print(c1.number)
print(c1.suits)
# 结果呈现
card(suits='红桃', number=2)
2
红桃
- 类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:
# namedtuple('名称', [属性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])
2.2 deque 双端队列
- 使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。
# 队列 先进先出 FIFO
import queue
q = queue.Queue()
q.put(10)
q.put(5)
q.put(6)
print(q)
print("队列个数 %d" % q.qsize())
print(q.get())
print(q.get())
print(q.get())
print(q.get()) # 阻塞 # 等待新输入的值
# 执行结果
<queue.Queue object at 0x0000007147F4E278>
队列个数 3
10
5
6
- deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:
from collections import deque
dq = deque([1, 2])
dq.append("a") # 从后面放数据[3, 1, 2, "a"]
dq.appendleft("b") # 从前面放数据["b",1, 2, "a"]
dq.insert(1, 3) # 从指定的索引位置插入数据["b", 3, 1, 2, "a"]
print(dq.pop()) # 从前面放数据
print(dq.popleft()) # 从前面放数据
print(dq)
# 结果呈现
a
b
deque([3, 1, 2])
- deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。
2.3 OrderedDict
- 使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。
- 如果要保持Key的顺序,可以用OrderedDict:
from collections import OrderedDict
d = dict([('a', 1), ('b', 2), ('c', 3)])
print(d) # dict的Key是无序的字典
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
print(od) # OrderedDict的Key是有序的字典
print("*" * 50)
for k in od:
print(k)
# 结果呈现
{'a': 1, 'b': 2, 'c': 3}
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
**************************************************
a
b
c
- 注意,OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:
from collections import OrderedDict
od = OrderedDict()
od['z'] = 1
od['y'] = 2
od['x'] = 3
print(od.keys()) # 按照插入的Key的顺序返回
# 结果呈现
odict_keys(['z', 'y', 'x'])
2.4 defaultdict
- 有如下值集合 [11,22,33,44,55,66,77,88,99,90...],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。
- 即:
原生字典解决方法
values = [11, 22, 33,44,55,66,77,88,99,90]
my_dict = {}
for value in values:
if value>66:
if my_dict.__contains__('k1'):
my_dict['k1'].append(value)
else:
my_dict['k1'] = [value]
else:
if my_dict.__contains__('k2'):
my_dict['k2'].append(value)
else:
my_dict['k2'] = [value]
print(my_dict)
# 结果呈现
{'k2': [11, 22, 33, 44, 55, 66], 'k1': [77, 88, 99, 90]}
defaultdict 字典解决方法
from collections import defaultdict
values = [11, 22, 33,44,55,66,77,88,99,90]
my_dict = defaultdict(list)
for value in values:
if value>66:
my_dict['k1'].append(value)
else:
my_dict['k2'].append(value)
print(my_dict)
# 结果呈现
defaultdict(<class 'list'>, {'k2': [11, 22, 33, 44, 55, 66], 'k1': [77, 88, 99, 90]})
- 使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:
from collections import defaultdict
dd = defaultdict(lambda: 'N/A')
dd['key1'] = 'abc'
print(dd['key1']) # key1存在
print(dd['key2']) # key2不存在,返回默认值
# 结果呈现
abc
N/A
2.5 Counter
- Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。Counter类和其他语言的bags或multisets很相似。
from collections import Counter
c = Counter('abcdeabcdabcaba')
print(c)
# 结果呈现
Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1})
2.5.1 创建
- 下面的代码说明了Counter类创建的四种方法:
# Counter类的创建
>>> c = Counter() # 创建一个空的Counter类
>>> c = Counter('gallahad') # 从一个可iterable对象(list、tuple、dict、字符串等)创建
>>> c = Counter({'a': 4, 'b': 2}) # 从一个字典对象创建
>>> c = Counter(a=4, b=2) # 从一组键值对创建
- 计数值的访问与缺失的键
- 当所访问的键不存在时,返回0,而不是KeyError;否则返回它的计数。
2.5.2 计数值的访问
>>> c = Counter("abcdefgab")
>>> c["a"]
2
>>> c["c"]
1
>>> c["h"]
0
2.5.3 计数器的更新(update和subtract)
- 可以使用一个iterable对象或者另一个Counter对象来更新键值。
- 计数器的更新包括增加和减少两种。其中,增加使用update()方法:
- 计数器的更新(update)
>>> c = Counter('which')
>>> c.update('witch') # 使用另一个iterable对象更新
>>> c['h']
3
>>> d = Counter('watch')
>>> c.update(d) # 使用另一个Counter对象更新
>>> c['h']
4
- 减少则使用subtract()方法:
- 计数器的更新(subtract)
>>> c = Counter('which')
>>> c.subtract('witch') # 使用另一个iterable对象更新
>>> c['h']
1
>>> d = Counter('watch')
>>> c.subtract(d) # 使用另一个Counter对象更新
>>> c['a']
-1
2.5.4 键的修改和删除
- 当计数值为0时,并不意味着元素被删除,删除元素应当使用del。
- 键的删除
>>> c = Counter("abcdcba")
>>> c
Counter({'a': 2, 'c': 2, 'b': 2, 'd': 1})
>>> c["b"] = 0
>>> c
Counter({'a': 2, 'c': 2, 'd': 1, 'b': 0})
>>> del c["a"]
>>> c
Counter({'c': 2, 'b': 2, 'd': 1})
2.5.5 elements()
- 返回一个迭代器。元素被重复了多少次,在该迭代器中就包含多少个该元素。元素排列无确定顺序,个数小于1的元素不被包含。
- elements()方法
>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> list(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
2.5.6 most_common([n])
- 返回一个TopN列表。如果n没有被指定,则返回所有元素。当多个元素计数值相同时,排列是无确定顺序的。
- most_common()方法
>>> c = Counter('abracadabra')
>>> c.most_common()
[('a', 5), ('r', 2), ('b', 2), ('c', 1), ('d', 1)]
>>> c.most_common(3)
[('a', 5), ('r', 2), ('b', 2)]
2.5.7 浅拷贝copy
>>> c = Counter("abcdcba")
>>> c
Counter({'a': 2, 'c': 2, 'b': 2, 'd': 1})
>>> d = c.copy()
>>> d
Counter({'a': 2, 'c': 2, 'b': 2, 'd': 1})
2.5.8 算术和集合操作
- +、-、&、|操作也可以用于Counter。其中&和|操作分别返回两个Counter对象各元素的最小值和最大值。需要注意的是,得到的Counter对象将删除小于1的元素。
- Counter对象的算术和集合操作
>>> c = Counter(a=3, b=1)
>>> d = Counter(a=1, b=2)
>>> c + d # c[x] + d[x]
Counter({'a': 4, 'b': 3})
>>> c - d # subtract(只保留正数计数的元素)
Counter({'a': 2})
>>> c & d # 交集: min(c[x], d[x])
Counter({'a': 1, 'b': 1})
>>> c | d # 并集: max(c[x], d[x])
Counter({'a': 3, 'b': 2})
2.5.9 其他常用操作
- 下面是一些Counter类的常用操作,来源于Python官方文档
Counter类常用操作
sum(c.values()) # 所有计数的总数
c.clear() # 重置Counter对象,注意不是删除
list(c) # 将c中的键转为列表
set(c) # 将c中的键转为set
dict(c) # 将c中的键值对转为字典
c.items() # 转为(elem, cnt)格式的列表
Counter(dict(list_of_pairs)) # 从(elem, cnt)格式的列表转换为Counter类对象
c.most_common()[:-n:-1] # 取出计数最少的n个元素
c += Counter() # 移除0和负值
3 time模块
- 时间模块
- 和时间有关系的我们就要用到时间模块。在使用模块之前,应该首先导入这个模块。
import time
#常用方法
time.sleep(secs)
# (线程)推迟指定的时间运行。单位为秒。
time.time()
# 获取当前时间戳
3.1 表示时间的三种方式
- 在Python中,通常有这三种方式来表示时间:时间戳、元组(struct_time)、格式化的时间字符串:
- 1, 时间戳(timestamp) :通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。我们运行“type(time.time())”,返回的是float类型。
- 2, 格式化的时间字符串(Format String): ‘1999-12-06’
python中时间日期格式化符号:
%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)
%f 毫秒(000000-999999)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%F 本地年月日的日期表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身
- 3,元组(struct_time) :struct_time元组共有9个元素共九个元素:(年,月,日,时,分,秒,一年中第几周,一年中第几天等)
|索引(Index)| 属性(Attribute) 值(Values)|
|:--|:---|:--|
|0 |tm_year(年) |比如2011|
|1 |tm_mon(月) |1 - 12|
|2 |tm_mday(日) |1 - 31|
|3 |tm_hour(时) |0 - 23|
|4 |tm_min(分) |0 - 59|
|5 |tm_sec(秒) |0 - 60|
|6 |tm_wday(weekday) |0 - 6(0表示周一)|
|7 |tm_yday(一年中的第几天) |1 - 366|
|8 |tm_isdst(是否是夏令时) |默认为0|
3.2 python中表示时间的几种格式
# 格式化时间 - - 字符串:人类可读性
# 时间戳时间 - - float时间: 计算机看的
# 结构话时间 - - 元组:计算机用的
import time
# 时间戳
print(time.time())
# 结果呈现
# 时间字符串
print(time.strftime("%F %X"))
print(time.strftime("%Y-%m-%d %H:%M:%S")) # year month day HOUR MINUTE SECOND
# 结果呈现
2018-09-29 10:34:52
2018-09-29 10:34:52
# 时间元组:localtime将一个时间戳转换为当前时区的struct_time
print(time.localtime())
print(time.localtime().tm_year)
# 结果呈现
time.struct_time(tm_year=2018, tm_mon=9, tm_mday=29, tm_hour=10, tm_min=34, tm_sec=52, tm_wday=5, tm_yday=272, tm_isdst=0)
2018
- 小结:时间戳是计算机能够识别的时间;时间字符串是人能够看懂的时间;元组则是用来操作时间的
3.3 几种格式之间的转换
# 时间戳-->结构化时间
# time.gmtime(时间戳) # UTC时间,与英国伦敦当地时间一致
# time.localtime(时间戳) # 当地时间。例如我们现在在北京执行这个方法:与UTC时间相差8小时,UTC时间+8小时 = 北京时间
>>>time.gmtime(1500000000)
time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, tm_hour=2, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0)
>>>time.localtime(1500000000)
time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, tm_hour=10, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0)
# 结构化时间-->时间戳
# time.mktime(结构化时间)
>>>time_tuple = time.localtime(1500000000)
>>>time.mktime(time_tuple)
1500000000.0
# 结构化时间-->字符串时间
# time.strftime("格式定义","结构化时间") 结构化时间参数若不传,则显示当前时间
>>>time.strftime("%Y-%m-%d %X")
'2017-07-24 14:55:36'
>>>time.strftime("%Y-%m-%d",time.localtime(1500000000))
'2017-07-14'
# 字符串时间-->结构化时间
# time.strptime(时间字符串,字符串对应格式)
>>>time.strptime("2017-03-16","%Y-%m-%d")
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)
>>>time.strptime("07/24/2017","%m/%d/%Y")
time.struct_time(tm_year=2017, tm_mon=7, tm_mday=24, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=205, tm_isdst=-1)
# 结构化时间 --> %a %b %d %H:%M:%S %Y串
# time.asctime(结构化时间) 如果不传参数,直接返回当前时间的格式化串
>>>time.asctime(time.localtime(1500000000))
'Fri Jul 14 10:40:00 2017'
>>>time.asctime()
'Mon Jul 24 15:18:33 2017'
# 时间戳 --> %a %b %d %H:%M:%S %Y串
# time.ctime(时间戳) 如果不传参数,直接返回当前时间的格式化串
>>>time.ctime()
'Mon Jul 24 15:19:07 2017'
>>>time.ctime(1500000000)
'Fri Jul 14 10:40:00 2017'
3.4 计算时间差
import time
true_time=time.mktime(time.strptime('2018-09-29 08:30:00','%Y-%m-%d %H:%M:%S'))
time_now=time.mktime(time.strptime('2018-09-30 17:30:00','%Y-%m-%d %H:%M:%S'))
dif_time=time_now-true_time
struct_time=time.gmtime(dif_time)
print('过去了%d年%d月%d天%d小时%d分钟%d秒'%(struct_time.tm_year-1970,struct_time.tm_mon-1,
struct_time.tm_mday-1,struct_time.tm_hour,
struct_time.tm_min,struct_time.tm_sec))
# 结果呈现
过去了0年0月1天9小时0分钟0秒
4 random模块
- 随机数模块
>>> import random
# 随机小数
>>> random.random() # 大于0且小于1之间的小数
0.7664338663654585
>>> random.uniform(1,3) # 大于1小于3的小数
1.6270147180533838
# 随机整数
>>> random.randint(1,5) # 大于等于1且小于等于5之间的整数
>>> random.randrange(1,10,2) # 大于等于1且小于10之间的奇数
# 随机选择一个返回
>>> random.choice([1,'23',[4,5]]) #1或者23或者[4,5]
# 随机选择多个返回,返回的个数为函数的第二个参数
>>> random.sample([1,'23',[4,5]],2) # 列表元素任意2个组合
[[4, 5], '23']
# 打乱列表顺序
>>> item=[1,3,5,7,9]
>>> random.shuffle(item) # 打乱次序
>>> item
[5, 1, 3, 7, 9]
>>> random.shuffle(item)
>>> item
[5, 9, 7, 1, 3]
4.1 生成随机验证码
import random
def v_code():
code = ''
for i in range(5):
num=random.randint(0,9)
alf=chr(random.randint(65,90))
add=random.choice([num,alf])
code="".join([code,str(add)])
return code
print(v_code())
# 结果呈现
W31PA
5 os模块
- 与操作系统交互的模块, os模块是与操作系统交互的一个接口
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.system("bash command") 运行shell命令,直接显示
os.popen("bash command).read() 运行shell命令,获取执行结果
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd
os.path
os.path.abspath(path) 返回path规范化的绝对路径
os.path.split(path) 将path分割成目录和文件名二元组返回
os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path) 如果path是绝对路径,返回True
os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.getatime(path) 返回path所指向的文件或者目录的最后访问时间
os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间
os.path.getsize(path) 返回path的大小
- 注意:os.stat('path/filename') 获取文件/目录信息 的结构说明
stat 结构
stat 结构:
st_mode: inode 保护模式
st_ino: inode 节点号。
st_dev: inode 驻留的设备。
st_nlink: inode 的链接数。
st_uid: 所有者的用户ID。
st_gid: 所有者的组ID。
st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。
st_atime: 上次访问的时间。
st_mtime: 最后一次修改的时间。
st_ctime: 由操作系统报告的"ctime"。在某些系统上(如Unix)是最新的元数据更改的时间,在其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。
os模块的属性
os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
os.linesep 输出当前平台使用的行终止符,win下为"\r\n",Linux下为"\n"
os.pathsep 输出用于分割文件路径的字符串 win下为;,Linux下为:
os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
6 sys模块
- sys模块是与python解释器交互的一个接口
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.exit(n) 退出程序,正常退出时exit(0),错误退出sys.exit(1)
sys.version 获取Python解释程序的版本信息
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
异常处理和status
import sys
try:
sys.exit(1)
except SystemExit as e:
print(e)
# 结果呈现
1
7 序列化模块
- 从原本的 字典、列表 等 数据类型 内容转换成一个 字符串 的 过程 就叫做 序列化。
- 从 字符串 转换成 原本的 字典、列表 等的 数据类型 的 过程 就叫做 反序列化。
7.1 为什么要有序列化模块
,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢?
没错序列化的过程就是从dic 变成str(dic)的过程。现在你可以通过str(dic),将一个名为dic的字典转换成一个字符串,
但是你要怎么把一个字符串转换成字典呢?
聪明的你肯定想到了eval(),如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。
eval()函数十分强大,但是eval是做什么的?e官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果。
BUT!强大的函数有代价。安全性是其最大的缺点。
想象一下,如果我们从文件中读出的不是一个数据结构,而是一句"删除文件"类似的破坏性语句,那么后果实在不堪设设想。
而使用eval就要担这个风险。
所以,我们并不推荐用eval方法来进行反序列化操作(将str转换成python中的数据结构)
7.2 序列化的目的
- 1、以某种存储形式使自定义对象持久化;
- 2、将对象从一个地方传递到另一个地方。
- 3、使程序更具维护性。
7.3 json
- 通用的序列化格式
- 只有很少的一部分数据类型能够通过jsonz转化成字符串
- Json模块提供了四个功能:dumps、dump、loads、load
7.3.1 dumps 序列化方法 loads 反序列化方法
- 所有的操作都在内存中
dic = {"k1":"v1", 1: "a"}
print(type(dic),dic)
import json
str_d = json.dumps(dic) # 序列化:将一个字典转换成一个字符串
print(type(str_d), str_d)
# 注意,json转换完的字符串类型的字典中的字符串是由""表示的
dic_d = json.loads(str_d) # 反序列化:将一个字符串格式的字典转换成一个字典
print(type(dic_d), dic_d)
# 注意,要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示
# 结果呈现
<class 'dict'> {'k1': 'v1', 1: 'a'}
<class 'str'> {"k1": "v1", "1": "a"}
<class 'dict'> {'k1': 'v1', '1': 'a'}
import json
list_dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}]
str_dic = json.dumps(list_dic) # 也可以处理嵌套的数据类型
print(type(str_dic),str_dic)
list_dic2 = json.loads(str_dic)
print(type(list_dic2),list_dic2)
# 结果呈现
<class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}]
<class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
7.3.2 json dump load
- 所有的操作都在磁盘上的文件中
import json
dic = {"k1":"v1", 1: "a"}
f = open("xlh_file", "w", encoding="utf-8")
json.dump(dic, f) # dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件
f.close()
f = open("xlh_file", "r", encoding="utf-8")
res = json.load(f) # load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回
f.close
print(type(res), res)
# xlh_file 文件
{"k1": "v1", "1": "a"}
# 结果呈现
<class 'dict'> {'k1': 'v1', '1': 'a'}
7.3.3 ensure_ascii关键字参数
import json
dic = {"k1":"北京", 1: "a"}
f = open("xlh_file", "w", encoding="utf-8")
json.dump(dic, f,ensure_ascii=False)
f.close()
f = open("xlh_file", "r", encoding="utf-8")
res = json.load(f)
f.close
print(type(res), res)
# 未使用了参数 ensure_ascii 文件 xlh_file 内容如下
{"k1": "\u5317\u4eac", "1": "a"}
# 使用了参数 ensure_ascii 文件 xlh_file 内容如下
{"k1": "北京", "1": "a"}
# 结果呈现
<class 'dict'> {'k1': '北京', '1': 'a'}
7.3.4 json 使用for 间接分部
import json
lst = [{"k1": "v1"},{"k2": "v2"},{"k3": "v3"} ]
f = open("xlh_file_1", "w")
for dic in lst:
str_dic = json.dumps(dic)
f.write(str_dic + "\n")
f.close()
f = open("xlh_file_1", "r")
lit = []
for line in f:
dic = json.loads(line.strip())
lit.append(dic)
f.close()
print(lit)
# 文件 xlh_file_1
{"k1": "v1"}
{"k2": "v2"}
{"k3": "v3"}
# 结果呈现
[{'k1': 'v1'}, {'k2': 'v2'}, {'k3': 'v3'}]
7.3.5 其它参数说明
Serialize obj to a JSON formatted str.(字符串表示的json对象)
Skipkeys:默认值是False,如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None),设置为False时,就会报TypeError的错误。此时设置成True,则会跳过这类key
ensure_ascii:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。)
If check_circular is false, then the circular reference check for container types will be skipped and a circular reference will result in an OverflowError (or worse).
If allow_nan is false, then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity).
indent:应该是一个非负的整型,如果是0就是顶格分行显示,如果为空就是一行最紧凑显示,否则会换行且按照indent的数值显示前面的空白分行显示,这样打印出来的json数据也叫pretty-printed json
separators:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(‘,’,’:’);这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。
default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError.
sort_keys:将数据根据keys的值进行排序。
To use a custom JSONEncoder subclass (e.g. one that overrides the .default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.
7.3.6 json 格式化输出
import json
data = {'username':['李华','二愣子'],'sex':'male','age':16}
json_dic2 = json.dumps(data,sort_keys=True,indent=2,separators=(',',':'),ensure_ascii=False)
print(json_dic2)
# 结果呈现
{
"age":16,
"sex":"male",
"username":[
"李华",
"二愣子"
]
}
7.4 pickle
- 所有的python中的数据类型都可以转化成字符串形式
- pickle序列化的内容只有python能理解
- 且部分反序列化依赖python代码
- pickle 使用方法与 json 一样,pickle 序列化的为 bytes 类型,读写文件必须加
"b"
import pickle
dic = {'k1':'v1','k2':'v2','k3':'v3'}
str_dic = pickle.dumps(dic)
print(str_dic) #一串二进制内容
dic2 = pickle.loads(str_dic)
print(dic2) #字典
# 结果呈现
b'\x80\x03}q\x00(X\x02\x00\x00\x00k1q\x01X\x02\x00\x00\x00v1q\x02X\x02\x00\x00\x00k2q\x03X\x02\x00\x00\x00v2q\x04X\x02\x00\x00\x00k3q\x05X\x02\x00\x00\x00v3q\x06u.'
{'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
import pickle
import time
struct_time = time.localtime(1000000000)
print(struct_time)
f = open('pickle_file','wb')
pickle.dump(struct_time,f)
f.close()
f = open('pickle_file','rb')
struct_time2 = pickle.load(f)
print(struct_time2.tm_year)
# 结果呈现
time.struct_time(tm_year=2001, tm_mon=9, tm_mday=9, tm_hour=9, tm_min=46, tm_sec=40, tm_wday=6, tm_yday=252, tm_isdst=0)
2001
7.4.1 pickle 可以直接分部
import pickle
import time
struct_time1 = time.localtime(1000000000)
struct_time2 = time.localtime(2000000000)
f = open('pickle_file','wb')
pickle.dump(struct_time1,f)
pickle.dump(struct_time2,f)
f.close()
f = open('pickle_file','rb')
struct_time1 = pickle.load(f)
struct_time2 = pickle.load(f)
print(struct_time1.tm_year)
print(struct_time2.tm_year)
# 文件 pickle_file
�ctime
struct_time
q (M�K K K K.K(KK�K tq}q(X tm_zoneqX ?D1¨²¡À¨º¡Á?¨º¡À??qX tm_gmtoffqM�pu�qRq.�ctime
struct_time
q (M�KKKK!KKK�K tq}q(X tm_zoneqX ?D1¨²¡À¨º¡Á?¨º¡À??qX tm_gmtoffqM�pu�qRq.
# 结果呈现
2001
2033
7.5 shelve
- 序列化句柄
- 使用句柄直接操作,非常方便
- shelve模块是一个简单的k,v将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据格式,是pickle 更上一层的封装。
- Shelve模块提供了基本的存储操作,Shelve中的open函数在调用的时候返回一个shelf对象,通过该对象可以存储内容,即像操作字典一样进行存储操作。当在该对象中查找元素时,对象会根据已经存储的版本进行重新构建,当给某个键赋值的时候,元素会被存储。
持久化存储
import shelve
def member_info(name, age):
print('Member info:', name, age)
name = ['Jack', 'Pumpkin', 'Tom']
info = {'name': 'Pumpkin', 'age': 20}
with shelve.open('shelve_demo') as data:
data['name'] = name
data['info'] = info
data['func'] = member_info
# shelve 会生成三个文件
# shelve_demo.bak
'name', (0, 43)
'info', (512, 45)
'func', (1024, 27)
# shelve_demo.dat
�]q (X JackqX PumpkinqX Tomqe. �}q (X nameqX PumpkinqX ageqKu. �c__main__
member_info
q .
# shelve_demo.dir
'name', (0, 43)
'info', (512, 45)
'func', (1024, 27)
解析文件内容
import shelve
def member_info(name, age):
print('Member info:', name, age)
with shelve.open('shelve_demo') as data:
print(data['name'])
print(data['info'])
print(data['func']('Alex', 22))
# 结果呈现
['Jack', 'Pumpkin', 'Tom']
{'name': 'Pumpkin', 'age': 20}
Member info: Alex 22
None
value值的修改
- 一般情况下,通过shelve来open一个对象后,只能进行一次赋值处理,赋值后不能再次更新处理
import shelve
def member_info(name, age):
print("Member info:", name, age)
name = ['Jack', 'Pumpkin', 'Tom']
info = {'name':'Pumpkin', 'age':18}
with shelve.open('shelve_demo') as data:
data['name'] = name
data['info'] = info
data['name'].append('Alex')
print(data['name'])
# 结果呈现
['Jack', 'Pumpkin', 'Tom'] # 第一次赋值后apend的元素并没有生效
- 再次open打开结果也是这样:
import shelve
def member_info(name, age):
print("Member info:", name, age)
name = ['Jack', 'Pumpkin', 'Tom']
info = {'name':'Pumpkin', 'age':18}
with shelve.open('shelve_demo') as data:
print(data['name'])
# 结果呈现
['Jack', 'Pumpkin', 'Tom']
- 方法一: shelve open一个对象后,先用临时变量指向对象副本,在临时变量上修改后让对象副本再次指向临时变量,从而覆盖保存对象副本。这种方法的本质是对open后的对象重新赋新值,并非在原有基础上进行update,也就是open后的对象内存指向地址发生了变化。
import shelve
def member_info(name, age):
print("Member info:", name, age)
name = ['Jack', 'Pumpkin', 'Tom']
info = {'name':'Pumpkin', 'age':18}
with shelve.open('shelve_demo') as data:
data['name'] = name
data['info'] = info
temp = data['name'] # 这里的关键点在于对临时变量的使用
temp.append('Alex')
data['name'] = temp
print(data['name'])
# 结果呈现
['Jack', 'Pumpkin', 'Tom', 'Alex']
- 方法二:借助open的writeback=True参数来实现,默认情况下该参数的值为False。
import shelve
def member_info(name, age):
print("Member info:", name, age)
name = ['Jack', 'Pumpkin', 'Tom']
info = {'name':'Pumpkin', 'age':18}
with shelve.open('shelve_demo', writeback=True) as data:
data['name'] = name
data['info'] = info
data['name'].append('Alex')
print(data['name'])
# 结果呈现
['Jack', 'Pumpkin', 'Tom', 'Alex']
- value值的更新还有一个update方法,使用起来也比较方便:
import shelve
def member_info(name, age):
print("Member info:", name, age)
name = ['Jack', 'Pumpkin', 'Tom']
info = {'name':'Pumpkin', 'age':18}
with shelve.open('shelve_demo', writeback=True) as data:
data['name'] = name
data['info'] = info
data.update({'name':['Jack', 'Pumpkin', 'Tom', 'Alex']}) # 这里也是重新赋值
print(data['name'])
# 结果呈现
['Jack', 'Pumpkin', 'Tom', 'Alex']
- load 方法看结果
import shelve
def member_info(name, age):
print("Member info:", name, age)
name = ['Jack', 'Pumpkin', 'Tom']
info = {'name':'Pumpkin', 'age':18}
with shelve.open('shelve_demo') as data:
print(data['name'])
# 结果呈现
['Jack', 'Pumpkin', 'Tom', 'Alex']
- get方法
- 通过shelve.open反序列化load对象到内存后,可以通过get方法来获取key对应的value:
import shelve
def member_info(name, age):
print("Member info:", name, age)
name = ['Jack', 'Pumpkin', 'Tom']
info = {'name':'Pumpkin', 'age':18}
with shelve.open('shelve_demo') as data:
print(data.get('name'))
# 结果呈现
['Jack', 'Pumpkin', 'Tom', 'Alex']
shelve概念总结:
- shelve模块可以看做是pickle模块的升级版,因为shelve使用的就是pickle的序列化协议,但是shelve比pickle提供的操作方式更加简单、方便;
- shelve模块相对于其它两个模块在将Python数据持久化到本地磁盘时有一个很明显的优点就是,它允许我们可以像操作dict一样操作被序列化的数据,而不必一次性的保存或读取所有数据。
- shelve模块持久化支持更多的python数据类型。
7.6 序列化小结:
-
1、需要与外部系统交互时用json模块;
-
2、需要将少量、简单Python数据持久化到本地磁盘文件时可以考虑用pickle模块;
-
3、需要将大量Python数据持久化到本地磁盘文件或需要一些简单的类似数据库的增删改查功能时,可以考虑用shelve模块。
-
序列化模块
- 数据类型转化成字符串的过程就是序列化
- 字符串转化成数据类型的过程就是反序列化
- 为了方便存储和网络传输
- json
- dumps
- loads
- dump 和文件有关
- load 不能load多次
- pickle
- 方法和json一样
- dump 和 load 的时候, 文件是 rb 或者 wb 打开的
- 支持 python 所有的数据类型
- 序列化和反序列化需要相同的环境
- shelve
- open 方法
- open方法获取一个文件句柄
- 操作和字典类似
8 hashlib模块
8.1 算法介绍
- Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。
- 什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
- 摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。
- 摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
- 我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?')
print md5.hexdigest()
# 结果呈现
d26a53750bc40b38b65a520292f69306
- 如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:
md5 = hashlib.md5()
md5.update('how to use md5 in ')
md5.update('python hashlib?')
print md5.hexdigest()
- MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:
import hashlib
sha1 = hashlib.sha1()
sha1.update('how to use sha1 in ')
sha1.update('python hashlib?')
print sha1.hexdigest()
- SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。
8.2 摘要算法应用
- 任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:
name | password
--------+----------
michael | 123456
bob | abc999
alice | alice2008
- 如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:
username | password
---------+---------------------------------
michael | e10adc3949ba59abbe56e057f20f883e
bob | 878ef96e86145580c38c87f0410ad153
alice | 99b1c2188db85afee403b1536010c2c9
- 考虑这么个情况,很多用户喜欢用123456,888888,password这些简单的口令,于是,黑客可以事先计算出这些常用口令的MD5值,得到一个反推表:
'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'
- 这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用口令的用户账号。
- 对于用户来讲,当然不要使用过于简单的口令。但是,我们能否在程序设计上对简单口令加强保护呢?
- 由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:
hashlib.md5("salt".encode("utf8"))
- 经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。
import hashlib # 提供摘要算法的模块
md5 = hashlib.md5()
md5.update(b"min123")
print(md5.hexdigest()) # 07105a4643a5c133207afd3ccd98badc
-
不管算法多么不同,摘要的功能始终不变
-
对于相同的字符串使用同一个算法进行摘要,得到的值总是不变的
-
使用不用算法对相同的字符串进行摘要,得到的值是不同的
-
不管使用什么算法,使用hashlib的方式不变
-
sha 算法 随着 算法复杂程度的增加 摘要的时间和空间成本都会增加
-
用户登录
import hashlib
usr = input("username: ")
pwd = input("password: ")
with open("userinfo") as f:
for line in f:
user, passwd, role = line.split("|")
md5 = hashlib.md5()
md5.update(bytes(pwd,encoding="utf-8"))
md5_pwd = md5.hexdigest()
if usr == user and md5_pwd == passwd:
print("登录成功")
- 加盐
import hashlib
md5 = hashlib.md5(bytes("salt", encoding="utf-8"))
md5.update(b"min123")
print(md5.hexdigest()) # f4ce519205cdd10528f9b0f0b54a8927
- 动态加盐
- 用户名 密码
- 使用用户名的一部分或者 直接使用整个用户名作为盐
import hashlib
md5 = hashlib.md5(bytes("salt", encoding="utf-8")+b"min")
md5.update(b"min123")
print(md5.hexdigest()) # 6714cc6db8867ccbf8cb21f990e81937
- 对一个文件进行摘要算法,最后计算出这个文件的md5值
import hashlib
md5 = hashlib.md5()
md5.update(b"egg")
md5.update(b"123")
print(md5.hexdigest()) # 042c9076128b0658da027bf39d1dd4c3
- 不管算法多么不同,摘要的功能始终不变
- 对于相同的字符串使用同一个算法进行摘要,得到的值总是不变的
- 使用不用算法对相同的字符串进行摘要,得到的值是不同的
- 不管使用什么算法,使用hashlib的方式不变
- sha 算法 随着 算法复杂程度的增加 摘要的时间和空间成本都会增加
- 摘要算法
- 密码的密文存储
- md5 加密认证
- 正常的MD5算法
- 加盐的
- 动态加盐的
- 文件的一致性验证,在文件的一致性校验中不能加盐
- 在下载的时候 检查下载的文件和远程服务器上文件是否一致
- 两台机器上的两个文件,检查这两个文件是否相等
9 configparse模块
- 该模块适用于配置文件的格式与windows ini文件类似,可以包含一个或多个节(section),每个节可以有多个参数(键=值)。
9.1 创建文件
- 来看一个好多软件的常见文档格式如下:
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes
[bitbucket.org]
User = hg
[topsecret.server.com]
Port = 50022
ForwardX11 = no
- 用python生成一个以上格式的文档
import configparser
config = configparser.ConfigParser()
config["DEFAULT"] = {'ServerAliveInterval': '45',
'Compression': 'yes',
'CompressionLevel': '9',
'ForwardX11':'yes'
}
config['bitbucket.org'] = {'User':'hg'}
config['topsecret.server.com'] = {'Host Port':'50022','ForwardX11':'no'}
with open('example.ini', 'w') as configfile:
config.write(configfile)
9.2 查找文件
import configparser
config = configparser.ConfigParser()
#---------------------------查找文件内容,基于字典的形式
print(config.sections()) # []
config.read('example.ini')
print(config.sections()) # ['bitbucket.org', 'topsecret.server.com']
print('bytebong.com' in config) # False
print('bitbucket.org' in config) # True
print(config['bitbucket.org']["user"]) # hg
print(config['DEFAULT']['Compression']) #yes
print(config['topsecret.server.com']['ForwardX11']) #no
print(config['bitbucket.org']) #<Section: bitbucket.org>
for key in config['bitbucket.org']: # 注意,有default会默认default的键
print(key)
print(config.options('bitbucket.org')) # 同for循环,找到'bitbucket.org'下所有键
print(config.items('bitbucket.org')) #找到'bitbucket.org'下所有键值对
print(config.get('bitbucket.org','compression')) # yes get方法Section下的key对应的value
9.3 增删改操作
import configparser
config = configparser.ConfigParser()
config.read('example.ini')
config.add_section('yuan')
config.remove_section('bitbucket.org')
config.remove_option('topsecret.server.com',"forwardx11")
config.set('topsecret.server.com','k1','11111')
config.set('yuan','k2','22222')
f = open('new2.ini', "w")
config.write(f)
f.close()
10 logging模块
import logging
logging.debug('debug message') # 排错信息 # 低级别
logging.info('info message') # 正常信息
logging.warning('warning message') # 警告信息
logging.error('error message') # 错误信息
logging.critical('critical message') # 严重错误信息 # 高级别
- 默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG),默认的日志格式为日志级别:Logger名称:用户输出消息。
10.1 basicconfig
- 灵活配置日志级别,日志格式,输出位置:
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='/tmp/test.log',
filemode='w')
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
- basicconfig 简单 能做的事情相对少
- 中文的乱码问题 解决不了
- 不能同时往文件和屏幕上输出
import logging
logging.basicConfig(level=logging.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')
try:
int(input("num >>> "))
except ValueError:
logging.error('error message> 输入的值不是一个数字')
- 配置参数:
logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有:
filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger(后边会讲解具体概念)的日志级别
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。
format参数中可能用到的格式化串:
%(name)s Logger的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s用户输出的消息
10.2 logger对象配置
import logging
logger = logging.getLogger()
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log',encoding='utf-8')
# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh) #logger对象可以添加多个fh和ch对象
logger.addHandler(ch)
logger.debug('logger debug message')
logger.info('logger info message')
logger.warning('logger warning message')
logger.error('logger error message')
logger.critical('logger critical message')
import logging
logger = logging.getLogger()
fh = logging.FileHandler("log.log", encoding="utf-8")
sh = logging.StreamHandler() # 创建一个屏幕控制对象
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
formatter2 = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - [line:%(lineno)d]- %(message)s')
# 文件操作符 和 格式关联
fh.setFormatter(formatter)
sh.setFormatter(formatter2)
# logger 对象 和 文件操作符 关联
logger.addHandler(fh)
logger.addHandler(sh)
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
-
logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。另外,可以通过:logger.setLevel(logging.Debug)设置级别,当然,也可以通过
-
fh.setLevel(logging.Debug)单对文件流设置某个级别。
-
程序的充分解耦
-
让程序变的高可定制化