自定义一个简单的模块
例如,我们有一个文件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 xxxfrom 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
本文围绕Python模块与包的导入展开。介绍了自定义简单模块的方法,阐述了包和模块在大型程序中的作用及使用方式。还讲解了导入包、不同位置包、处理模块名冲突的方法,对比了两种模块导入方式及应用场景,最后给出导入模块的规范。
431

被折叠的 条评论
为什么被折叠?



