python 模块中的 __init__.py __main__.py

1 概念

假设一个最简单的Package如下:

 

(1) 如果你希望 python 将一个文件夹作为 Package 对待,那么这个文件夹中必须包含一个名为 __init__.py 的文件,即使它是空的。

(2) 如果你需要 python 将一个文件夹作为 Package 执行,那么这个文件夹中必须包含一个名为 __main__.py 的文件。


在实际中,可以将pkg作为一个文件夹执行:

python pkg

  

也可以将pkg作为一个Package执行:

python -m pkg

2. 那么,这两者有什么区别呢?为此,我们做一个简单的实验。


(1) 案例1

在__init__.py写入如下内容:

import sys

print('__init__')
print('__init__.__name__', __name__)
print('__init__.__package__', __package__)

在__main__.py写入如下内容:

import sys

print('__main__')
print('__main__.__name__', __name__)
print('__main__.__package__', __package__)

print('sys.path', sys.path)

执行 python pkg 和 python -m pkg,对比一下它们的输出结果:

E:\>python pkg
__main__
__main__.__name__  __main__
__main__.__package__
sys.path ['pkg', 'D:\\Software\\Python\\Python37\\python37.zip', 'D:\\Software\\Python\\Python37\\DLLs', 'D:\\Software\\Python\\Python37\\lib', 'D:\\Software\\Python\\Python37', 
'D:\\Software\\Python\\Python37\\lib\\site-packages']

  

E:\>python -m pkg
__init__
__init__.__name__ pkg
__init__.__package__ pkg
__main__
__main__.__name__  __main__
__main__.__package__ pkg
sys.path ['E:\\Python_Web\\day_learning', 'D:\\Software\\Python\\Python37\\python37.zip', 'D:\\Software\\Python\\Python37\\DLLs', 'D:\\Software\\Python\\Python37\\lib', 'D:\\Software\\Python\\Python37', 
'D:\\Software\\Python\\Python37\\lib\\site-packages']

  

可以看出:

(a) 当作文件夹执行的时候,__init__.py 不会被执行。在 __main__.py 来说,打印的变量 __package__ 是一个空字符串。
     当作模块执行的时候,会先执行 __init__.py ,再执行 __main__.py 。对于 __main__.py 来说,变量 __package__ 是 Package 的名字(pkg)。
另外, __init__.py 和 __main__.py 中的 __name__变量的值也是不同的。


(b) 对于一个 Package 来说,既然 __init__.py 必须存在,并且当作为模块执行的时候,它会先执行,我们就应该把入口函数 main() 定义在 __init__.py 中。
当我们使用模块方式 -m 执行的时候, __init__.py 定义了 main() 函数,然后在 __main__.py 中调用它,就能实现我们统一入口的目的。

 

(2) 案例2


对 __init__.py 做如下修改:

  

import sys

print('__init__')
print('__init__.__name__', __name__)
print('__init__.__package__', __package__)

print('sys.path', sys.path)


def main():
    print('__init__.main()')

对 __main__.py 做如下修改:

import sys

print('__main__')
print('__main__.__name__', __name__)
print('__main__.__package__', __package__)

print('sys.path', sys.path)

import pkg
pkg.main()

执行 python pkg ,调用失败;执行 python -m pkg,调用正常。对比一下它们的输出结果:

E:\>python pkg
__main__
__main__.__name__ __main__
__main__.__package__
sys.path ['pkg', 'D:\\Software\\Python\\Python37\\python37.zip', 'D:\\Software\\Python\\Python37\\DLLs', 'D:\\Software\\Python\\Python37\\lib', 'D:\\Software\\Python\\Python37', 'D:\\Software\\Python\\Python37\\lib\\site-packages']
Traceback (most recent call last):
  File "D:\Software\Python\Python37\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "D:\Software\Python\Python37\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "pkg\__main__.py", line 15, in <module>
    import pkg
ModuleNotFoundError: No module named 'pkg'

  

E:>python -m pkg
__init__
__init__.__name__ pkg
__init__.__package__ pkg
sys.path ['E:\\Python_Web\\day_learning', 'D:\\Software\\Python\\Python37\\python37.zip', 'D:\\Software\\Python\\Python37\\DLLs', 'D:\\Software\\Python\\Python37\\lib', 'D:\\Software\\Python\\Python37', 
'D:\\Software\\Python\\Python37\\lib\\site-packages'] __main__ __main__.__name__ __main__ __main__.__package__ pkg sys.path ['E:\\Python_Web\\day_learning', 'D:\\Software\\Python\\Python37\\python37.zip', 'D:\\Software\\Python\\Python37\\DLLs', 'D:\\Software\\Python\\Python37\\lib',
'D:\\Software\\Python\\Python37', 'D:\\Software\\Python\\Python37\\lib\\site-packages'] __init__.main()

  

python pkg ,调用失败原因在于sys.path 的第一个搜索路径pkg,但作为文件夹执行时package确是一个是空字符串(找不到)。

对于 python pkg 的调用方式来说,由于 __init__.py 没有被载入,python 解释器并不知道自己正在一个 Package 下面工作。默认的,python 解释器将 __main__.py 的当前路径 pkg 加入 sys.path 中,然后在这个路径下面寻找 pkg 这个模块。显然, pkg 文件夹下面并没有 pkg 这个模块,因此出错。

对于 python -m pkg 的调用方式来说,由于 __init__.py 被事先载入,此时 python 解释器已经知道了这是一个 package ,因此当前路径(空字符串)被包含在 sys.path 中。然后再调用 __main__.py ,这时 import pkg 这个包就没有问题了。

要理解这点,就要明白 __init__.py 是 python 解释器将当前文件夹作为 Package 处理的必要条件。

如果没有读取到 __init__.py ,python 就不会认为当前的文件夹是一个 Package,而只是把它当作普通文件夹来处理。

既然找到了问题原因,那么只需要把当前路径加入到 sys.path 中,就能解决这个问题。

修改后的 __main__.py 如下:

import sys

print('__main__')
print('__main__.__name__', __name__)
print('__main__.__package__', __package__)

if not __package__:
    import os
    path = os.path.join(os.path.dirname(__file__), os.pardir)
    sys.path.insert(0, path)
    del os
print('sys.path', sys.path)

import pkg
pkg.main()

  

 

 

执行 python pkg ,结果正常:

 

E:\>python pkg
__main__
__main__.__name__ __main__
__main__.__package__
sys.path ['pkg\\..', 'pkg', 'D:\\Software\\Python\\Python37\\python37.zip', 'D:\\Software\\Python\\Python37\\DLLs', 'D:\\Software\\Python\\Python37\\lib', 'D:\\Software\\Python\\Python37', 'D:\\Software\\Python\\Python37\\lib\\site-packages']
__init__
__init__.__name__ pkg
__init__.__package__ pkg
sys.path ['pkg\\..', 'pkg', 'D:\\Software\\Python\\Python37\\python37.zip', 'D:\\Software\\Python\\Python37\\DLLs', 'D:\\Software\\Python\\Python37\\lib', 'D:\\Software\\Python\\Python37', 'D:\\Software\\Python\\Python37\\lib\\site-packages']
__init__.main()

  

看到这里,有人可能会提出两个问题:

1. 为什么不直接在 sys.path 前面加上一个空字符串来解决问题呢? 

答:因为,如果不是在当前路径下调用,空字符串就没效果了,比如执行 python /path/to/pkg。


2. 为什么不用 if __package__ == '' 来判断 __package__ 的值呢?

答:这并不是偷懒。因为可能会出现这种调用(不推荐): python pkg/__main__.py 。而这种情况下, __package__ 的值就是 None 而不是 '' 了。

 零零散散的终于彻底理解了,高兴

转载:https://blog.csdn.net/ywcpig/article/details/51179547

posted @ 2019-04-23 18:19  (时光)光阴飞逝  阅读(4212)  评论(0编辑  收藏  举报