__main__.py 和 __init__.py

原文: http://zengrong.net/post/2192.htm

  • 本站文章除注明转载外,均为本站原创或者翻译。
  • 本站文章欢迎各种形式的转载,但请18岁以上的转载者注明文章出处,尊重我的劳动,也尊重你的智商;
  • 本站部分原创和翻译文章提供markdown格式源码,欢迎使用 文章源码 进行转载;
  • 本文标题:main.py 和 init.py
  • 本文链接: http://zengrong.net/post/2192.htm

本文基于 python 3.4 ,在 python 2.7 上也可以使用。

在昨天的文章 在 setuptools 中使用 dependency_links 里,我提到了发布hhlb工具集的工作。今天又遇到了一些具体的问题。

hhlb 的文件夹结构如下:

.
├── MANIFEST.in
├── README.rst
├── hhlb
│   ├── __init__.py
│   ├── __main__.py
│   ├── admin.py
│   ├── base.py
│   ├── bin
│   ├── build.conf
│   ├── config.py
│   ├── init.py
│   ├── res.py
│   ├── templ.py
│   └── update.py
├── requirements.txt
├── setup.py
└── test
    └── testbuild.py

hhlb 是一个 python package。最初的做法,我在 hhlb 的同级目录中放了一个 build.py 文件,在这个文件中 import hhlb 这个 package ,使用其功能。

例如,下面的代码会强制初始化整个项目:

python build.py init -af

build.py 这个引导文件其实 没有存在的必要 。既然我要将 hhlb 发布成一个工具,我就应该让它能像这样被调用:

hhlb init -af
python -m hhlb init -af

第一种调用方法,就像 pip 一样。第二种方法,就像 http.server 一样。

另外,在开发过程中,还可以直接通过指定 hhlb 文件夹路径的方式来调用:

python /path/to/hhlb init -af

1. 调用顺序

为了实现上面的功能,我们先需要了解一下 python 对于 package 的调用顺序。

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

如果你需要 python 讲一个文件夹作为 package 执行,那么这个文件夹中必须包含一个名为 __main__.py 的文件,当执行 python -m hhlb 或者 python hhlb 的时候,这个文件中的代码都会被执行。参见: main — Top-level script environment

让我们做个试验。

hhlb/__init__.py 中写入如下内容:

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

hhlb/__main__.py 中写入如下内容:

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

执行 python hhlb ,打印如下:

-> % python hhlb
__main__
__main__.__name__ __main__
__main__.__package__

这说明,将 hhlb 当作文件夹执行的时候,对于 __main__.py 来说,变量 __package__ 是一个空字符串。而 __init__.py 不会被执行。

下面看看执行 python -m hhlb 的效果:

-> % python -m hhlb
__init__
__init__.__name__ hhlb
__init__.__package__ hhlb
__main__
__main__.__name__ __main__
__main__.__package__ hhlb

当作为模块执行的时候,python 会先执行 __init__.py ,然后执行 __main__.py 。而且,前者和后者对于 __name__ 变量的理解是不同的。

2. 定义一个 main()

对于一个 package 来说,既然 __init__.py 必须存在,而且当作为模块执行的时候,它会先执行,我们就应该把入口函数 main() 定义在 __init__.py 中。

当我们使用模块方式 -m 调用包的时候, __init__.py 定义了 main() 函数,然后在 __main__.py 中调用它,就能实现我们统一入口的目的。

那么继续试验。

hhlb/__init__.py 修改成这样:

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

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

hhlb/__main__.py 中对 main() 进行调用:

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

import hhlb
hhlb.main()

注意,在 __main__.py 中,没有必要判断 __name__ 是否为 __main__ ,因为无论如何调用, __name__ 的值都一定是 __main__

执行 python -m hhlb ,调用正常:

-> % python -m hhlb
__init__
__init__.__name__ hhlb
__init__.__package__ hhlb
__main__
__main__.__name__ __main__
__main__.__package__ hhlb
__init__.main()

执行 python hhlb ,调用失败:

-> % python hhlb
__main__
__main__.__name__ __main__
__main__.__package__
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/runpy.py", line 171, in _run_module_as_main
    "__main__", mod_spec)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "hhlb/__main__.py", line 7, in <module>
    import hhlb
ImportError: No module named 'hhlb'

发生了什么事?

3. sys.path

从上面的试验看出,不同的调用顺序,得到的结果完全不同。

究其原因,还是调用顺序的问题。把下面这段代码分别加入 __init__.py__main__.py 即可了解原因:

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

在使用 python hhlb 调用的时候, sys.path 的内容如下(省略了报错信息):

-> % python hhlb
__main__
__main__.__name__ __main__
__main__.__package__
__main__.sys.path ['hhlb', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python34.zip', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/plat-darwin', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/lib-dynload', '/Users/zrong/pythonenv/hhl/lib/python3.4/site-packages']

在使用 python -m hhlb 调用的时候, sys.path 的内容如下:

-> % python -m hhlb
__init__
__init__.__name__ hhlb
__init__.__package__ hhlb
__init__.sys.path ['', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python34.zip', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/plat-darwin', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/lib-dynload', '/Users/zrong/pythonenv/hhl/lib/python3.4/site-packages']
__main__
__main__.__name__ __main__
__main__.__package__ hhlb
__main__.sys.path ['', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python34.zip', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/plat-darwin', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/lib-dynload', '/Users/zrong/pythonenv/hhl/lib/python3.4/site-packages']
__init__.main()

可以看出, sys.path 的第一个搜索路径,一个是 hhlb ,一个是空字符串。

对于 python -m hhlb 的调用方式来说,由于 __init__.py 被事先载入,此时 python 解释器已经知道了自己就是一个 package ,因此当前路径被包含在 sys.path 中。然后再调用 __main__.py ,这时 import hhlb 这个包就毫无压力了。

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

要理解这点,就要明白 __init__.py 是 python 解释器将当前文件夹作为 package 处理的必要条件。如果没有读取到 __init__.py ,python 就不会认为当前的文件夹是一个 package,而只是把它当作普通文件夹来处理。

4. 问题解决

既然知道了问题原因,解决的方法也就立刻浮现了。

我们只需要把当前路径加入到 sys.path 中,就能解决这个问题。

最终的 __main__.py 是这个样子的(删除了 print):

import sys
import os

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

import hhlb
hhlb.main()

QA

  1. 为什么不像上面提到的那样,直接在 sys.path 前面加上一个空字符串来解决问题呢?
    因为,如果不是在当前路径下调用,空字符串就没效果了: python /path/to/hhlb
  2. 为什么不写 if __package__ == '' 来判断 __package__ 的值呢?这样应该更准确。
    这并不是偷懒。因为你可能会变态到使用这种方法调用: python hhlb/__main__.py 。而这种情况下, __package__ 的值就是 None 而不是 '' 了。

5. 参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容