为什么会有模块

Python不仅有自带的模块(称之为标准库),还有海量的第三方模块,并且很多开发者还在不断贡献自己开发的新模块,正是有了这么强大的“模块自信”,Python才被很多人钟爱。并且这种方式也正在不断被其他更多语言所借鉴,几乎成为普世行为了

总结以下两点:

1.使用内置的或者第三方模块的好处是:拿来主义,极大提升开发效率
2.使用自定义模块的好处是:抽取程序中多个地方都需要用到的公共的功能定义成模块减少代码冗余(程序是多文件的情况下,将多个文件都需要用到的功能放到一个地方,供大家统一查找)

模块的定义

“模块是程序”一语道破了模块的本质,它就是一个扩展名为.py的Python程序
我们能够在应该使用它的时候将它引用过来,节省精力,不需要重写雷同的代码

模块的三种来源

1.内置的模块
2.第三方的模块
3.自定义模块

模块的四种表现格式

1.使用python编写的py文件
2.已被编译为共享库或DLL的C或C++扩展
3.把一系列模块组织到一起的文件夹(文件夹下有一个init.py文件,该文件夹称之为包)
4.使用C编写并连接到python解释器的内置模块

初识模块

  • sys 模块
1
2
3
import math

math.pow(3,2)

这里的math(是Python标准库之一)就是一个模块,用import引入这个模块,然后可以使用模块里面的函数,比如pow()函数。显然,这里是不需要自己动手写具体函数的,我们的任务就是拿过来使用。这就是模块的好处:拿过来就用,不用自己重写

导入模块的几种方法

1
2
3
4
import module
from module.xx.xx import xx
from module.xx.xx import xx as rename
from module.xx.xx import *
1. import 的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
"""
新建两个py文件一个执行一个被导入
"""
# md.py
print('from the md.py')
money = 1000

def read1():
print('md',money)

def read2():
print('md模块')
read1()

def change():
global money
money = 0

# run.py
money = 66
import md # 文件名是md.py而模块名则就是md

# 右键运行,执行了md文件,多次导入并不会多次运行

"""
1.首次导入模块发生的3件事情
1.会产生一个模块的名称空间
2.执行文件md.py,将执行过程中产生的名字都放到模块的名称空间中
‘money’:1000的内存地址
‘read1’:read1函数的内存地址
‘read2’:read2函数的内存地址
‘change’:change函数的内存地址
此处图示理解效果更佳

3.在当前执行文件的名称空间中拿到一个模块名,该名字指向模块的名称空间
即: 既然运行了md文件,那么肯定会创建一个名称空间,将md文件中所有的名字与值的绑定关系存起来

run文件也运行了,那么肯定也会创建一个名称空间,将money与md存放进去
‘money’:66的内存地址
‘md’:md.py的名称空间地址
2.之后的导入都是直接引用第一次导入的成果,不会重新执行文件
"""

# 1.执行文件中访问模块名称空间中的名字的语法: 模块名.名字
print(md.money) # 指名道姓的跟md文件要名字money,肯定不会跟当前执行文件中的名字冲突
money # 向当前执行文件的名称空间要名字money

print(md.read1)
print(md.read2)
print(md.change) # 打印获取到的是函数的内存地址 >>> 加括号执行的冲动

# 一次调用read1,read2,change反复明确查找名字的规律
md.read1() # 找的是md文件中的money
md.read2() # 找的是md文件中的money
md.change() # 修改的也是md文件中的money


# 一行导入多个模块(不推荐)
import os,sys,time
# 给导入的模块名起别名
import xxxyyy as xy

import导入模块总结:
1.在使用的时候必须加上前缀: 模块名.
优点:指名道姓的向某一个名称空间要名字,肯定不会与当前名称空间中的名字冲突
缺点:但凡要用模块中的名字都需要加上模块前缀

2.from…import…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 仍然以上述两个文件为例
from md import money
from md import money

"""
from...import...也发生了三件事,前两件跟import导入一样,仅仅是最后一件有点区别
1.会产生一个模块的名称空间
2.执行文件md.py,将执行过程中产生的名字都放到模块的名称空间中
3.在当前执行文件中直接拿到一个名字,该名字就是模块中相对应的名字
‘money’:md文件中money
"""

# 1.
money = 888
from md import money
# 2.
from md import money
money = 888

# 总结

"""
优点:使用时无需再加前缀,更简洁
缺点:可能会与当前名称空间中的名字冲突
"""

from md import read1
money = 999
read1() # 函数调用在定义阶段就已经固定死了,与调用位置没有关系,这里修改的也仅仅是执行文件名称空间中的名字!!! 图示理解

# 了解
from md import * # *代表从被导入模块中拿到所有的名字(不推荐使用),因为很有可能与当前名称空间中的名字器冲突
3. 判断py文件被当作模块导入还是执行文件
1
if __name__ == '__main__':  # 当文件被直接执行时

模块的导入查找顺序

1
2
3
4
5
6
"""
模块的查询顺序
1.先从内存中找可能已经加载了的
2.内置模块
3.sys.path列表里面每一个路径下去找, 需要知道sys.path列表中第一个路径就是当前被执行文件所在的文件夹
"""
1.从内存加载

验证第一点,找模块先从内存中找看看要找的模块是不是已经加载了

1
2
3
4
5
6
7
定义一个模块,在另一个文件中导入,然后执行文件中主动睡眠10s,期间快速讲模块文件删除,发现执行文件睡眠之后的代码仍然可以访问
但是一旦程序结束运行,再次执行就会报错了
import time
import m1
time.sleep(10)
import m1
m1.f1()
2.内置模块加载

需要注意的是你新建的py文件名最好不要与已经存在的模块名冲突 time模块为例

3.sys.path列表里面每一个路径下去找
1
2
3
4
5
6
7
8
9
10
11
# 要导的模块在一个文件夹内,执行文件与文件夹同级跟模块文件不同级
import md. # 保存,因为内存,内置都没有,sys.path也是以当前执行文件所在的文件夹找文件

## 第一种解决办法:
# 要想解决,需要利用sys.path.append()将模块所在的文件夹添加进去

## 第二种解决办法:
# 不想用上面的方式解决,可以用以下方式, 推荐使用这种方法
from ... import ...

from dir1.dir2 import md

包和库以及模块的概念和关系

顾名思义,包是比“模块”大的。一般来讲,一个“包”里面会有多个模块,每个包都有若干个模块。一个包由多个模块组成,即有多个.py的文件,那这个所谓的“>包”其实就是我们熟悉的一个目录罢了。

如何将一个目录变成一个包?

解决方法就是在该目录中放一个__init__.py文件。

内置模块

1. sys

用于对python解释器系统级别的模块

1
2
3
4
5
6
7
8
sys.argv           命令行参数List,第一个元素是程序本身路径
sys.exit(n) 退出程序,正常退出时exit(0)
sys.version 获取Python解释程序的版本信息
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
sys.stdin 输入相关
sys.stdout 输出相关
sys.stderror 错误相关

进度百分比

1
2
3
4
5
6
7
import sys,time

# 进度条的演示案例
for i in range(20):
sys.stdout.write("#")
sys.stdout.flush()
time.sleep(0.2)
2. os
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
os.getcwd()                 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd
os.curdir 返回当前目录: ('.')
os.pardir 获取当前目录的父目录字符串名:('..')
os.makedirs('dir1/dir2') 可生成多层递归目录
os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirname
os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.remove() 删除一个文件
os.rename("oldname","new") 重命名文件/目录
os.stat('path/filename') 获取文件/目录信息
os.sep 操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
os.linesep 当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
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.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
3. random
1
2
3
import random
print random.random()
print random.randint(1,2)

生成随机验证码

1
2
3
4
5
6
7
8
9
10
import random

code = ''
for i in range(5):
str1 = str(random.randint(1,9))
str2 = chr(random.randint(65,90))
str3 = chr(random.randint(97,122))
tmp = random.choice([str1,str2,str3])
code += tmp
print(code)
4. 序列化

现有如下的需求,需要将下面的字典存入到磁盘中,怎么办?

1
2
3
4
info = {
"name":'zhangsan',
"age":25
}

将这个字典存入到文件中不就完了

于是,我们打开一个文件,将上面的字典存入到一个文件中

于是,有了如下的代码,但是我们发现一个问题:

1
2
3
f = open('data.json',"w")
f.write(str(info))
f.close()

存入到文件中的 是一个字符串,我们采用下面的方法取出数据:

1
2
3
4
f = open("data.json","r")
data = eval( f.read() )
print(data)
f.close()

这个时候我们再去取name和age的时候,很方便取出来了

但是有一个问题,如果我们这个时候用python写了一个接口给php使用,接口里面的内容就是使用上述方法将字典保存到文件中,那此时php想获取值得时候,发现没有eval这个函数,那php怎么去取?

因此,上面的思路已经不能满足我们的需求了,那该怎么办?

我们采用json模块来进行存储

因此,我们有如下的代码:

1
2
3
4
5
# 此处调用的是json的dumps方法
data = json.dumps(info)

with open("data2.json","w") as f:
f.write(data)

这个时候,我们存入的就是一个标准的json格式

取出的时候,我们用如下的代码:

1
2
3
4
5
with open("data2.json","r") as f:
# 采用loads方法
data = json.loads(f.read())
print(data)
print(data['name'])

除了上述的dumps和loads方法之外,还有两个方法:

dump 和 load

dump的用法:

1
2
3
4
f = open("data3.json","w")

data = json.dump(info,f)
print(data)

load的用法:

1
2
3
f = open('data3.json',"r")
data = json.load(f)
print(data['age'])

上述的过程就叫做

序列化—–即把一个内存对象转换成字符串的过程
反序列化—将一个字符串转换成对应内存对象的过程

下面是补充的知识点,了解即可

在python中,除了json可以进行序列化和返序列化之外,还有一个特定的模块pickle,也可以进行序列化和反序列化

我们需要注意的是,json和pickle最终的结果都是一样的,那还要pickle干啥?

pickle的存在是用来序列化和反序列化一些特有的类型,比如函数

pickle和json的区别?

1.pickle能够序列化python中的所有数据类型,而json只能够序列化基本的数据类型
2.pickle是python独有的,只能是用于python中,不能用于和其他语言交换数据

Python中用于序列化的两个模块

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

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


5. logging模块

主要用来记录程序的各种错误和运行信息,方便后续调试

  • 记录单文件日志
1
2
3
4
5
6
7
8
9
10
11
12
13
import logging

logging.basicConfig(filename='test.log',
format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S %p',
level=10)

logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
logging.log(10,'log')

日志格式化

日志格式.png

6. time

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

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

常见的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import time

print(time.altzone) #返回与utc时间的时间差,以秒计算
print(time.asctime()) #返回时间格式"Fri Aug 19 11:14:16 2016",
print(time.gmtime()) #返回utc时间的struc时间对象格式

print(time.ctime()) #返回Fri Aug 19 12:38:29 2016 格式


# 日期字符串 转成 时间戳
string_to_struct = time.strptime("2016/05/22","%Y/%m/%d") #将 日期字符串 转成 struct时间对象格式
print(string_to_struct)
#
struct_to_stamp = time.mktime(string_to_struct) #将struct时间对象转成时间戳
print(struct_to_stamp)


#时间戳 转为 日期字符串
print(time.strftime("%Y-%m-%d %H:%M:%S",time.gmtime()) ) #将utc struct_time格式转成指定的字符串格式


#时间加减
import datetime

print(datetime.datetime.now()) #返回 2016-08-19 12:47:03.941925
print(datetime.date.fromtimestamp(time.time()) ) # 时间戳直接转成日期格式 2016-08-19
print(datetime.datetime.now() )
print(datetime.datetime.now() + datetime.timedelta(3)) #当前时间+3天
print(datetime.datetime.now() + datetime.timedelta(-3)) #当前时间-3天
print(datetime.datetime.now() + datetime.timedelta(hours=3)) #当前时间+3小时
print(datetime.datetime.now() + datetime.timedelta(minutes=30)) #当前时间+30分

J4uxSK.png

占位符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
%Y  Year with century as a decimal number.
%m Month as a decimal number [01,12].
%d Day of the month as a decimal number [01,31].
%H Hour (24-hour clock) as a decimal number [00,23].
%M Minute as a decimal number [00,59].
%S Second as a decimal number [00,61].
%z Time zone offset from UTC.
%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.
%I Hour (12-hour clock) as a decimal number [01,12].
%p Locale's equivalent of either AM or PM.
7. 加密hashlib

用于加密相关的操作,代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import hashlib

# 简单实用
import hashlib # 提供摘要算法的模块
md5 = hashlib.md5() # md5只是其中一种使用频率较高的算法之一
md5.update('jason123'.encode('utf-8')) # 必须传bytes类型的数据
print(md5.hexdigest()) # 获取加密之后的结果
"""
特点:
1.在使用同一个算法的前提下,只要传入的字符串内容相同,那么每次得到的结果必然相同。且只要内容相同的情况下,无论分多少次传入结果都是一样的
2.不同的摘要算法使用方式是一模一样的
3.算法的复杂度,密文的长度越大。那么所消耗的时间,和空间资源也就越高。
"""

# 摘要算法的主要应用场景
1.密码的密文存储
2.文件的一致性验证
1.在下载的时候,检查下载的文件与远程服务器上的文件是否一致
2.文件名相同的情况下,检查文件内容是否一致

# 利用hashlib完成用户的注册登陆


# ######## sha256 ########

hash = hashlib.sha256()
hash.update(bytes('admin', encoding='utf-8'))
print(hash.hexdigest())


# ######## sha512 ########

hash = hashlib.sha512()
hash.update(bytes('admin', encoding='utf-8'))
print(hash.hexdigest())

以上加密算法虽然依然非常厉害,但时候存在缺陷,即:通过撞库可以反解。所以,有必要对加密算法中添加自定义key再来做加密。

1
2
3
4
5
6
7
import hashlib

# ######## md5 ########

hash = hashlib.md5(bytes('898oaFs09f',encoding="utf-8"))
hash.update(bytes('admin',encoding="utf-8"))
print(hash.hexdigest())

python内置还有一个 hmac 模块,它内部对我们创建 key 和 内容 进行进一步的处理然后再加密

1
2
3
4
5
import hmac

h = hmac.new(bytes('898oaFs09f',encoding="utf-8"))
h.update(bytes('admin',encoding="utf-8"))
print(h.hexdigest())
8. requests

Requests 是使用 Apache2 Licensed 许可证的 基于Python开发的HTTP 库,其在Python内置模块的基础上进行了高度的封装,从而使得进行网络请求时,变得美好了许多,因为使用Requests可以轻而易举的完成浏览器可有的任何操作

1)安装模块

pip3 install requests

2) 使用模块

  • get请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1、无参数实例

import requests
ret = requests.get('https://github.com/timeline.json')

print(ret.url)
print(ret.text)


# 2、有参数实例
import requests

payload = {'key1': 'value1', 'key2': 'value2'}
ret = requests.get("http://httpbin.org/get", params=payload)

print(ret.url)
print(ret.text)
  • post请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1、基本POST实例

import requests

payload = {'key1': 'value1', 'key2': 'value2'}
ret = requests.post("http://httpbin.org/post", data=payload)

print(ret.text)


# 2、发送请求头和数据实例

import requests
import json

payload = {'some': 'data'}
headers = {'content-type': 'application/json'}

ret = requests.post('"http://httpbin.org/post"', data=json.dumps(payload), headers=headers)

print(ret.text)
print(ret.json())
  • 其他请求
1
2
3
4
5
6
7
8
requests.get(url, params=None, **kwargs)
requests.post(url, data=None, json=None, **kwargs)
requests.put(url, data=None, **kwargs)
requests.delete(url, **kwargs)
requests.patch(url, data=None, **kwargs)

# 以上方法均是在此方法的基础上构建
requests.request(method, url, **kwargs)
9. subprocess

这个模块主要是用来替换os.system()以及os.spawn()

subprocess包主要功能是执行外部的命令和程序。比如说,我需要使用wget下载文件。我在Python中调用wget程序。从这个意义上来说,subprocess的功能与shell类似

  • 常见的subprocess的方法
1
2
3
4
5
import subprocess

>>> p = subprocess.getoutput("df -h|grep disk")
>>> p
b'/dev/disk1 465Gi 64Gi 400Gi 14% 16901472 104938142 14% /\n'
10. 正则表达式

常见的正则表达式符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'.'     默认匹配除\n之外的任意一个字符,若指定flag DOTALL,则匹配任意字符,包括换行
'^' 匹配字符开头,若指定flags MULTILINE,这种也可以匹配上(r"^a","\nabc\neee",flags=re.MULTILINE)
'$' 匹配字符结尾,或e.search("foo$","bfoo\nsdfsf",flags=re.MULTILINE).group()也可以
'*' 匹配*号前的字符0次或多次,re.findall("ab*","cabb3abcbbac") 结果为['abb', 'ab', 'a']
'+' 匹配前一个字符1次或多次,re.findall("ab+","ab+cd+abb+bba") 结果['ab', 'abb']
'?' 匹配前一个字符1次或0
'{m}' 匹配前一个字符m次
'{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|456)c", "abcabca456c").group() 结果 abcabca456c


'\A' 只从字符开头匹配,re.search("\Aabc","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'

常见的使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import re

# 第一个参数是正则表达式,第二个参数是待匹配的文本内容

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

ret = re.search('a', 'eva szk yuan')
print(ret.group()) # 结果:'a'

# 函数会在字符串内查找模式匹配,直到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None,并且需要注意的是如果ret是None,再调用.group()会直接报错。这一易错点可以通过if判断来进行筛选
if ret:
print(ret.group())

ret = re.match('a', 'abc').group() # 同search,不过仅在字符串开始处进行匹配
print(ret) # ‘a'
# match是从头开始匹配,如果正则规则从头开始可以匹配上,就返回一个对象,需要用group才能显示,如果没匹配上就返回None,调用group()就会报错

常见的正则表达式

IP:
^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$
手机号:
^1[3|4|5|8][0-9]\d{8}$
邮箱:
[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+