Bootstrap

python package学习

python package

引言

前提知识:days10-python-modules&& namespace学习

我们已经学习了python 的模块知识,我们也知道了模块就是.py文件,那如果我们有很多模块呢,是将他们放在一个目录下?那如果模块名相互重复了我们要怎么解决冲突呢?还有这个多模块之间肯定会有名称重复呀(函数名重复、全局变量名重复、类名重复等问题),我们如何解决?

要解决上面的问题我们就要引入包:包允许使用点表示法对模块命名空间进行分层结构管理。它有助于避免模块间全局变量名之间的冲突,包也有助于避免模块名之间的冲突。

python package ,其实就是一个目录,用来管理模块的,不过这个目录与我们操作系统的文件夹有点小小的不一样,它 必须包含__init__.py 这样一个模块(文件),这个文件的内容可以为空,但必须有这个文件来标识当前所在文件夹为python package。

包的创建

  • 直接通过创建文件夹的方式创建,并在文件夹中添加__init__.py 文件

通过这种方式创建,要注意你的文件夹所在路径一定要在sys.path的路径中呀。否则导入包的时候会提示找不到包哦。

  • 通过pycharm的方式创建。

image-20221007093459541

举例:

我通过pycharm的方式创建了名称为package的包,并在包中添加了mod1.pymod2.py的两个模块

image-20221007094114833

mod1.py

def mod1_func():
    print('[mod1] mod1_func()')

mode_var='我是模块1'

mod2.py

def mod2_func():
    print('[mod2] mod2_func()')

mode_var='我是模块2'

包的导入

我们可以通过类似于点的方式(pakcage.mod1, package.mod2)来import我们包中的模块,语法其实与模块的导入语法类似。

回忆模块的导入语法:

import <module_name>[, <module_name> ...]

包的导入举例:

工程下新建days11.py

import package.mod1,package.mod2
print(package.mod1.mode_var)
print(package.mod2.mode_var)
package.mod1.mod1_func()
package.mod2.mod2_func()

输出:

我是模块1
我是模块2
[mod1] mod1_func()
[mod2] mod2_func()

image-20221007100108261

当然上面两种方式导致我们使用包中内容十分麻烦,我们要通过包名.模块名.这样的前缀去使用我们模块中的内容,我们改成from import的语句

回忆模块的from import语法:

from <module_name> import <name> as <alt_name>

举例:

from package.mod1 import mode_var as mode1_var ,mod1_func
from package.mod2 import mode_var as mode2_var,mod2_func
print(mode1_var)
print(mode2_var)
mod1_func()
mod2_func()

image-20221007100906817

包也支持下面这种from import语法

from <package_name> import <modules_name>[, <module_name> ...]
from <package_name> import <module_name> as <alt_name>

示例:

from package import mod1 as model1
from package import mod2

print(model1.mode_var)
print(mod2.mode_var)
model1.mod1_func()
mod2.mod2_func()

image-20221007101357313

__init__.py

结论:该文件的作用就是相当于把自身整个文件夹当作一个包来管理,每当有外部import包或者包中模块的时候,就会自动执行里面的函数。

我们已经知道我们在模块中我们可以使用from <module_name> import *,[虽然不建议这样做]。那包是否也有个from <package_name> import *的语法呢?

试试:

我们知道dir()是返回当前命名空间的名称列表

列如:

from package.mod1 import *
print(dir())

输出:

[‘annotations’, ‘builtins’, ‘cached’, ‘doc’, ‘file’, ‘loader’, ‘name’, ‘package’, ‘spec’, ‘mod1_func’, ‘mode_var’]

结果是返回了mod1的内容呀

那么:

from package import *
print(dir())

是会返回package下的mod1、mod2名,还是?

结果:

['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']

无报错说明语法是支持的呀,但关于mod1、mod2什么也没有返回呀。实际上python遵循这样的约定:当我们使用from <package_name> import *这样的语句的时候,我们必须在__init__.py中在一个叫__all__名字的列表中列入你要导入的模块名

举例:

image-20221007221555497

from package import *
print(dir())
mod1.mod1_func()
mod2.mod2_func()

结果:

image-20221007222031820

其实模块中使用import * 也必须在模块中在__all__列表中申明什么要导入

__init__.py还可以干些什么呢?

举例我有如下的这种包结构

.
└── package
|    ├── subpackage
|    │   ├── mod3.py
|    │   └── subsonpackage
|    │    	 |__mod4.py
|    └── mod1.py
|	 |__ mod2.py
|    |__ __init__.py
|_____ days11.py

模块对应代码如下

#mod1.py
def mod1_func():
    print('[mod1] mod1_func()')

mode_var='我是模块1'

#mod2.py
def mod2_func():
    print('[mod2] mod2_func()')

mode_var='我是模块2'
#mod3.py
def mod3_func():
    print('[mod3] mod4_func()')

mode_var='我是模块3'
##mod4.py
def mod4_func():
    print('[mod4] mod4_func()')

mode_var='我是模块4'

试想我要在去days11.py引用mod1、mod2、mod3、mod4中的函数 ,那我是不是我要写类似下面这种的import 语句, 那如果我不止days10.py要引入,我还有其他文件都要引入,那是不是我每个文件都要写类似下面的这种import语句。

from package.subpackage.subsonpackage import mod4
from package.subpackage import mod3
from package import  mod1 , mod2

image-20221007230537535

还有其他办法吗?不想写这么多重复的代码…

尝试一:

我啥都不干,直接导入包是否可行?

image-20221008213753766

不行呀,显然它把我的包当成了模块来处理了…

尝试二 使用__init__.py

修改__init__.py文件,将我的导包语句放到__init__.py中呢

__init__.py

from package import mod1,mod2
from package.subpackage import mod3
from package.subpackage.subsonpackage import mod4

days11.py


import package as pg

pg.mod1.mod1_func()
pg.mod2.mod2_func()
pg.mod3.mod3_func()
pg.mod4.mod4_func()

输出:

image-20221008214817292

结果可行啊…

我甚至可以这样玩

__init__.py

from package import mod1,mod2
from package.subpackage import mod3
from package.subpackage.subsonpackage import mod4
func1=mod1.mod1_func
func2=mod2.mod2_func
func3=mod3.mod3_func
func4=mod4.mod4_func

days11.py

import package as pg

pg.func1()
pg.func2()
pg.func3()
pg.func4()

输出:

image-20221008215244108

什么原理?

答: 命名空间 ,在__init__.py中声明的所有类型和变量,就是其代表的模块的类型和变量。

__init__.py的设计原则

A、不要污染现有的命名空间。模块一个目的,是为了避免命名冲突,如果你在种用__init__.py时违背这个原则,是反其道而为之,就没有必要使用模块了。

B、利用__init__.py对外提供类型、变量和接口,对用户隐藏各个子模块的实现。

C、只在__init__.py中导入有必要的内容,不要做没必要的运算。


参考资料:

Python Modules and Packages – An Introduction

init__.py的神奇用法

;