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 而不是 '' 了。
零零散散的終於徹底理解了,高興