多起村干部被灭门案,需要反思了!

高铁打人者身份被曝光,人脉资源碾压普通人!却遇到了硬茬

OPPO芯片业务解散不一定是坏事

明确了!任期届满后,他不再寻求连任!

去泰国看了一场“成人秀”,画面尴尬到让人窒息.....

生成图片,分享到微信朋友圈

自由微信安卓APP发布,立即下载! | 提交文章网址
查看原文

聊一聊__init__.py文件 | 第17期

青衣极客 青衣极客 2020-01-12

      最近在使用django开发一个网站项目,刚开始创建的数据库模型都在一个models.py文件中,等到模型较多以后就显得非常不方便,不仅是逻辑组织不清晰,修改代码也要找很久。这时就有一个需求,即创建一个名为models的python包,然后在包内分文件组织模型。但是要求对包外的所有调用与之前的models.py没有差异,否则修改外部调用的代码也是一件非常头疼的事情。这种情况下的需求可以抽象为使用python包模块提供与python文件模块同样的操作接口

       在开发稍微复杂一点的python项目中,这种需求都是会经常遇到的,而对于一个python开发者而言,掌握这种需求的实现方式也是非常必要的。虽然大多数的开发者可能并不需要在自己的项目中这样操作,但是在调用一些第三方库的时候,却是常常遇到这种设计并受益匪浅。这种需求的实现方式的关键就是__init__.py文件。这个文件对使用python的朋友应该是再熟悉不过,因为我们常常遇到需要在目录中添加这样一个诡异的空文件,否则就会报错。本文就讨论一下python中的__init__.py文件在组织python包时的作用。本次实验需要创建一些目录和文件,方便起见,创建本次实验代码的根目录,然后加载到环境变量的path中,如下演示。

# 每次服务核心重启之后,都需要先运行以下这块代码,确保路径在path中# 创建本次实验所需代码的根目录!mkdir -p ./code/init_py# 将该目录添加到python模块的搜索目录中import syssys.path.append('./code/init_py')

1. python包由__init__.py定义

在使用python2的时候,我们被教导需要在作为python模块的目录中放置一个__init__.py文件,否则python解释器不会将该目录视为python模块,而对于这种目录的import操作都会报错。因此__init__.py文件是python包的定义,当然如果使用python3.3或更高版本的朋友会提出质疑。下面我们就看一下在python2.7与python3.7中的演示。

# 在根目录下创建一个目录!mkdir ./code/init_py/test_pkg!tree ./code/init_py/try: import test_pkgexcept Exception as e: print('没有__init__.py, 导入模块出错: {}'.format(e))else: print('没有__init__.py, 导入模块成功')# 在该目录下添加一个名为__init__.py的空文件!touch ./code/init_py/test_pkg/__init__.py!tree ./code/init_py/# 再次导入该模块import test_pkgprint('有__init__.py, 导入模块成功!')!rm -rf ./code/init_py/test_pkg

Output:

没有__init__.py, 导入模块成功有__init__.py, 导入模块成功!

同样的情况,在python2下运行这段代码的结果如下:

没有__init__.py, 导入模块出错: No module named test_pkg有__init__.py, 导入模块成功!

       从以上演示可以看出,在python2.7的实例中,没有__init__.py文件的目录在import的时候就报错了,错误原因是没有该名字的模块。而在python3.7中存不存在这个文件对python包的导入没有影响。事实上从python3.3开始就不需要使用__init__.py文件来定义python包了。这样说来,如果所使用的python版本在3.3以上是否就不需要再使用__init__.py文件呢?从实现的角度而言,确实如此,因为可以使用import直接导入包内部所需的功能模块。但是,对于为外部调用提供方便以及实现python包替换python文件模块的需求而言,__init__.py文件仍然是必要的,而且还需要在其中添加一些内容。

2. __init__.py为空或非空的区别

      如果只是将一个目录作为组织python代码的过渡,而不需要将其作为python包,那么使用空的__init__.py文件或者不要这个文件都是可以的。如果需要将代码按照python包的方式组织,即包具有子模块属性,那么需要在__init__.py文件中对子模块进行导入。下面的演示包含了使用空的__init__.py文件和包含导入子模块代码的__init__.py文件。注意在notebook中演示以下两段代码之前最好重启一下服务,因为一些缓存机制会使实验效果出现混淆。

使用空__init__.py文件的例子:

# 清空__init__.py文件!printf '' > ./code/init_py/test_pkg/__init__.py# 查看目录结构!tree ./code/init_py/# 导入模块import test_pkgprint(test_pkg.__dir__())del test_pkg

Output:

./code/init_py/└── test_pkg ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── func1.cpython-37.pyc └── func1.py
2 directories, 4 files['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__']

使用包含导入子模块代码的__init__.py文件的例子:

# 查看__init__.py文件的内容!cat ./code/init_py/test_pkg/__init__.py!printf '\r\n'# 查看目录结构!tree ./code/init_py/# 导入模块import test_pkgprint(test_pkg.__dir__())del test_pkg

Output:

from .func1 import f1./code/init_py/└── test_pkg ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── func1.cpython-37.pyc └── func1.py
2 directories, 4 files['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'func1', 'f1']

     从演示结果来看,在__init__.py中包含子模块导入的操作之后,该模块的__dir__()函数中包含了子模块名,而在空的__init__.py文件演示中,模块对应的__dir__()函数就没有子模块的名字。这有什么意义呢?这个的意义在于,对于空__init__.py,如果单独导入模块是没有作用的,因为没有可供调用的子模块。也就是说,空__init__.py的目录只适合作为模块代码组织中的中间命名空间,而不是真正功能完整的python包。

3. 导入所有子模块

      虽然使用星号导入所有子模块并不是很推荐的编写代码的方式,但是日常开发中这样使用确实能增加一些效率。而且在一些不是太正式的项目中也是比较常用。有两种方式可以实现,一种是在__init__.py文件中使用import语句逐个导入子模块,另一种方式是将子模块名填充到__all__变量中。

# 查看__init__.py文件的内容!cat ./code/init_py/test_pkg/__init__.py!printf '\r\n'# 查看目录结构!tree ./code/init_py/# 导入模块from test_pkg import *func1.f1()

Output:


from . import func1
./code/init_py/└── test_pkg ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── func1.cpython-37.pyc └── func1.py
2 directories, 4 files运行正常

使用__all__变量的例子如下:

# 查看__init__.py文件的内容!cat ./code/init_py/test_pkg/__init__.py!printf '\r\n'# 查看目录结构!tree ./code/init_py/# 导入模块from test_pkg import *func1.f1()

Output:

__all__=['func1']
./code/init_py/└── test_pkg ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── func1.cpython-37.pyc └── func1.py
2 directories, 4 files运行正常

      到此,关于python中的__init__.py文件的使用就讨论到这里。那么本文开头所描述的情形能够实现了吗?其实就是在__init__.py文件中将每个子模块中的模型都导入,就可以实现对外接口与单个文件模块一致。需要说明的是,如果想要复现本文实验结果,需要在每个实验之前重启一下notebook的服务核心,避免缓存引起的混淆。


github链接:https://github.com/cnbluegeek/notebook/tree/master

欢迎对python和人工智能感兴趣的朋友加入微信群讨论和交流:


    文章有问题?点此查看未经处理的缓存