Python 中的 __init__.py 和__all__ 详解(抄袭的joker) 因为写的实在是太好了

发布时间 2023-11-03 16:30:39作者: 纯丿乱

Python 中的 __init__.py 和__all__ 详解

 

之前不论是自己写代码还是用别人的代码,都没有注意过这个东西,今天忽然看了一下,网上的教程感觉讲的都不是很清楚,自己又研究了研究,总结一下,如果有不对的地方,大家帮忙指正一下。

在Python工程里,当python检测到一个目录下存在__init__.py文件时,python就会把它当成一个模块(module)。个人习惯说这是一个包,只有.py的文件我才说是模块,不知道我这个表述规范不规范,不规范的话大家帮忙指正一下。

__init__.py这个东西不是必须有的,如果有的话,在调用包的时候,会运行这个文件。没有的话,也不耽误。

__init__.py存在的意义,是简化代码

看这个示例:

# my_test.py

from test2.sub4.test41 import print_41 # print_41是在test41.py里定义的函数 后面的都可以依次类推 我就不写了

print_41()

跑的通,没任何问题

这样也跑的通:

from test2.sub4 import test41
test41.print_41()

但这个,是跑不通的,不是把py文件写在了一个文件夹下,这个文件夹就是知道他下面有啥了的!!!!

from test2 import sub4
sub4.test41.print_41()

# 报错:AttributeError: module 'test2.sub4' has no attribute 'test41'

下面调整一下,加上__init__.py,但是__init__.py里什么都不写

再跑这段代码:

from test2 import sub4
sub4.test41.print_41()

不好意思,这样照样也是跑不通,会报同样的错误,回到我前面说的,在调用包的时候,会运行__init__.py这个文件,你__init__里啥也没写,就跟没有一样

那要怎么样这段代码才能跑的通呢?在两个__init__文件里分别写上这些东西:

# test2/__init__.py
from . import sub4

# test2/sub4/__init__.py
from . import test41

然后运行那一段代码,就可以跑的通了,原理就是,我在运行 from test2 import sub4 这句代码的时候,就运行了 test2/sub4/__init__.py (其实test2/__init__.py也有运行) 就相当于在sub4这个包里,引入了test41这个模块,sub4知道自己有这个模块了。

这里还有一个,我为什么要写 from . import test41 直接 import 不行吗?不行的,在我们执行 import 时,当前目录是不会变的(就算是执行子目录的文件),还是需要完整的路径的。from . 的意思,就是在当前运行文件的所在目录下面找。

所以 我如果运行下面这个:

from test2 import sub4
sub4.test42.print_42()

跑不通的, test2/sub4/__init__.py没有写 import test42,所以sub4是不知道自己下面有test42这个模块的

继续 为什么我说是运行了那两个__init__.py文件呢,我们稍微修改一下__init__.py文件:

# test2/__init__.py
from . import sub4
print("you have imported sub4")

# test2/sub4/__init__.py
from . import test41
print("you have imported test41")

再来运行这个代码:

from test2 import sub4
sub4.test41.print_41()

# 输出:
# you have imported test41
# you have imported sub4
# 41 (函数.print_41的输出)

看到了吧 from test2 import sub4 照样会运行 test2/__init__.py 这个文件

哎 反正涉及到 test2 就会运行 test2/__init__.py,就可以import sub4,import sub4 不就又运行了 test2/sub4/__init__.py 那我这样写不也可以吗:

import test2
test2.sub4.test41.print_41()

当然可以了,完全跑的通,输出也依然是:

# you have imported test41
# you have imported sub4
# 41 (函数.print_41的输出)

但这样可不行:

import test2
sub4.test41.print_41()

冷不丁的写一个sub4,是不行的,在my_test.py里sub4是没有被定义的。

但是这样写好长啊,有没有办法短一点呢?有办法的,把 test2/__init__.py 改成这个:

# test2/__init__.py
from .sub4.test41 import print_41 as p41
print("you have imported sub4")

然后 我就只需要写,就可以了:

import test2
test2.p41()

# 输出
# you have imported test41
# you have imported sub4
# 41 (函数.print_41的输出)

其实一般情况下,init文件都是这样写的,不然写了init 我们还要写一大堆东西,那写init干嘛呢。

 

接下来 还要说一个变量,就是__all__:

看上边这个东西

# test/__init__.py
from .sub1 import *
from . import sub2
from . import sub3
__all__=['sub1','sub3']
print("You have imported test")

# test/sub1/__init__.py
from . import test11,test12
__all__ = ['test11']
print("you have imported sub1")

# test/sub2/__init__.py
from . import test21
from . import test22
print('you have imported sub2')

# sub3 里没有init文件

然后我们分别运行下面的代码 我直接把输出都列出来了:

from test import *
print(dir())

# 输出:
# you have imported sub1
# you have imported sub2
# You have imported test
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sub1', 'sub3']
# 看好了,输出了 you have imported sub2 ,dir()没有sub2,原因就是,sub2 不在__all__这个变量里。那为什么会输出那句话呢,原因就是 运行了test/__init__.py

import test
print(dir())
print(dir(test))
print(dir(test.sub1))
print(dir(test.sub2))
print(dir(test.sub3))
test.test11.print_11()

# 输出:
# you have imported sub1
# you have imported sub2
# You have imported test
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'test']
# ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'sub1', 'sub2', 'sub3', 'test11']
# ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'sub1', 'sub2', 'test11']
# ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'test11', 'test12']
# ['__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
# 11

# 我们发现,在当前的py文件下,只有test这个包,但在test包中,不仅有 'sub1', 'sub2', 'sub3',还有一个 'test11'模块,并且可以直接test.test11.print_11()调用里面的函数
# 我们还发现,test.sub1包里照样有test11和test12两个,test.sub2里也照样有test21和test22两个,sub3下面啥也没有
# 挨个解释,只有当 from test import * 的时候,__all__才有影响。当直接import test 时,__all__是影响不到test中子包的。dir(test)里,是有sub2的
# 为什么test下会有 test11,因为在init里,写的是 from .sub1 import * ,这就相当于把sub1包里的模块导入了test包里,所以可以直接调用
# 为什么test下没有 test12呢,因为test12 不在 test/sub1/__init__.py 的__all__变量里。
# 为什么test.sub1下会有test11和test12两个,原因还是__all__是影响不到包里面的模块的。
# 为什么sub3下面啥也没有,因为sub3里是没有init的,他是不知道自己下边有啥的

加深一下理解,把 test/__init__.py 改一下,改成这样:

# test/__init__.py
from . import sub2
from . import sub3

__all__=['sub1','sub3']
print("You have imported test")

运行下面这个:

import test
print(dir())
print(dir(test))

# 输出
# you have imported sub2
# You have imported test
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'test']
# ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'sub2', 'sub3']
# 完全没有sub1什么事了,原因就是在test里没有import sub1,test下是没有那个子包的

from test import *
print(dir())
# 输出
# you have imported sub2
# You have imported test
# you have imported sub1
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sub1', 'sub3']
# 哎!这下有了sub1了,但注意you have imported sub1这句话跑到最后了。原因是,__all__变量起了作用,并且是在运行完了init之后,才把sub1给弄进来的