聲明:如果你每天寫Python,你會發現這篇文章中沒有新東西。 這是專為那些像運維人員等偶爾使用Python的人以及那些忘記/誤用python import的人寫的。 盡管如此,代碼是用Python 3.6類型注釋編寫的,以滿足有經驗的Python讀者。 像往常一樣,如果你發現任何錯誤,請告訴我!
模塊
我們從一個常見的python代碼開始
if __name__ == '__main__': invoke_the_real_code()
很多人,我也不例外,把它當成固定格式,而不去深入理解它。 我們已經知道一點,當從CLI調用你的代碼而不是導入它時,這個代碼片段會有所不同。 現在讓我們試著去理解我們為什么需要用它。
為了說明,假設我們正在編寫一款披薩店軟件。 源碼在Github上。 這是pizza.py文件。
# pizza.py fileimport mathclass Pizza: name: str = '' size: int = 0 price: float = 0 def __init__(self, name: str, size: int, price: float) -> None: self.name = name self.size = size self.price = price def area(self) -> float: return math.pi * math.pow(self.size / 2, 2) def awesomeness(self) -> int: if self.name == 'Carbonara': return 9000 return self.size // int(self.price) * 100print('pizza.py module name is %s' % __name__)if __name__ == '__main__': print('Carbonara is the most awesome pizza.')
我已經添加了打印__name__變量的代碼,以便了解__name__是如何變化的。
$ python3 pizza.pypizza.py module name is __main__Carbonara is the most awesome pizza.
的確,全局變量__name__在從CLI調用的時候設置成了“__main__”。
可是如果從另外一個文件中引用它會怎么樣呢?以下是menu.py的源碼:
# menu.py filefrom typing import Listfrom pizza import PizzaMENU: List[Pizza] = [ Pizza('Margherita', 30, 10.0), Pizza('Carbonara', 45, 14.99), Pizza('Marinara', 35, 16.99),]if __name__ == '__main__': print(MENU)
運行menu.py
$ python3 menu.pypizza.py module name is pizza[
接著看看下面兩點:
pizza.py代碼中的第一條打印語句在import的時候執行了。
pizza.py代碼中的全局變量__name__設置成了沒有.py后綴的文件名。
所以,事實是,__name__是保存當前Python模塊名稱的全局變量。
模塊名稱由解釋器在__name__變量中設置
當從CLI調用模塊時,其名稱被設置為__main__
那么到底什么是模塊呢? 這非常簡單 - 模塊是一個包含Python代碼的文件,可以使用解釋器(python程序)執行或從其他模塊導入。
Python模塊只是一個包含Python代碼的文件
就像執行時一樣,當模塊被導入時,它的頂級語句也會被執行,但是要知道,即使從不同的文件中導入它幾次,它也只會被執行一次。
當你導入模塊時,它會被執行
因為模塊只是純文件,所以有一個簡單的方法來導入它們。 只取文件名,去掉.py擴展名并將其放入import語句中。
要導入模塊,請使用不帶.py擴展名的文件名
有趣的是,__name__被設置為文件名,無論你如何導入它 - 例如import pizza as broccoli,__name__仍然是pizza。 所以
導入時,即使使用import module as othername將模塊名稱重命名,模塊名稱仍然設置為不帶.py擴展名的文件名
但是如果導入的模塊不在同一個目錄下,我們怎么導入呢? 答案是放在模塊搜索路徑中,我們最終會在討論包時研究它。
包
包是模塊集合的名稱空間
命名空間部分很重要,因為它本身并不提供任何功能 - 它只是給你一個組合你所有模塊的方式。
兩種情況下,你需要把模塊放入一個包中。 首先是隔離一個模塊的定義。 在我們的pizza模塊中,我們有一個可能與其他Pizza包相沖突的Pizza類(我們在pypi上有一些pizza包)
第二種情況是,如果你想分發你的代碼,因為
包是Python中最小的代碼分發單元
你在PyPI上看到的所有東西都是通過pip安裝的,所以為了分享你的東西,你必須把它做成一個包。
好吧,假設我們確信并想將我們的2個模塊轉換成一個很好的包。 要做到這一點,我們需要創建一個包含一個空的__init__.py文件的目錄,并將我們的文件移入該目錄:
pizzapy/├── __init__.py├── menu.py└── pizza.py
就是這樣 - 現在你有一個比薩餅包!
要創建一個包,創建一個包含__init__.py文件的目錄
請記住,程序包是模塊的名稱空間,因此您不會導入包本身,而是從包中導入模塊。
>>> import pizzapy.menupizza.py module name is pizza>>> pizzapy.menu.MENU[
如果以這種方式進行導入,則可能看起來過于冗長,因為您需要使用完全限定的名稱。 我猜這是有意為之,因為Python宗旨之一是“明確比隱含更好”。
無論如何,你總是可以使用from package import module的格式來縮短名稱:
>>> from pizzapy import menupizza.py module name is pizza>>> menu.MENU[
包初始化
還記得我們如何把一個__init__.py文件放在一個目錄中,這個目錄就神奇地變成了一個包嗎?這是一個很好的慣例配置示例,我們不需要描述任何配置或注冊任何東西。約定包含__init__.py的任何目錄都是Python包。
除了標識一個包,__init__.py還有一個目的 - 包初始化。這就是為什么它被稱為init!初始化是在包導入時觸發的,換句話說,導入包時調用__init__.py
當你導入一個包時,包內的__init__.py模塊被執行
在__init__模塊中,你可以做任何你想做的事情,但最常用的是用于一些包初始化或設置專用的__all__變量。后者控制*(通配符)導入 - from package import *。
而且因為Python很棒,我們可以在__init__模塊中做很多事情,甚至是很奇怪的事情。假設我們不喜歡顯式導入,并且希望將所有模塊符號上升到包級別,這樣我們就不必記住實際的模塊名稱。
為此,我們可以在__init__.py中像這樣導入menu和pizza模塊中的所有東西
# pizzapy/__init__.pyfrom pizzapy.pizza import *from pizzapy.menu import *
看看運行結果:
>>> import pizzapypizza.py module name is pizzapy.pizzapizza.py module name is pizza>>> pizzapy.MENU[
沒有更多的pizzapy.menu.Menu或menu.MENU :-)這種方式有點像Go中的軟件包,但請注意,你正試圖濫用Python,不鼓勵這樣做,因為在你要代碼檢查時,會讓你抓狂的。 別怪我哦,我只是為了舉例說明!
您可以像這樣更簡潔地重寫導入
# pizzapy/__init__.pyfrom .pizza import *from .menu import *
這只是另一種做同樣事情的語法,就是所謂的相對導入。 我們來仔細看看。
絕對和相對導入
上面的2個代碼段是做所謂的相對導入的唯一方法,因為自Python 3開始,所有導入都默認為絕對導入(如在PEP328中),這意味著導入將嘗試首先導入標準模塊,然后才導入本地包。 在創建自己的sys.py模塊時,需要避免使用標準模塊的名稱,因為import sys可以覆蓋標準庫sys模塊。
自Python 3開始,所有導入都默認為絕對導入 - 它將首先查找系統包
但是如果你的軟件包有一個名為sys的模塊,并且你想把它導入到同一個包內的另一個模塊中,你必須做相對的導入。 要做到這一點,你必須再次明確的這樣寫package.module import somesymbol或from .module import somesymbol。 模塊名稱之前的那個有趣的點理解為“當前包”。
要進行相對導入,請在模塊名前加上程序包名稱或點
可執行程序包
在Python中,您可以使用python3 -m
$ python3 -m pizzapizza.py module name is __main__Carbonara is the most awesome pizza.
然而也可以這樣調用:
$ python3 -m pizzapy/usr/bin/python3: No module named pizzapy.__main__; 'pizzapy' is a package and cannot be directly executed
如你所看到的,這需要一個__main__模塊,因此要先實現它:
# pizzapy/__main__.pyfrom pizzapy.menu import MENUprint('Awesomeness of pizzas:')for pizza in MENU: print(pizza.name, pizza.awesomeness())
現在可以正常使用了:
$ python3 -m pizzapypizza.py module name is pizzaAwesomeness of pizzas:Margherita 300Carbonara 9000Marinara 200
添加__main__.py使包可執行(使用python3 -m package調用它)
導入兄弟包
而我想要涵蓋的最后一件事是導入兄弟包。 假設我們有一個兄弟包pizzashop:
.├── pizzapy│ ├── __init__.py│ ├── __main__.py│ ├── menu.py│ └── pizza.py└── pizzashop ├── __init__.py └── shop.py
# pizzashop/shop.pyimport pizzapy.menuprint(pizzapy.menu.MENU)
現在,位于頂層目錄下,如果我們試圖像這樣調用shop.py
$ python3 pizzashop/shop.pyTraceback (most recent call last): File "pizzashop/shop.py", line 1, in
我們得到了找不到pizzapy模塊的錯誤。 但是,如果我們把它作為包的一部分來調用它
$ python3 -m pizzashop.shoppizza.py module name is pizza[
它能正常工作了。 這到底是怎么回事?
對此的解釋原因在于Python模塊的搜索路徑,在模塊文檔中有很詳細的描述。
模塊搜索路徑是解釋器用于查找模塊的目錄(在運行時可用sys.path得到)的列表。 它通過Python標準模塊(/usr/lib64/python3.6)的路徑進行初始化,site-packages是pip放置全局安裝的所有內容的地方,也是一個依賴如何運行模塊的目錄。 如果將模塊像這樣python3 pizzashop/shop.py作為一個文件運行,則將包含目錄(pizzashop)的路徑添加到sys.path中。 另外,使用-m選項運行時,當前目錄(如在pwd中)被添加到模塊搜索路徑。 我們可以通過在pizzashop/shop.py中打印sys.path來檢查它:
$ pwd/home/avd/dev/python-imports$ tree.├── pizzapy│ ├── __init__.py│ ├── __main__.py│ ├── menu.py│ └── pizza.py└── pizzashop ├── __init__.py └── shop.py$ python3 pizzashop/shop.py['/home/avd/dev/python-imports/pizzashop', '/usr/lib64/python36.zip', '/usr/lib64/python3.6', '/usr/lib64/python3.6/lib-dynload', '/usr/local/lib64/python3.6/site-packages', '/usr/local/lib/python3.6/site-packages', '/usr/lib64/python3.6/site-packages', '/usr/lib/python3.6/site-packages']Traceback (most recent call last): File "pizzashop/shop.py", line 5, in
正如你在第一種情況中可以看到的,我們在路徑中有pizzashop dir,所以我們找不到兄弟包pizzapy,而在第二種情況下,當前dir(表示為"")在sys.path中并且包含兩個包。
Python的模塊搜索路徑在運行時可作為sys.path
如果將模塊作為腳本文件運行,則將包含該模塊的目錄添加到sys.path中,否則,會將當前目錄添加到sys.path中
當人們將一堆測試或示例腳本放在主包相鄰的目錄或包中時,常常會出現導入同級包的問題。 這里有幾個StackOverflow問題:
https://stackoverflow.com/q/6323860
https://stackoverflow.com/q/6670275
好的解決方案是把測試或例子放在包里,然后使用相對的導入來避免這個問題。 差點的解決方案是在運行時修改sys.path,增加所需包的父目錄(耶,動態!)。 人們實際上這樣做,雖然這是一個糟糕的方式。
結束語
我希望閱讀這篇文章之后,你將會對Python的導入有更好的理解,并且可以最終順利地將你工具箱中的巨大腳本分解成多個部分。最后,Python中的所有東西都非常簡單,即使它不能完整地滿足你的需求,你總可以在運行時隨時修改任何內容。
目前想寫的就這些,謝謝你的關注。 接下來如何,下次分解!
-
源碼
+關注
關注
8文章
647瀏覽量
29284 -
python
+關注
關注
56文章
4799瀏覽量
84817
原文標題:Python imports指南
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論