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 而不是 '' 了。
零零散散的终于彻底理解了,高兴