python

您所在的位置:网站首页 python__init__py python

python

2023-01-27 02:27| 来源: 网络整理| 查看: 265

python __init__.py 文件的用法 发表于 2021-07-11 更新于 2021-07-12 阅读次数: 本文字数: 3.2k 阅读时长 ≈ 3 分钟

最近 rush 代码遇到一些问题,如一种典型的结构

12345|-main/|----test1/|--------module1.py|----test2/|--------module2.py

如上,想在 module2.py 中调用 module1.py 中的某个类,如果在 module2.py 中写:from ..test1 import module1,在 test2 文件夹下执行 python module2.py 会提示:

1ImportError: attempted relative import with no known parent package

会遇到这样的错误。那么,如何解决呢?如果你只想看如何解决问题,直接翻到文末即可;网上大概搜了一下,需要 __init__.py 来解决下这个问题,但是网上搜了一圈,没啥写的特别好的教程,实在是烂的可以,特此来填坑。

__init__.py 是什么 1

假设此时的路径结构为:

12345678|-main/|----__init__.py|----test1/|--------__init__.py|--------module1.py|----test2/|--------__init__.py|--------module2.py

在 test1 目录下的 __init__.py 中写入:

1print('module1 was called')

在 test2 目录下的 __init__.py 中写入:

1print('module2 was called')

在 main 目录下的 __init__.py 中写入:

123print('parent package was called')# 导入 [] 里面定义的模块__all__ = ['test1', 'test2'] 实验

通俗来说,__init__.py 可以将文件封装成包,将多个文件合并到一个逻辑命名空间。但是这么说太突兀了,由浅入深,一点一点来。先来看看文件夹中添加 __init__.py 会发生什么。假设此时的路径为 main 文件夹下,尝试导入模块,会发现上述信息被打印:

同理,在 main 文件夹的 上一级路径 下执行导入,也会有同样的效果,但是不会导入子模块。

如果想导入单个子模块,可以 import main.test1,此时会打印 module1 was called;如果再次调用 import main.test1,也就是在模块已经导入的情况下再次导入,则不会打印任何信息。

如果导入全部子模块,也是可以的。因为声明了 __all__,所以子模块被导入。

但是你也许会有疑问,我经常写 import math,而 math.sin 等函数是导入的,且可以使用,为什么这里就不行了呢?如果想行,也是可以的,只需要在 main 目录下的 __init__.py 中写入以下信息就可以了,也就是 import main; main.test1 可用。

1234print('parent package was called')# 删除 __all__from . import test1from . import test2

通过以上例子,我们可以看出,__init__.py 会起到以下作用:

导入模块时初始化一些信息,如 web 项目中,启动 session 等 在父目录中,导入多个子模块 进阶

也许你会觉得以上功能比较弱,或者说没啥用。那么来看一些实用的简化工作量的写法 2 。此时的目录结构如下:

1234567├─ main.py└─ network ├─ __init__.py ├─ msg │ └─ info │ └─ send.py └─ parse.py

在 send.py 中,定义如下函数:

12def send_msg(msg): print('send:', msg)

如果想在 main.py 中调用这个函数,需要以下写法:

12345from network.msg.info import sendsend.send_msg('hello')# 或者# from network.msg.info.send import send_msg# send_msg('hello')

但无论那种方法,都要写长长的路径,甚为不便。这个时候,我们可以在 network 文件夹下面创建一个 __init__.py 文件,并在里面填写如下内容:from .msg.info.send import send_msg。而 main.py 文件中的内容可以修改为:

12from network import send_msgsend_msg('hello')

是不是简短了很多。这是因为,当一个文件夹里面有 __init__.py 以后,这个文件夹就会被 python 作为一个包 package 来处理。此时,对于这个包里面层级比较深的函数、常量、类,我们可以先把它们导入到 __init__.py 中。这样以来,包外面再想导入这些内容时,就可以用 from 包名 import 函数名 来导入了。

这样做有很多好处,由于调用包的其他模块所在的绝对路径是千变万化的,当有一些代码会在很多地方被使用时,我们可以把这些代码打包起来,作为一个公共的接口提供给其他模块调用,这会方便很多。

所以在包的内部调用自身其他文件中的函数、常量、类,就不应该使用相对路径,而是绝对路径。这里以添加新功能为例,如下所示,在 parse.py 文件中添加以下内容:

123456# 两种都可以# from .msg.info.send import send_msgfrom . import send_msgdef parse_msg(msg): print('parse:', msg) send_msg(msg)

可以看到,在包里面的一个文件调用这个包里面的另一个文件,只需要知道另一个文件的相对位置就可以了,不用关心这个包被放在哪里。上 面parse.py 中导入 send_msg 函数的代码还可以进一步简化,由于 send_msg 函数已经被导入到了 __init__.py 中,所以我们可以直接从 . 里面导入 send_msg 函数。

之后在 __init__.py 中追加:

1from .parse import parse_msg

此时,main.py 的写法可以如下,可以看到,即使追加了新的模块,main.py 调用起来也会很方便,并不需要知道 parse_msg 这个方法的任何位置信息。

12from network import parse_msgparse_msg('hhh')

此外,当一个文件夹里面包含 __init__.py 时,这个文件夹会被 python 认为是一个包 package,此时,包内部的文件之间互相导入可以使用相对导入,并且通过提前把函数、常量、类导入到 __init__.py 中再在其他文件中导入,可以精简代码。

问题解决

既然了解了 __init__.py 的用法,那么去解决文章最开始提到的问题。目录结构如下:

1234567main├─ main.py├─ test1│ ├─ __init__.py│ └─ m1.py└─ test2 └─ m2.py

实现的想法也很简单,m2.py 调用 m1.py 中的函数。

m1.py 定义如下:

12def send(msg): print(msg)

m2.py 定义如下:

123from test1 import m1def run(): m1.send('hello')

距离成功只差一步,那就是修改 test1 中的 __init__.py 的内容,把 test1 看成一个 package,暴露其中的 m1 即可。

123from test1 import m1# 或者# from . import m1

这样,在外部的 main 函数中:

12345import test2.m2 as m2m2.run()# 或者# from test2 import m2# m2.run()

就可以了。

references1.https://zhuanlan.zhihu.com/p/130927618 ↩2.https://www.kingname.info/2020/03/23/init-in-python/ ↩ 本文相关 C++ 中的 this 和 Python 中的 self 从分词到自然语言的世界 迭代器与生成器 PyQt5的基本控件整理 PyQt5踩坑 明人不说暗话,如果感觉这篇文章还不错,您的打赏是对我读书路上莫大的支持,当然一切全凭自愿。 实在不行,我,秦始皇,打钱。 兰铃 o(*≧▽≦)ツ

o(*≧▽≦)ツ

兰铃 ♪(^∇^*)

♪(^∇^*)

欢迎订阅我的文章

RSS # Python 对抗训练篇:MART 防御算法论文笔记 C++ 中的引用


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3