为什么是模块?
在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就可以称之为一个模块(Module)。
使用模块有什么好处?
- 最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Python内置的模块和来自第三方的模块。
- 使用模块还可以避免函数名和变量名冲突。每个模块有独立的命名空间,因此相同名字的函数和变量完全可以分别存在不同的模块中,所以,我们自己在编写模块时,不必考虑名字会与其他模块冲突
模块分类
模块分为三种:
- 内置标准模块(又称标准库)执行help(‘modules’)查看所有python自带模块列表
- 第三方开源模块,可通过pip install 模块名 联网安装
- 自定义模块
模块导入&调用
import module_a #导入
from module import xx
from module.xx.xx import xx as rename #导入后重命令
from module.xx.xx import * #导入一个模块下的所有方法,不建议使用
__import__("test_name") # 动态导入
__import__("basicGrammar.module.my_module") # 动态导入(跨包)
importlib.import_module("test_name") # 动态导入
importlib.import_module("basicGrammar.module.my_module") # 动态导入(跨包)
module_a.xxx #调用
注意:模块一旦被调用,即相当于执行了另外一个py文件里的代码
自定义模块
这个最简单, 创建一个.py文件,就可以称之为模块,就可以在另外一个程序里导入
模块查找路径(跨目录导入)
发现,自己写的模块只能在当前路径下的程序里才能导入,换一个目录再导入自己的模块就报错说找不到了, 这是为什么?
这与导入模块的查找路径有关
import sys
print(sys.path)
输出(注意不同的电脑可能输出的不太一样)
['D:\\workspace_python\\basis_learn01\\basicGrammar\\module',
'D:\\workspace_python\\basis_learn01',
'E:\\PyCharm\\PyCharm 2020.1.3\\plugins\\python\\helpers\\pycharm_display', 'E:\\PyCharm\\Python38\\python38.zip',
'E:\\PyCharm\\Python38\\DLLs',
'E:\\PyCharm\\Python38\\lib',
'E:\\PyCharm\\Python38',
'D:\\workspace_python\\basis_learn01\\venv',
'D:\\workspace_python\\basis_learn01\\venv\\lib\\site-packages',
'E:\\PyCharm\\PyCharm 2020.1.3\\plugins\\python\\helpers\\pycharm_matplotlib_backend']
你导入一个模块时,Python解释器会按照上面列表顺序去依次到每个目录下去匹配你要导入的模块名,只要在一个目录下匹配到了该模块名,就立刻导入,不再继续往后找。
注意列表第一个元素为空,即代表当前目录,所以你自己定义的模块在当前目录会被优先导入。
我们自己创建的模块若想在任何地方都能调用,那就得确保你的模块文件至少在模块路径的查找列表中。
我们一般把自己写的模块放在一个带有“site-packages”字样的目录里,我们从网上下载安装的各种第三方的模块一般都放在这个目录。
第3方开源模块的安装使用
https://pypi.python.org/pypi 是python的开源模块库,截止2019年4.30日 ,已经收录了175870个来自全世界python开发者贡献的模块,几乎涵盖了你想用python做的任何事情。 事实上每个python开发者,只要注册一个账号就可以往这个平台上传你自己的模块,这样全世界的开发者都可以容易的下载并使用你的模块。
直接通过pip安装
pip install paramiko #paramiko 是模块名
pip命令会自动下载模块包并完成安装。
软件一般会被自动安装你python安装目录的这个子目录里
'D:\\workspace_python\\basis_learn01\\venv\\lib\\site-packages',
使用国内镜像(暂时)
pip命令默认会连接在国外的python官方服务器下载,速度比较慢,你还可以使用国内的豆瓣源,数据会定期同步国外官网,速度快好多
pip install -i http://pypi.douban.com/simple/ alex_sayhi --trusted-host pypi.douban.com #alex_sayhi是模块名
-i 后面跟的是豆瓣源地址
—trusted-host 得加上,是通过网站https安全验证用的
使用国内镜像(永久)
- 在文件夹的地址栏输入 %appdata% (即进入这个文件夹)。
- 在当前文件夹下新建一个pip文件夹。
- 进入pip文件夹,新建一个pip.ini文件
- 在pip.ini文件中写下如下内容:
[global]
index-url=http://mirrors.aliyun.com/pypi/simple/
[install]
trusted-host=mirrors.aliyun.com
包&跨目录导入模块
当你的模块文件越来越多,就需要对模块文件进行划分,比如把负责跟数据库交互的都放一个文件夹,把与页面交互相关的放一个文件夹,
my_proj/
├── apeland_web #代码目录
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── my_proj #配置文件目录
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
像上面这样,一个文件夹管理多个模块文件,这个文件夹就被称为包
一个包就是一个文件夹,但该文件夹下必须存在 init.py 文件, 该文件的内容可以为空, int.py用于标识当前文件夹是一个包。
这个init.py的文件主要是用来对包进行一些初始化的,当当前这个package被别的程序调用时,init.py文件会先执行,一般为空, 一些你希望只要package被调用就立刻执行的代码可以放在init.py里,一会后面会演示。
跨模块导入
目录结构如下
根据上面的结构,如何实现在包.py
里调用my_package
模块?
直接导入的话,会报错,说找到不模块
方式一
我们自己创建的模块若想在任何地方都能调用,那就得确保你的模块文件至少在模块路径的查找列表中。
我们一般把自己写的模块放在一个带有“site-packages”字样的目录里,我们从网上下载安装的各种第三方的模块一般都放在这个目录。
from my_package.test import test_p
test_p()
方式二
添加环境变量,把父亲级的路径添加到sys.path中,就可以了,这样导入 就相当于从父亲级开始找模块了。
import sys
import os
# 固定获取目录(不可取)
# base_dir = "D:\\workspace_python\\basis_learn01\\basicGrammar"
print(os.path.dirname(os.path.dirname(__file__)))
# 动态的获取目录
base_dir = os.path.dirname(os.path.dirname(__file__))
sys.path.append(base_dir)
from my_package.test import test_p
test_p()
方式三(官方推荐)
虽然通过添加环境变量的方式可以实现跨模块导入,但是官方不推荐这么干,因为这样就需要在每个目录下的每个程序里都写一遍添加环境变量的代码。
官方推荐的玩法是,在项目里创建个入口程序,整个程序调用的开始应该是从入口程序发起,这个入口程序一般放在项目的顶级目录
这样做的好处是,项目中的二级目录 module.包.py中再调用他表亲my_package.test.py时就不用再添加环境变量了。
原因是由于manage.py在顶层,manage.py启动时项目的环境变量路径就会自动变成….xxx/basicGrammar/这一级别
其实三种方式本质上都是通过
模块查找路径
来跨目录导入的
方式四(动态加载)
import importlib
__import__("basicGrammar.module.my_module")
importlib.import_module("basicGrammar.module.my_module")
random
#1.随机的浮点数,范围是在0.0~1.0之间:random.random()
import random
random.random()
0.644354136192532
#2.函数随机生成一个[a,b]范围内的浮点数:random.uniform(a, b)
random.uniform(0, 100)
24.333751706253736
#3.随机生成一个范围[a, b]内的整数:random.randint(a, b)
random.randint(1,10)
6
#4.随机选取一个元素返回:random.choice()
可以用于字符串、列表、元组等
random.choice([1,2,3]) #列表
3
random.choice((1,2,3)) #元组
2
random.choice("hello world") #字符串
'h'
#随机生成字符
random.choice('abcdefghijklmnopqrstuvwxyz!@#$%^&*()')
'l'
#5.随机打乱元素:random.shuffle()
l = [1,2,3,4]
random.shuffle(l)
print(l)
[2, 4, 3, 1]
#6.从序列a中截取指定长度n的片段:random.sample(a, n)
a = [1,2,3,4,5]
b = "hello world"
n = 2
random.sample(a, n)
[5, 3]
random.sample(b, n)
['o', 'r']
#7.随机选取a到b间的奇数1/偶数2:random.randrange(a, b, 2)
random.randrange(0, 11, 1) #奇数
5
random.randrange(0, 11, 2) #偶数
10
简单描述
- 随机的浮点数,范围是在0.0~1.0之间:random.random();
- 函数随机生成一个[a,b]范围内的浮点数:random.uniform(a, b);
- 随机生成一个范围[a, b]内的整数:random.randint(a, b);
- 随机选取一个元素返回或随机生成字符:random.choice();
- 随机打乱元素:random.shuffle();
- 从序列a中截取指定长度n的片段:random.sample(a, n);
- 随机选取a到b间的奇数1/偶数2:random.randrange(a, b, 2)。
chardet
安装
pip install chardet
使用
import chardet
file = open(file="F:\\Animation\\0.txt", mode="r")
info = file.read(6)
print("编码格式:", chardet.detect(info.encode()).get("encoding"))
file.close()
输出
编码格式: utf-8
os
import os
# 修改文件的名称
os.replace(file_new_name, file_name) # win
# os.rename(file_new_name, file_name) # mac
更多方法
得到当前工作目录,即当前Python脚本工作的目录路径: os.getcwd()
返回指定目录下的所有文件和目录名:os.listdir()
函数用来删除一个文件:os.remove()
删除多个目录:os.removedirs(r“c:\python”)
检验给出的路径是否是一个文件:os.path.isfile()
检验给出的路径是否是一个目录:os.path.isdir()
判断是否是绝对路径:os.path.isabs()
检验给出的路径是否真地存:os.path.exists()
返回一个路径的目录名和文件名:os.path.split() e.g os.path.split('/home/swaroop/byte/code/poem.txt') 结果:('/home/swaroop/byte/code', 'poem.txt')
分离扩展名:os.path.splitext() e.g os.path.splitext('/usr/local/test.py') 结果:('/usr/local/test', '.py')
获取路径名:os.path.dirname()
获得绝对路径: os.path.abspath()
获取文件名:os.path.basename()
运行shell命令: os.system()
读取操作系统环境变量HOME的值:os.getenv("HOME")
返回操作系统所有的环境变量: os.environ
设置系统环境变量,仅程序运行时有效:os.environ.setdefault('HOME','/home/alex')
给出当前平台使用的行终止符:os.linesep Windows使用'\r\n',Linux and MAC使用'\n'
指示你正在使用的平台:os.name 对于Windows,它是'nt',而对于Linux/Unix用户,它是'posix'
重命名:os.rename(old, new) 如果命名文件已经存在,则无法创建该文件
替换: os.replace(src, dst) 就算命名文件已经存在,也可以创建该文件
创建多级目录:os.makedirs(r“c:\python\test”)
创建单个目录:os.mkdir(“test”)
获取文件属性:os.stat(file)
修改文件权限与时间戳:os.chmod(file)
获取文件大小:os.path.getsize(filename)
结合目录名与文件名:os.path.join(dir,filename)
改变工作目录到dirname: os.chdir(dirname)
获取当前终端的大小: os.get_terminal_size()
杀死进程: os.kill(10884,signal.SIGKILL)
遍历目录中的文件(含递归):os.walk()
sys
读取外部指令
import sys
command = sys.argv
old_area = command[1]
new_area = command[2]
print("%s---%s" % (old_area, new_area))
手动运行
常用方法
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.exit(n) 退出程序,正常退出时exit(0)
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
sys.stdout.write('please:') #标准输出 , 引出进度条的例子, 注,在py3上不行,可以用print代替
val = sys.stdin.readline()[:-1] #标准输入
sys.getrecursionlimit() #获取最大递归层数
sys.setrecursionlimit(1200) #设置最大递归层数
sys.getdefaultencoding() #获取解释器默认编码
sys.getfilesystemencoding #获取内存数据存到文件里的默认编码
copy
深拷贝
import copy
data_copy = copy.deepcopy(data)
time & datetime模块
在平常的代码中,我们常常需要与时间打交道。在Python中,与时间处理有关的模块就包括:time,datetime,calendar(很少用,不讲),下面分别来介绍。
我们写程序时对时间的处理可以归为以下3种:
-
时间的显示,在屏幕显示、记录日志等
-
时间的转换,比如把字符串格式的日期转成Python中的日期类型
-
时间的运算,计算两个日期间的差值等
time
在Python中,通常有这几种方式来表示时间:
- 时间戳(timestamp), 表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。例子:1554864776.161901
- 格式化的时间字符串,比如“2020-10-03 17:54”
- 元组(struct_time)共九个元素。由于Python的time模块实现主要调用C库,所以各个平台可能有所不同,mac上:time.struct_time(tm_year=2020, tm_mon=4, tm_mday=10, tm_hour=2, tm_min=53, tm_sec=15, tm_wday=2, tm_yday=100, tm_isdst=0)
索引(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 - 61
6 tm_wday(weekday) 0 - 6(0表示周日)
7 tm_yday(一年中的第几天) 1 - 366
8 tm_isdst(是否是夏令时) 默认为-1
UTC时间
UTC(Coordinated Universal Time,世界协调时)亦即格林威治天文时间,世界标准时间。在中国为UTC+8,又称东8区。DST(Daylight Saving Time)即夏令时。
time模块的方法
- time.localtime([secs]):将一个时间戳转换为当前时区的struct_time。若secs参数未提供,则以当前时间为准。
- time.struct_time(tm_year=2020, tm_mon=7, tm_mday=29, tm_hour=15, tm_min=15, tm_sec=50, tm_wday=2, tm_yday=211, tm_isdst=0)
- time.gmtime([secs]):和localtime()方法类似,gmtime()方法是将一个时间戳转换为UTC时区(0时区)的struct_time。
- time.time():返回当前时间的时间戳。1596006950.818224
- time.mktime(t):将一个struct_time转化为时间戳。
- time.sleep(secs):线程推迟指定的时间运行,单位为秒。
- time.asctime([t]):把一个表示时间的元组或者struct_time表示为这种形式:’Sun Oct 1 12:04:38 2019’。如果没有参数,将会将time.localtime()作为参数传入。
- time.ctime([secs]):把一个时间戳(按秒计算的浮点数)转化为time.asctime()的形式。如果参数未给或者为None的时候,将会默认time.time()为参数。它的作用相当于time.asctime(time.localtime(secs))。
- time.strftime(format[, t]):把一个代表时间的元组或者struct_time(如由time.localtime()和time.gmtime()返回)转化为格式化的时间字符串。如果t未指定,将传入time.localtime()。
- 举例:time.strftime("%Y %m %d %H:%M:%S %p %j %z %Z", time.localtime())
- 输出:2020 07 29 15:36:17 PM 211 +0800 中国标准时间
- time.strptime(string[, format]):把一个格式化时间字符串转化为struct_time。实际上它和strftime()是逆操作。
- 举例:time.strptime(‘2017-10-3 17:54’,”%Y-%m-%d %H:%M”)
- 输出:time.struct_time(tm_year=2017, tm_mon=10, tm_mday=3, tm_hour=17, tm_min=54, tm_sec=0, tm_wday=1, tm_yday=276, tm_isdst=-1)
- 字符串转时间格式对应表
Meaning | Notes | |
---|---|---|
%a | Locale’s abbreviated weekday name. | |
%A | Locale’s full weekday name. | |
%b | Locale’s abbreviated month name. | |
%B | Locale’s full month name. | |
%c | Locale’s appropriate date and time representation. | |
%d | Day of the month as a decimal number [01,31]. | |
%H | Hour (24-hour clock) as a decimal number [00,23]. | |
%I | Hour (12-hour clock) as a decimal number [01,12]. | |
%j | Day of the year as a decimal number [001,366]. | |
%m | Month as a decimal number [01,12]. | |
%M | Minute as a decimal number [00,59]. | |
%p | Locale’s equivalent of either AM or PM. | (1) |
%S | Second as a decimal number [00,61]. | (2) |
%U | Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0. | (3) |
%w | Weekday as a decimal number [0(Sunday),6]. | |
%W | Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. | (3) |
%x | Locale’s appropriate date representation. | |
%X | Locale’s appropriate time representation. | |
%y | Year without century as a decimal number [00,99]. | |
%Y | Year with century as a decimal number. | |
%z | Time zone offset indicating a positive or negative time difference from UTC/GMT of the form +HHMM or -HHMM, where H represents decimal hour digits and M represents decimal minute digits [-23:59, +23:59]. | |
%Z | Time zone name (no characters if no time zone exists). | |
%% | A literal ‘%’ character. |
最后为了容易记住转换关系,看下图
datetime
相比于time模块,datetime模块的接口则更直观、更容易调用
datetime模块定义了下面这几个类:
- datetime.date:表示日期的类。常用的属性有year, month, day;
- datetime.time:表示时间的类。常用的属性有hour, minute, second, microsecond;
- datetime.datetime:表示日期时间。
- datetime.timedelta:表示时间间隔,即两个时间点之间的长度。
- datetime.tzinfo:与时区有关的相关信息。(这里不详细充分讨论该类,感兴趣的童鞋可以参考python手册)
我们需要记住的方法仅以下几个:
- d=datetime.datetime.now() 返回当前的datetime日期类型
d.timestamp(),d.today(), d.year,d.timetuple()等方法可以调用
-
时间戳和datetime日期类型的转换
print(datetime.datetime.now().timestamp()) # 1596018092.231518 print(datetime.datetime.fromtimestamp(time.time())) # 2020-07-29 18:21:32.231519
-
时间运算和时间替换
print(datetime.datetime.now()-datetime.timedelta(days=3, minutes=2)) print(datetime.datetime.now().replace(year=2009,month=10,day=7,minute=48,second=50)) --- 2020-07-26 18:21:22.140702 2009-10-07 18:48:50.140702
-
时区和时间字符串转换
print(datetime.datetime.now().astimezone(pytz.timezone("Asia/Shanghai")).strftime("%Y %m %d %H:%M:%S")) # 2020 07 29 18:36:00 print(datetime.datetime.now().astimezone(pytz.timezone("Africa/Asmara")).strftime("%Y %m %d %H:%M:%S")) # 2020 07 29 13:36:00
pytz(时区)
安装
pip install pytz
import pytz
# 查看所有时区
print(pytz.all_timezones)
print(type(pytz.timezone("Asia/Shanghai")))
print(datetime.datetime.now().astimezone(pytz.timezone("Asia/Shanghai")).strftime("%Y %m %d %H:%M:%S"))
print(datetime.datetime.now().astimezone(pytz.timezone("Africa/Asmara")).strftime("%Y %m %d %H:%M:%S"))
输出
['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', 'Africa/Algiers', 'Africa/Asmara', .....]
<class 'pytz.tzfile.Asia/Shanghai'>
2020 07 29 18:36:55
2020 07 29 13:36:55
pickcle & json(序列化)
什么叫序列化?
序列化是指把内存里的数据类型转变成字符串,以使其能存储到硬盘或通过网络传输到远程,因为硬盘或网络传输时只能接受bytes
为什么要序列化?
你打游戏过程中,打累了,停下来,关掉游戏、想过2天再玩,2天之后,游戏又从你上次停止的地方继续运行,你上次游戏的进度肯定保存在硬盘上了,是以何种形式呢?游戏过程中产生的很多临时数据是不规律的,可能在你关掉游戏时正好有10个列表,3个嵌套字典的数据集合在内存里,需要存下来?你如何存?把列表变成文件里的多行多列形式?那嵌套字典呢?根本没法存。所以,若是有种办法可以直接把内存数据存到硬盘上,下次程序再启动,再从硬盘上读回来,还是原来的格式的话,那是极好的。
用于序列化的两个模块
- json,用于字符串 和 python数据类型间进行转换
- pickle,用于python特有的类型 和 python的数据类型间进行转换
pickle
pickle模块提供了四个功能:dumps、dump、loads、load
dumps和loads
print(pickle.dumps(info))
print(pickle.dumps(name))
print(pickle.loads(b"\x80\x04\x95\x1b\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x04Alex\x94\x8c\x03age\x94K\x14u."))
print(pickle.loads(b"\x80\x04\x95\x08\x00\x00\x00\x00\x00\x00\x00\x8c\x04Alex\x94."))
输出
b'\x80\x04\x95\x1b\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x04Alex\x94\x8c\x03age\x94K\x14u.'
b'\x80\x04\x95\x08\x00\x00\x00\x00\x00\x00\x00\x8c\x04Alex\x94.'
{'name': 'Alex', 'age': 20}
Alex
dump和load
file = open("F:\\Animation\\10.txt", "wb")
pickle.dump(info, file)
pickle.dump(name, file)
file.close()
"F:\\Animation\\10.txt", "wb"
file = open("F:\\Animation\\10.txt", "rb")
print(pickle.load(file)) # {'name': 'Alex', 'age': 20}
print(pickle.load(file)) # Alex
file.close()
输出
{'name': 'Alex', 'age': 20}
Alex
json
Json模块也提供了四个功能:dumps、dump、loads、load,用法跟pickle一致
dumps和loads
print(json.dumps(name)) # 注意json dumps生成的是字符串,不是bytes
print(json.dumps(info))
print(json.loads(json.dumps(info)))
输出
"Alex"
{"name": "Alex", "age": 20}
{'name': 'Alex', 'age': 20}
dump和load
需要注意的一点是json在文件中序列化的数据最好是字典的格式。因为读取数据的时候只能以
字典的数据结构
取出一次。
# 因为json的load只能load一次,将数据转为字典。所以序列化的数据只能是字典
file = open("F:\\Animation\\10.txt", "w")
info.setdefault("姓名", name)
json.dump(info, file)
file.close()
file = open("F:\\Animation\\10.txt", "r")
print(json.load(file))
file.close()
输出
{'name': 'Alex', 'age': 20, '姓名': 'Alex'}
json与pickle的比较
JSON:
-
优点:跨语言(不同语言间的数据传递可用json交接)、体积小
-
缺点:只能支持int\str\list\tuple\dict
Pickle:
-
优点:专为python设计,支持python所有的数据类型
-
缺点:只能在python中使用,存储数据占空间大
其实就是Json和序列化的一个比较
加密算法
hash
Hash,一般翻译做“散列”,也有直接音译为”哈希”的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。
简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
HASH主要用于信息安全领域中加密算法,他把一些不同长度的信息转化成杂乱的128位的编码里,叫做HASH值.也可以说,hash就是找到一种数据内容和数据存放地址之间的映射关系
MD5
什么是MD5算法
MD5讯息摘要演算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码杂凑函数,可以产生出一个128位的散列值(hash value),用于确保信息传输完整一致。MD5的前身有MD2、MD3和MD4。
MD5功能
输入任意长度的信息,经过处理,输出为128位的信息(数字指纹);
不同的输入得到的不同的结果(唯一性);
MD5算法的特点
- 压缩性:任意长度的数据,算出的MD5值的长度都是固定的
- 容易计算:从原数据计算出MD5值很容易
- 抗修改性:对原数据进行任何改动,修改一个字节生成的MD5值区别也会很大
- 强抗碰撞:已知原数据和MD5,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
MD5算法是否可逆?
MD5不可逆的原因是其是一种散列函数,使用的是hash算法,在计算过程中原文的部分信息是丢失了的。
MD5用途
- 防止被篡改:
- 比如发送一个电子文档,发送前,我先得到MD5的输出结果a。然后在对方收到电子文档后,对方也得到一个MD5的输出结果b。如果a与b一样就代表中途未被篡改。
- 比如我提供文件下载,为了防止不法分子在安装程序中添加木马,我可以在网站上公布由安装文件得到的MD5输出结果。
- SVN在检测文件是否在CheckOut后被修改过,也是用到了MD5.
- 防止直接看到明文:
- 现在很多网站在数据库存储用户的密码的时候都是存储用户密码的MD5值。这样就算不法分子得到数据库的用户密码的MD5值,也无法知道用户的密码。(比如在UNIX系统中用户的密码就是以MD5(或其它类似的算法)经加密后存储在文件系统中。当用户登录的时候,系统把用户输入的密码计算成MD5值,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这不但可以避免用户的密码被具有系统管理员权限的用户知道,而且还在一定程度上增加了密码被破解的难度。)
- 防止抵赖(数字签名):
- 这需要一个第三方认证机构。例如A写了一个文件,认证机构对此文件用MD5算法产生摘要信息并做好记录。若以后A说这文件不是他写的,权威机构只需对此文件重新产生摘要信息,然后跟记录在册的摘要信息进行比对,相同的话,就证明是A写的了。这就是所谓的“数字签名”。
使用
import hashlib
md5 = hashlib.md5()
md5.update("你好呀".encode("utf-8"))
print(md5.digest())
print(md5.hexdigest())
md5.update("你好呀".encode("utf-8")) # 相加
print(md5.digest())
print(md5.hexdigest())
md5_2 = hashlib.md5("你好呀".encode("utf-8"))
print(md5_2.hexdigest())
输出
b'Oe\xfd\xb3>\x0f+\xd0\xdek\xd1\xb4\x1f\xde\xa9h'
4f65fdb33e0f2bd0de6bd1b41fdea968
b'\x0e\x10\xe6\xe2.\x82_\x9e\xbfe\xa7\x12\xde\x80\xd0\xec'
0e10e6e22e825f9ebf65a712de80d0ec
4f65fdb33e0f2bd0de6bd1b41fdea968
一般还需要加盐
SHA-1
安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。
SHA是美国国家安全局设计的,由美国国家标准和技术研究院发布的一系列密码散列函数。
由于MD5和SHA-1于2005年被山东大学的教授王小云破解了,科学家们又推出了SHA224, SHA256, SHA384, SHA512,当然位数越长,破解难度越大,但同时生成加密的消息摘要所耗时间也更长。目前最流行的是加密算法是SHA-256 .
使用
print("-----SH1-----")
print(hashlib.sha1("你好呀".encode("utf-8")).hexdigest())
print(hashlib.sha1("你好呀".encode("utf-8")).hexdigest())
print("-----SH256-----")
print(hashlib.sha256("你好呀".encode("utf-8")).hexdigest())
print(hashlib.sha256("你好呀".encode("utf-8")).hexdigest())
输出
-----SH1-----
06161148546813f145022d12c7df76b8546edcea
06161148546813f145022d12c7df76b8546edcea
-----SH256-----
d2033138c3b3be1321ad29d0aff15a4b1b47934a2b91afcea6f59b96a9fed115
d2033138c3b3be1321ad29d0aff15a4b1b47934a2b91afcea6f59b96a9fed115
MD5与SHA-1的比较
由于MD5与SHA-1均是从MD4发展而来,它们的结构和强度等特性有很多相似之处,SHA-1与MD5的最大区别在于其摘要比MD5摘要长32 比特。对于强行攻击,产生任何一个报文使之摘要等于给定报文摘要的难度:MD5是2128数量级的操作,SHA-1是2160数量级的操作。产生具有相同摘要的两个报文的难度:MD5是264是数量级的操作,SHA-1 是280数量级的操作。因而,SHA-1对强行攻击的强度更大。但由于SHA-1的循环步骤比MD5多80:64且要处理的缓存大160比特:128比特,SHA-1的运行速度比MD5慢。
shutil & zipfile & tarfile
高级的 文件、文件夹、压缩包 处理模块
shutil.copyfileobj(fsrc, fdst[, length])
将文件内容拷贝到另一个文件中
import shutilshutil.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)
拷贝文件和权限
import shutilshutil.copy('f1.log', 'f2.log')
shutil.copy2(src, dst)
拷贝文件和状态信息
import shutilshutil.copy2('f1.log', 'f2.log')
shutil.ignore_patterns(*patterns)
shutil.copytree(src, dst, symlinks=False, ignore=None)
递归的去拷贝文件夹
import shutilshutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) #目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除
shutil.rmtree(path[, ignore_errors[, onerror]])
递归的去删除文件
import shutilshutil.rmtree('folder1')
shutil.move(src, dst)
递归的去移动文件,它类似mv命令,其实就是重命名。
import shutilshutil.move('folder1', 'folder3')
shutil.make_archive(base_name, format,…)
创建压缩包并返回文件路径,例如: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 两个模块来进行的,详细:
zipfile压缩&解压缩
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()
tarfile压缩&解压缩
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()
自定义压缩
import zipfile
import os
zip_file = zipfile.ZipFile("F:\\Zip.zip","w")
file_list = []
for root_dir,dirs,files in os.walk("F:\\Animation"):
print(root_dir,dirs,files)
for file_name in files:
file_list.append(os.path.join(root_dir, file_name))
for file in file_list:
zip_file.write(file)
zip_file.close()
re(正则表达式)
引子
请从以下文件里取出所有的手机号
姓名 地区 身高 体重 电话
况咏蜜 北京 171 48 13651054608
王心颜 上海 169 46 13813234424
马纤羽 深圳 173 50 13744234523
乔亦菲 广州 172 52 15823423525
罗梦竹 北京 175 49 18623423421
刘诺涵 北京 170 48 18623423765
岳妮妮 深圳 177 54 18835324553
贺婉萱 深圳 174 52 18933434452
叶梓萱 上海 171 49 18042432324
杜姗姗 北京 167 49 13324523342
你能想到的办法是什么?
必然是下面这种吧?
f = open("兼职白领学生空姐模特护士联系方式.txt",'r',encoding="gbk")
phones = []
for line in f:
name,city,height,weight,phone = line.split()
if phone.startswith('1') and len(phone) == 11:
phones.append(phone)
print(phones)
有没有更简单的方式?
手机号是有规则的,都是数字且是11位,再严格点,就都是1开头,如果能把这样的规则写成代码,直接拿规则代码匹配文件内容不就行了?
file = open("F:\\Animation\\2.txt", "r")
file_info = file.read()
find_info = re.findall("[0-9]{11}", file_info)
print("类型:{0},数据:{1}".format(type(find_info), find_info))
输出
类型:<class 'list'>,数据:['13744234523', '15823423525', '18623423421', '18623423765', '18835324553', '18933434452', '18042432324']
re
正则表达式就是字符串的匹配规则,在多数编程语言里都有相应的支持,python里对应的模块是re
常用的表达式规则
'.' 默认匹配除\n之外的任意一个字符,若指定flag DOTALL,则匹配任意字符,包括换行
'^' 匹配字符开头,若指定flags MULTILINE,这种也可以匹配上(r"^a","\nabc\neee",flags=re.MULTILINE)
'$' 匹配字符结尾, 若指定flags MULTILINE ,re.search('foo.$','foo1\nfoo2\n',re.MULTILINE).group() 会匹配到foo1
'*' 匹配*号前的字符0次或多次, re.search('a*','aaaabac') 结果'aaaa'
'+' 匹配前一个字符1次或多次,re.findall("ab+","ab+cd+abb+bba") 结果['ab', 'abb']
'?' 匹配前一个字符1次或0次 ,re.search('b?','alex').group() 匹配b 0次
'{m}' 匹配前一个字符m次 ,re.search('b{3}','alexbbbs').group() 匹配到'bbb'
'{n,m}' 匹配前一个字符n到m次,re.findall("ab{1,3}","abb abc abbcbbb") 结果'abb', 'ab', 'abb']
'|' 匹配|左或|右的字符,re.search("abc|ABC","ABCBabcCD").group() 结果'ABC'
'(...)' 分组匹配, re.search("(abc){2}a(123|45)", "abcabca456c").group() 结果为'abcabca45'
'\A' 只从字符开头匹配,re.search("\Aabc","alexabc") 是匹配不到的,相当于re.match('abc',"alexabc") 或^
'\Z' 匹配字符结尾,同$
'\d' 匹配数字0-9
'\D' 匹配非数字
'\w' 匹配[A-Za-z0-9]
'\W' 匹配非[A-Za-z0-9]
's' 匹配空白字符、\t、\n、\r , re.search("\s+","ab\tc1\n3").group() 结果 '\t'
'(?P...)' 分组匹配 re.search("(?P[0-9]{4})(?P[0-9]{2})(?P[0-9]{4}
re的匹配语法有以下几种
-
re.compile 指定一个匹配规则
-
re.match 从头开始匹配
-
re.search 匹配包含
-
re.findall 把所有匹配到的字符放到以列表中的元素返回
-
re.split 以匹配到的字符当做列表分隔符
-
re.sub 匹配字符并替换
-
re.fullmatch 全部匹配
Flags标志符
- re.I(re.IGNORECASE): 忽略大小写(括号内是完整写法,下同)
- re.M(MULTILINE): 多行模式,改变’^’和’$’的行为
- re.S(DOTALL): 改变’.’的行为,make the ‘.’ special character match any character at all, including a newline; without this flag, ‘.’ will match anything except a newline.
- re.X(re.VERBOSE) 可以给你的表达式写注释,使其更可读,下面这2个意思一样
a = re.compile(r"""\d + # the integral part
\. # the decimal point
\d * # some fractional digits""",
re.X)
b = re.compile(r"\d+\.\d*")
group
可以拿到括号中匹配的值
print(re.match("(AB|CD|EF|GH|I|12|54)(AB|CD|EF|GH|I|12|54)", "ABCDEFGHI123456").groups())
print(re.match("(AB|CD|EF|GH|I|12|54)(AB|CD|EF|GH|I|12|54)", "ABCDEFGHI123456").group())
输出
('AB', 'CD')
ABCD
re.compile(pattern, flags=0)
Compile a regular expression pattern into a regular expression object, which can be used for matching using its match(), search() and other methods, described below.
例子
compile_email = re.compile('\w+@\w+\.(com|cn|edu)')
print(compile_email.match("alex@oldboyedu.cn"))
输出
<re.Match object; span=(0, 17), match='alex@oldboyedu.cn'>
等同于
print(re.match('\w+@\w+\.(com|cn|edu)', "alex@oldboyedu.cn"))
re.match(pattern, string, flags=0)
从起始位置开始根据模型去字符串中匹配指定内容,匹配单个
- pattern 正则表达式
- string 要匹配的字符串
- flags 标志位,用于控制正则表达式的匹配方式
print(re.match("^jcl[1-3]{2}(AB|1|2){2}.*$", "jcl22AB1dsa")) #如果能匹配到就返回一个可调用的对象,否则返回None
输出
<re.Match object; span=(0, 11), match='jcl22AB1dsa'>
re.search(pattern, string, flags=0)
根据模型去字符串中匹配指定内容,匹配单个
print(re.search("jcl[1-3]{2}(AB|1|2){2}", "dsadajcl22AB1dsadjasoibjcl22AB1dsa"))
输出
<re.Match object; span=(5, 13), match='jcl22AB1'>
re.findall(pattern, string, flags=0)
match and search均用于匹配单值,即:只能匹配字符串中的一个,如果想要匹配到字符串中所有符合条件的元素,则需要使用 findall。
file = open("F:\\Animation\\2.txt", "r")
file_info = file.read()
find_info = re.findall("[0-9]{11}", file_info)
print("类型:{0},数据:{1}".format(type(find_info), find_info))
输出
类型:<class 'list'>,数据:['13744234523', '15823423525', '18623423421', '18623423765', '18835324553', '18933434452', '18042432324']
re.sub(pattern, repl, string, count=0, flags=0)
用于替换匹配的字符串,比str.replace功能更加强大
print(re.sub("(ldh|zxy)+", "*", "dsaldhdjsiaolzxydasldhdusazxy"))
print(re.sub("(ldh|zxy)+", "*", "dsaldhdjsiaolzxydasldhdusazxy", count=2))
输出
dsa*djsiaol*das*dusa*
dsa*djsiaol*dasldhdusazxy
re.split(pattern, string, maxsplit=0, flags=0)
用匹配到的值做为分割点,把值分割成列表
print(re.split("(ldh|zxy)+", "dsaldhdjsiaolzxydasldhdusazxy"))
输出
['dsa', 'ldh', 'djsiaol', 'zxy', 'das', 'ldh', 'dusa', 'zxy', '']
re.fullmatch(pattern, string, flags=0)
整个字符串匹配成功就返回re object, 否则返回None
print(re.fullmatch('\w+@\w+\.(com|cn|edu)', "alex@oldboyedu.cn"))
输出
<re.Match object; span=(0, 17), match='alex@oldboyedu.cn'>