1、模块的介绍与使用模块import
1.1、模块的介绍
1.1.1、什么是模块?
模块就是一组功能的集合体,我们的程序可以导入模块来复用模块里的功能。
在python中,模块的使用方式都是一样的,但其实细说的话,模块可以分为四个通用类别:
(1)使用python编写的.py文件
(2)已被编译为共享库或DLL的C或C++扩展
(3)把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包)
(4)使用C编写并链接到python解释器的内置模块
模块有三种来源:
(1)自带的模块 内置模块 && 标准库
(2)第三方模块 pip3 install requests
(3)自定义的模块
常见的场景:一个模块就是一个包含了一组功能的python文件,比如spam.py,模块名为spam,可以通过import spam使用。
1.1.2、为何要使用模块
1、从文件级别组织程序,更方便管理
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用
2、拿来主义,提升开发效率
同样的原理,我们也可以下载别人写好的模块然后导入到自己的项目中使用,这种拿来主义,可以极大地提升我们的开发效率
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
1.1.3、实例
以spam.py为例来介绍模块的使用:文件名spam.py,模块名spam
#spam.py
print('from the spam.py')
money=1000
def read1():
print('spam模块:',money)
def read2():
print('spam模块')
read1()
def change():
global money
money=0
2.1、使用模块之import
2.1.1、import的使用
模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载到内存中的模块对象增加了一次引用,不会重新执行模块内的语句),如下
test.py
import spam #只在第一次导入时才执行spam.py内代码,此处的显式效果是只打印一次'from the spam.py',当然其他的顶级代码也都被执行了,只不过没有显示效果.
import spam
import spam
import spam
#result:
from the spam.py
2.1.2、在第一次导入模块时会做三件事,重复导入会直接引用内存中已经加载好的结果
执行文件run.py\自定义模块spam.py
(1)执行run.py文件,在被导入的spam.py模块所在文件中执行;
(2)产生spam.py的名称空间(run.py的名称空间),将spam.py运行过程中产生的名字都丢到新产生的模块spam的名称空间中;
(3)在当前文件中产生一个名字spam,该名字指向spam.py的名称空间
#总的来说:
(1)会触发模块文件的运行,产生一个模块的名称空间,将运行模块文件过程中产生的名字都丢到模块的名称空间
(2)在当前名称空间中产生一个名字spam,该名字指向模块的名称空间
2.1.3、被导入模块有独立的名称空间
每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突
测试一:money与spam.money不冲突
test.py
import spam
def read1():
print('========')
spam.read1()
执行结果:
from the spam.py
spam->read1->money 1000
测试二:read1与spam.read1不冲突
test.py
import spam
def read1():
print('========')
spam.read1()
执行结果:
from the spam.py
spam->read1->money 1000
测试三:执行spam.change()操作的全局变量money仍然是spam中的
test.py
import spam
money=1
spam.change()
print(money)
#print(spam.money) 输出的结果就是0
执行结果:
from the spam.py
1
2.1.4、为模块名起别名
为已经导入的模块起别名的方式对编写可扩展的代码很有用
import spam as sm
print(sm.money)
有两中sql模块mysql和oracle,根据用户的输入,选择不同的sql功能
(1)mysql.py
def sqlparse():
print('from mysql sqlparse')
(2)oracle.py
def sqlparse():
print('from oracle sqlparse')
(3)test.py
db_type=input('>>: ')
if db_type == 'mysql':
import mysql as db
elif db_type == 'oracle':
import oracle as db
db.sqlparse()
2.1.5、在一行导入多个模块
import sys,os,re
2、使用模块之from...import...
2.1、from...import...的使用
from spam import read1,read2
2.2、from...import 与import的对比**
唯一的区别就是:使用from...import...则是将spam中的名字直接导入到当前的名称空间中,所以在当前名称空间中,直接使用名字就可以了、无需加前缀:spam.
from...import...的方式有好处也有坏处
好处:使用起来方便了
坏处:容易与当前执行文件中的名字冲突
以spam.py为例来介绍模块的使用:文件名spam.py,模块名spam
#spam.py
print('from the spam.py')
money=1000
def read1():
print('spam模块:',money)
def read2():
print('spam模块')
read1()
def change():
global money
money=0
验证一:当前位置直接使用read1和read2就好了,执行时,仍然以spam.py文件全局名称空间
测试一:导入的函数read1,执行时仍然回到spam.py中寻找全局变量money
#test.py
from spam import read1
money=200
read1()
执行结果:
from the spam.py
spam->read1->money 1000
测试二:导入的函数read2,执行时需要调用read1(),仍然回到spam.py中找read1()
#test.py
from spam import read2
def read1():
print('==========')
read2()
执行结果:
from the spam.py
spam->read2 calling read
spam->read1->money 1000
验证二:如果当前有重名read1或者read2,那么会有覆盖效果。
测试三:导入的函数read1,被当前位置定义的read1覆盖掉了
#test.py
from spam import read1
def read1():
print('==========')
read1()
'''执行结果:from the spam.py==========
验证三:导入的方法在执行时,始终是以源文件为准的
from spam import money,read1
money=100 #将当前位置的名字money绑定到了100
print(money) #打印当前的名字
read1() #读取spam.py中的名字money,仍然为1000
'''from the spam.py100spam->read1->money 1000
2.3、支持as,一行导入多个名字
from spam import read1 as read
from spam import read1,read2,money
2.4、from...import *
from spam import * 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置
大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。
from spam import * #将模块spam中所有的名字都导入到当前名称空间
print(money)
print(read1)
print(read2)
print(change)
执行结果:
from the spam.py
1000
可以使用__all__来控制*(用来发布新版本),在spam.py中新增一行 __all__=['money','read1'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字
2.5、模块循环导入问题
模块循环/嵌套导入抛出异常的根本原因是由于在python中模块被导入一次之后,就不会重新导入,只会在第一次导入时执行模块内代码
#在我们的项目中应该尽量避免出现循环/嵌套导入,如果出现多个模块都需要共享的数据,可以将共享的数据集中存放到某一个地方.
在程序出现了循环/嵌套导入后的异常分析、解决方法如下:
示范文件内容如下:
m1.py
print('正在导入m1')
from m2 import y
x='m1'
m2.py
print('正在导入m2')
from m1 import x
y='m2'
run.py
import m1
测试一(已经被导入的模块,执行时不会重新导入)
执行run.py会抛出异常
正在导入m1
正在导入m2
Traceback (most recent call last):
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/aa.py", line 1, in
import m1
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in
from m2 import y
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in
from m1 import x
ImportError: cannot import name 'x'
测试一结果分析:
先执行run.py--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1"
--->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”--->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错
测试二(执行文件不等于导入文件)
测试二:执行文件不等于导入文件,比如执行m1.py不等于导入了m1
直接执行m1.py抛出异常
正在导入m1
正在导入m2
正在导入m1
Traceback (most recent call last):
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in
from m2 import y
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in
from m1 import x
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in
from m2 import y
ImportError: cannot import name 'y'
测试二结果分析
执行m1.py,打印“正在导入m1”,执行from m2 import y ,导入m2进而执行m2.py内部代码--->打印"正在导入m2",执行from m1 import x,此时m1是第一次被导入,执行m1.py并不等于导入了m1,于是开始导入m1并执行其内部代码--->打印"正在导入m1",执行from m1 import y,由于m1已经被导入过了,所以无需继续导入而直接问m2要y,然而y此时并没有存在于m2中所以报错。
解决方案(两种)
方法一:导入语句放到最后
m1.py
print('正在导入m1')
x='m1'
from m2 import y
m2.py
print('正在导入m2')
y='m2'
from m1 import x
方法二:导入语句放到函数中
m1.py
print('正在导入m1')
def f1():
from m2 import y
print(x,y)
x = 'm1'
# f1()
m2.py
print('正在导入m2')
def f2():
from m1 import x
print(x,y)
y = 'm2'
run.py
import m1
m1.f1()
小测试(使用import调用的时候,必须要跟上被调用的值)
m1.py
print('正在导入m1')
import m2
x='m1'
print(m2,y)
m2.py
print('正在导入m2')
import m1
y='m2'
run.py
import m1
3、 py文件区分两种用途:模块与脚本
编写好的一个python文件可以有两种用途:
一:脚本,一个文件就是整个程序,用来被执行
二:模块,文件中存放着一堆功能,用来被导入使用
python为我们内置了全局变量__name__,
当文件被当做脚本执行时:__name__ 等于'__main__'
当文件被当做模块导入时:__name__等于模块名
作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
if __name__ == '__main__':
示范文件
m.py
def f1():
print("from f1")
def f2():
print("from f2")
# print(__name__) #输出的结果为__main__
if __name__ == '__main__':
f1()
f2()
run.py
from m import f1
f1() #执行的结果为:from f1
4、模块搜索路径
模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
模块的查找顺序:
1、在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看
2、如果没有,解释器则会查找同名的内建模块
3、如果还没有找到就从sys.path给出的目录列表中依次寻找spam.py文件。
继续以上面spam.py为例来进行介绍:
spam.py
print('from the spam.py')
money=1000
def read1():
print('spam模块:',money)
def read2():
print('spam模块')
read1()
def change():
global money
money=0
测试一:当run.py与spam.py不在同一个路径下面
在run.py中执行import spam命令的时输出结果如下:
Traceback (most recent call last):
File "D:/Program Files/Test_py/python-16/Day13/预习/模块的使用.py", line 7, in
import spam
ModuleNotFoundError: No module named 'spam'
如何解决以上问题?为什么?spam属于自定义模块,当与执行的程序不在同一个路径下面的时候,会直接报错:需要将其路径加到执行程序中。
import sys
print(sys)
sys.path.append(r"D:\Program Files\Test_py\python-16\Day13\预习\5模块的搜寻路径")
import spam
输出的结果如下:
from the spam.py
测试二:结合time,判断内存中已经加载的模块与sys.path路径中包含的模块的加载顺序
执行run.py中如下程序,在程序运行到time.sleep中时,将spam模块从绝对路径下删除看输出的结果:
import spam,time
time.sleep(10)
import spam
print((spam.money))
输出的结果为:
from the spam.py
1000
如果此时继续运行上面的程序,输出的结果会报错==》说明程序在运行完后后加载到内存中,输出结果后程序结束。
测试三:内置模块与sys.path路径中包含的模块的加载顺序
首先在当前的位置建一个time模块
time.py
print("my time module")
run.py #内置模块需要导入才能加载到内存中
import time
print(time)
time.sleep(3)
输出的结果如下:
5、项目开发的目录规范
项目以ATM为例
bin start.py 执行程序
core src.py 存放核心的业务逻辑,登录、注册、转账、显示余额等功能
conf setting.py 配置文件
lib 放置自定义模块,常用的功能
log access.log 访问日志
db (database数据库)存放数据文件
readme 说明书相当于document
执行程序start.py
# import sys
# sys.path.append(r"D:\Program Files\Test_py\python-16\Day13\ATM购物车\core")
# sys.path.append(r"D:\Program Files\Test_py\python-16\Day13\ATM购物车\lib")
# sys.path.append(r"D:\Program Files\Test_py\python-16\Day13\ATM购物车\conf")
如果你想要的把下面的路径全部都注释掉引用一个路径的话可以使用
# sys.path.append(r"D:\Program Files\Test_py\python-16\Day13\ATM购物车")
问题一:需求对于上面的导入路径不想使用的话,如何将start.py文件直接拖到ATM文件夹下面
问题二:目前使用from...import导入模块都不能使用tab键,
可以将ATM直接在顶级作为顶级文件打开,即file--》open--》new windows
问题三:核心业务逻辑src.py与start.py的环境变量是一样的。
from core import src
# print(sys.path) #导入两个sys查看他们的环境变量否是一样。
if __name__ == '__main__':
src.main()
核心的业务逻辑src.py
import sys
# print(sys.path) #导入两个sys查看他们的环境变量否是一样。
from lib import common
from conf import setting
def login():
print("login")
common.logger("刚刚已经登录了")
def register():
print("register")
common.logger("刚刚注册了一次")
def tranfer():
print("transfer")
common.logger("longge 给 jack转账了10000")
def withdraw():
print("withdraw")
common.logger("longge的余额还有5000000")
func_dic={
"0":["退出",exit],
"1":["登录",login],
"2":["注册",register],
"3":["转账",tranfer],
"4":["提现",withdraw],
}
def main():
while True:
for choice in func_dic:
print("%s%s"%(choice,func_dic[choice][0]))
choice=input("请输入命令编号:").strip()
if choice in func_dic:
func_dic[choice][1]()
else:
print("输入的命令不存在,请重新输入")
配置文件setting.py
import os #可以导入这个模块动态获取绝对路径的上一层目录
# print(os.path.dirname(__file__))#获取当前文件夹的文件夹
添加access.log的绝对路径赋值给一个变量
#方法一:
# LOG_PATH=r"D:\Program Files\Test_py\python-16\Day13\ATM购物车\log\access.log"
BASE_DIR=os.path.dirname(os.path.dirname(__file__))
#方法二:
# LOG_PATH=r"%s\log\access.log" %BASE_DIR
#方法三:
LOG_PATH=os.path.join(BASE_DIR,"log","access.log")
print(LOG_PATH)
DB_PATH=os.path.join(BASE_DIR,"db","db.txt")
自定义模块常用功能:common.py
#记录日志应该放在common.py文件中,是很多的业务逻辑放置的地方
import time
from conf import setting
def logger(msg):
with open(r"%s" %(setting.LOG_PATH),mode="at",encoding="utf-8")as f:
f.write("%s%s\n" %(time.strftime("%F-%H:%M:%S"),msg))