python用了2年多,一直都是用来开发小工具,每个项目的文件一般都不超过10个,疫情禁足期间,突然想用python开发一套游戏类的演示系统,于是就自己写了一个框架,发现python在模块化开发这块还是有挺多知识点的,今天就把它总结一下吧,纯粹是个人的实践和总结,有不对的地方,欢迎各位看观指正!
在讲模块化开发之前,先讲一下模块化软件设计的概念。
模块化程序设计是指在进行程序设计时将一个大程序按照功能划分为若干小程序模块,每个小程序模块完成一个确定的功能,并在这些模块之间建立必要的联系,通过模块的互相协作完成整个功能的程序设计方法。
我的理解就是方便程序的管理和复用的一种开发手段。
那么,模块到底指的是什么呢?模块,英文为 Modules,至于模块到底是什么,可以用一句话总结:模块就是 Python 程序。换句话说,任何 Python 程序都可以作为模块,包括在前面章节中写的所有 Python 程序,都可以作为模块。
模块可以比作一盒积木,通过它可以拼出多种主题的玩具,这与前面介绍的函数不同,一个函数仅相当于一块积木,而一个模块(.py 文件)中可以包含多个函数,也就是很多积木。
而模块化开发就是将这些积木有机,合理地组织到一起,提高代码的复用性。
python中是用import导入模块的,其用法如下:
-
import 模块名1 [as 别名1], 模块名2 [as 别名2],…
:使用这种语法格式的 import 语句,会导入指定模块中的所有成员(包括变量、函数、类等)。不仅如此,当需要使用模块中的成员时,需用该模块名(或别名)作为前缀,否则 Python 解释器会报错。 -
from 模块名 import 成员名1 [as 别名1],成员名2 [as 别名2],…
: 使用这种语法格式的 import 语句,只会导入模块中指定的成员,而不是全部成员。同时,当程序中使用该成员时,无需附加任何前缀,直接使用成员名(或别名)即可。
python中用import导入第三方安装的模块的使用方法,这里我就不详述了,那个东西看两眼基本上就知道怎么用了,这里我主要说说导入自定义模块的使用方法。
1、在入口文件中导入同级目录下的文件(引用同级目录下整个文件,文件名即包名,通过包名引用函数)
我在demo这个项目目录下有2个文件,一个是入口文件main.py,一个是导入文件base.py
base.by的代码如下:
# -*- coding: utf-8 -*-
def foo():
print('foo')
def mass():
print('mass')
class animal():
def __init__(self):
print('mi-ao')
main.py的代码如下:
# -*- coding: utf-8 -*-
import base
if __name__ == '__main__':
base.foo()
cat = base.animal()
2、在入口文件中导入模块中一部分函数或者类,可以直接在入口文件中使用这些函数或类:
main.py的代码
# -*- coding: utf-8 -*-
from base import foo,animal
if __name__ == '__main__':
foo()
cat = animal()
3、在入口文件中引用导入模块中的变量
修改base.py的内容
# -*- coding: utf-8 -*-
PRO_NAME = "BOYIKA V2.0"
DEBUG = False
def foo():
print('foo')
def mass():
print('mass')
class animal():
def __init__(self):
print('mi-ao')
main.py
# -*- coding: utf-8 -*-
from base import *
if __name__ == '__main__':
print(PRO_NAME)
4、从根目录下的子文件夹中导入模块,项目目录如下:
在项目目录下创建了python package目录inc,pycharm给自动生成了__init__.py空文件,我们暂且先不去管它,后面再细讲。
我们在这个文件夹下创建另一个模块文件core.py
core.py的内容如下:
# -*- coding: utf-8 -*-
import os
def getCurrentDir():
return os.path.split(os.path.abspath(__file__))[0]
现在我们要在入口文件main.py中导入core.py中的函数,在main.py中调用方法如下:
#第一种:直接导入,三级调用
# -*- coding: utf-8 -*-
import inc.core
if __name__ == '__main__':
dir = inc.core.getCurrentDir()
print(dir)
#第二种:from导入,二级调用
# -*- coding: utf-8 -*-
from inc import core
if __name__ == '__main__':
dir = core.getCurrentDir()
print(dir)
#第三种:from导入,直接调用
# -*- coding: utf-8 -*-
from inc.core import *
if __name__ == '__main__':
dir = getCurrentDir()
print(dir)
5、在子目录下导入模块:
项目目录结构如下:
在inc这个文件夹里,我创建了一个tools.py文件,我想在同级目录下core.py中导入引用tools.py中的内容。
tools.py代码如下:
# -*- coding: utf-8 -*-
def add(x,y):
return x+y
def remove(x,y):
return x-y
core.py中代码如下:
# -*- coding: utf-8 -*-
import os
from .tools import add #前面这个点很重要,表示在当前目录导入的意思
def getCurrentDir():
return os.path.split(os.path.abspath(__file__))[0]
def core_add(x,y):
return add(x,y)
6、在子目录中导入其下级目录下的模块
在inc这个文件夹里,我又创建一个lib文件夹,这个目录下同样有一个__init__.py,同时我还创建了一个image.py,我想在其上级目录中的core.py中导入image.py的内容。
image.py的内容如下:
# -*- coding: utf-8 -*-
def getImageSize():
return 800,600
在core.py中的引用方式如下:
# -*- coding: utf-8 -*-
import os
from .tools import add
from .lib import image #或者from .lib.image import *
def getCurrentDir():
return os.path.split(os.path.abspath(__file__))[0]
def core_add(x,y):
return add(x,y)
def core_imge_size():
return image.getImageSize() #或者 getImageSize()
7、引入上级目录或者引用其他目录
项目目录如下:
我想在inc目录下的core.py中导入其上级目录下的conf文件夹中的sys_conf.py中的变量。
sys_conf.py中的内容如下:
# -*- coding: utf-8 -*-
WHITE = (255,255,255)
BLACK = (0,0,0)
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
在core.py中的引用如下:
# -*- coding: utf-8 -*-
import sys
sys.path.append("..")
from conf.sys_conf import *
def get_white():
return WHITE
8、使用__init__.py文件提升效率
当一个包文件夹下有很多模块需要导入时,过多得使用import会导致程序非常繁琐,这样可以借用__init__.py文件来解决这个问题。
在这里我们需要理解2个概念:
1)在执行import时,__init__.py是自动被执行的
2)介于上面的原因,我们可以在这个文件里预先导入一些我们需要的模块。
请看下图项目目录结构,我在conf这个包文件夹中放了2个文件,我想通过配置__init__.py同时访问conf下面所有模块。
__init__.py的内容如下:
# -*- coding: utf-8 -*-
from .sys_conf import *
from .pay_conf import *
下面在入口文件main.py中用一句放引用conf下的内容
# -*- coding: utf-8 -*-
from conf import *
if __name__ == '__main__':
print(pay_switch['alipay'])
9、__all__变量的用法
事实上,当我们向文件导入某个模块时,导入的是该模块中那些名称不以下划线(单下划线“_”或者双下划线“__”)开头的变量、函数和类。因此,如果我们不想模块文件中的某个成员被引入到其它文件中使用,可以在其名称前添加下划线。
如果你不想通过__init__.py文件导入某些模块成员,可用以__all__变量来过滤。
下面来举例说明:
将sys_conf.py的内容修改如下:
# -*- coding: utf-8 -*-
WHITE = (255,255,255)
BLACK = (0,0,0)
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
__all__ = ['RED','GREEN','BLUE']
在该文件年末尾,我们在这里配置了__all__变量,意思是只允许外面引用RED,GREEN,BLUE这3个变量,而WHITE和BLACK则不允许访问。执行结果如下:
注:在变量之前加上单下划线_或者双__下划线,也可以设置变量为不可被引用。
10、模块的高级应用: 模块的动态引用。
比如系统中有许多支付方式,每个支付方式的业务逻辑都是一样的,只是支付内部的算法不一样,我们可以把多种支付方式整合到一个库里,根据用户选择的不同的支付方式,动态加载对应的支付模块。
项目目录结构如下:
在项目根目录下创建一个pay文件包,下面有2个文件,一个是alipay,一个是wechat.
其中aplipay.py的内容如下:
# -*- coding: utf-8 -*-
class payment():
def __init__(self):
print('alipay')
def dopay(self):
print('pay for alipay')
wechat.py中的内容如下:
# -*- coding: utf-8 -*-
class payment():
def __init__(self):
print('wechat')
def dopay(self):
print('pay for wechat')
在入口文件main.py中的调用方式如下:
# -*- coding: utf-8 -*-
import importlib
import importlib.util
#检测引用的程序包是否可用
def check_module(module_name):
module_spec = importlib.util.find_spec(module_name)
if module_spec is None:
print("the %s is error!" % module_name)
return module_spec
#如果可用则导入此程序包
def load_module(module_name):
module_checked = check_module(module_name)
if module_checked:
mLoader = importlib.util.module_from_spec(module_checked)
module_checked.loader.exec_module(mLoader)
return mLoader
else:
return None
if __name__ == '__main__':
pay_module = 'wechat' #假设用户选择的是wechat支付方式
mo = load_module('pay.'+pay_module) #动态加载pay.wechat
#print(mo.payment())
dopay = mo.payment() #执行同名支付方法