自定义一个简单的模块
例如,我们有一个文件utils.py
,用来给字符串加密:
import hashlib
def encrypt(data):
""" 数据加密 """
hash_object = hashlib.md5()
hash_object.update(data.encode('utf-8'))
return hash_object.hexdigest()
我们在另一个文件run.py
中可以引入这个utils.py
文件中的方法。
from utils import encrypt
user = input("请输入用户名:")
pwd = input("请输入密码:")
md5_password = encrypt(pwd)
message = "用户名:{},密码:{}".format(user, md5_password)
print(message)
目录结构:
└─ utils.py
└── run.py
包和模块
在开发简单的程序时,使用一个py文件就可以搞定,如果程序比较庞大,需要些10w行代码,此时为了,代码结构清晰,将功能按照某种规则拆分到不同的py文件中,使用时再去导入即可。另外,当其他项目也需要此项目的某些模块时,也可以直接把模块拿过去使用,增加重用性。
├── commons
│ ├── convert.py
│ ├── page.py
│ └── utils.py
└── run.py
如果run.py
想使用文件夹下面的模块的时候,可以进行下面的方式:
from common.utils import encrypt
from common.page import pagination
from common.convert import int_to_string
v1 = encrypt('tom')
v2 = pagination()
v3 = int_to_string()
向这样把一些功能放到一个文件夹中进行调用,这个文件夹就成为包
。包中的py
文件就是模块
包
中一般都需要一个__init__.py
文件,在这个__init__.py
文件中,往往会写一些关于这个包
的注释。表名这个包的作用是什么。
当导入这个包
中的模块时,这个__init__.py
文件会自动加载并执行。
例如在__init__.py
文件中,添加内容如下:
"""
包的作用
"""
VERSION = 0.1
print(VERSION)
目录结构如下:
├── commons
│ ├── __init__.py
│ ├── convert.py
│ ├── page.py
│ └── utils.py
└── run.py
在运行run.py
时,会自动运行__init__.py
输出如下:
0.1
那么我们加载了三个模块,为什么只运行了一次__init__呢?
原因是,对于python中所有的模块和包,如果加载过一次之后,下一次再次使用的时候,就不会再加载了,因为第一次加载之后就已经放入到内存中了。
导入包
文件结构:
├── commons
│ ├── __init__.py
│ ├── convert.py
│ ├── page.py
│ └── utils.py
└── run.py
其中utils.py
中的文件内容如下:
import hashlib
def encrypt(data):
""" 数据加密 """
hash_object = hashlib.md5()
hash_object.update(data.encode('utf-8'))
return hash_object.hexdigest()
def f1(data):
return 1
run.py
中的内容如下:
from common.utils import encrypt
from common.utils import f1
当我们执行from common.utils import encrypt
的时候,会将utils
文件中所有的内容加载到内存,然后执行from common.utils import f1
时,就不会再次导入utils
文件的内容了。
导入电脑中另一个位置的包
当前的项目所在的位置是C盘
,如果我要导入一个D盘
的模块,应该如何做?
例如我的run.py
文件内容如下:
# D:/xx/x1.py
from xx.x1 import x0
这种情况下,即使D盘
下存在/xx/x1.py
文件,也无法导入成功。提示xx
模块找不到。
因为Python在找模块时,只会去找指定的路径中找相应的模块,而D盘
不在指定的路径下,所以提示找不到模块。
查看Python内部默认的模块寻找路径,都模块或包时,都会按照指定顺序逐一去特定的路径下查找:
import sys
print(sys.path)
输出结果为:
['c:\\Users\\vincent\\Desktop\\code\\neimeng-python\\test', #'C:\\Users\\vincent\\anaconda3\\envs\\neimeng\\python310.zip', 'C:\\Users\\vincent\\anaconda3\\envs\\neimeng\\DLLs', 'C:\\Users\\vincent\\anaconda3\\envs\\neimeng\\lib', 'C:\\Users\\vincent\\anaconda3\\envs\\neimeng', 'C:\\Users\\vincent\\anaconda3\\envs\\neimeng\\lib\\site-packages']
而我们的自定义的模块所在的路径不在上面的路径下,所以提示导入不成功。
如果我们将自定义的模块路径添加到sys.path
中,就可以调用我们的模块了。
import sys
sys.path.append(r'D:\xx')
from xx.x1 import x0
print(sys.path)
导入模块名冲突
我们知道Python内部有一个模块random
,如果我们在项目中自己定义了一个模块也叫random
,那么会出现什么情况。
项目结构:
├── random.py
└── run.py
其中random.py的内容是个空文件。run.py内容如下:
import random
v = random.randint(1, 10)
print(v)
那么执行run.py
时,就会提示报错:AttributeError: module 'random' has no attribute 'randint'
说明他从我们自己定义的random.py
中去加载了。
原因是,我们的模块加载顺序是根据sys.path
列表中的顺序来确定的,而列表的路径顺序如下:
['c:\\Users\\vincent\\Desktop\\code\\neimeng-python\\test', #'C:\\Users\\vincent\\anaconda3\\envs\\neimeng\\python310.zip', 'C:\\Users\\vincent\\anaconda3\\envs\\neimeng\\DLLs', 'C:\\Users\\vincent\\anaconda3\\envs\\neimeng\\lib', 'C:\\Users\\vincent\\anaconda3\\envs\\neimeng', 'C:\\Users\\vincent\\anaconda3\\envs\\neimeng\\lib\\site-packages']
可以看到,我们当前的项目所在文件夹在列表中的开头,所以会加载我们定义的模块。
- 所以我们以后在写模块名称时,千万不能与内置和第三方的模块同名。
- 项目的执行文件一般都在项目的根目录,结构如下:
├── commons
│ ├── __init__.py
│ ├── convert.py
│ ├── page.py
│ └── utils.py
└── run.py
也就是说,run.py
要放到项目的根目录。
假设run.py
放到与commons
同一级的文件夹中:
├── commons
│ ├── __init__.py
│ ├── convert.py
│ ├── page.py
│ └── utils.py
├── bin
│ ├── run.py
这样运行run.py
时,当前的run.py
所在的目录加入到sys.path
中,而我们所导入的模块就不在sys.path
中,所以就无法导入commons
中的模块了。
如果还想使用commons
下的模块时,需要将这个路径加入到sys.path
列表中。 修改run.py
,在代码的最上面加入:
import sys
sys.path.append('commons的绝对路径')
但是这种方式,不利于移植,如果部署到别的地方,那么路径一旦发生变化,将不能运行了。
解决办法就是,使用相对路径。os.path.abspath(__file__)
可以获取当前文件的绝对路径,因此如果想获取common
的路径,可以使用sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
需要注意的是,如果使用pycharm,那么pycharm会自动将当前项目目录加入到sys.path列表中。
模块导入的方式
上面我们介绍到,我们项目的执行入口一般都会放入到项目的根目录下。
导入模块分为下面两种方式:
import xxx
from xx import xxx
import方式导入
这种方式可以导入一个模块,并将这个模块中的所有内容加载到内存。
├── commons
│ ├── __init__.py
│ ├── convert.py
│ ├── page.py
│ └── utils.py
├── test_module.py
└── run.py
当导入与run.py
同一级的test_module.py
模块时:
import test_module
data = test_module.func1()
当导入与run.py
不同一级的utils.py
模块时:
import common.utils
data = common.utils.encrypt()
为了方便,可以给import
的模块起一个别名:import common.utils as f1
import
除了导入一个python
模块(Python文件)之外,还可以导入一个包(文件夹)。这仅仅能导入包中的__init__.py
中的内容,而不能把包中的所有模块都导入进来。
from xx import xxx 方式导入
使用这种方式,比import
导入的方式不管是从粒度上,还是从级别上都更细。
from test_module import func1
:表示导入的是test_module
模块的func1
函数。
from test_module import *
:表示导入的是test_module
中所有的函数。
from common.utils import encrypt
:表示导入common.utils
模块下的encrypt
函数。
也可以直接导入一个模块:
from common import utils
:表示导入的是common
下的utils
所有函数。通过utils.encrypy()
来执行模块的函数。
通过from
方式导入一个包,假如common
下面还有一个文件夹models
,其中又包含其他一些Python文件,那么执行from common import models
会自动执行models
里面的__init__.py
文件。
from
也支持相对导入。例如convert.py
模块中也需要导入utils.py
模块中的函数,就可以使用相对导入:在convert.py
中导入from . import utils
。 (一般不推荐,而且只能用在包里面,不能用在项目根目录下导入同级的模块。也就是说run.py
不能导入test_module
。)
两种导入方式的应用场景
import
方式:适合导入项目根目录下的模块。
from
方式:适合导入某个函数,或者嵌套的包和模块。
需要注意的是,使用from方式导入一个模块中的函数,并不能节省内存,因为from导入一个模块,就会把当前模块全部加载到内存中
导入模块时一般遵循的规范
- 一般将模块的注释信息,写到文件顶部。
- 一般先导入内置模块,再导入第三方模块,最后导入自定义模块。他们之间用空行进行分割。
例如:
import os
import sys
import requests
import elasticsearch
from common.utils import encrypt