簡單的說,一個python文件就是一個模塊,本文主要介紹以下3點:
-
模塊的建立及導入
-
包的建立及導入
-
發佈和安裝自定義模塊
模塊的建立及導入
我們在寫c,或者c++時候,爲了複用代碼,總是將一系列相關的函數寫在一個.c
文件中,或者封裝一個類寫在一個.cpp
文件中,方便其他程序調用。
python這裏的模塊起到同樣的作用,我們可以把實現相同任務的一些類和函數寫在一個.py
文件中,稱之爲一個模塊,方便其它程序調用。
新建一個模塊
新建一個模塊,也就是新建一個.py
文件。
我們新建一個模塊utils.py
如下:
# utils.py test1和test2是我希望被別人重複利用的代碼
def test1():
print('-----test1-----')
def test2():
print('-----test2-----')
導入一個模塊
導入一個模塊常用的幾種方式如下:
# 第一種
import utils
utils.test1()
# 第二種
from utils import test1
test1()
# 第三種
from utils import test1, test2
test1()
test2()
# 第四種
import utils as u
u.test1()
# 第五種,這種方式建議少用
from utils import *
test1()
test2()
上述方式都可以完成模塊的導入,但是最後一種方式不建議使用。因爲
如果當兩個模塊中有同名的函數時,後面導入的文件會覆蓋掉前面的文件,這是就很難分清到底使用的是哪個模塊中的方法了。
舉個實際中的例子:
import os
import sys
import numpy as np
from skimage.io import imread, imsave
無論是新建一個模塊和導入一個模塊看起來都不難,接下來介紹一些需要注意的事情。
模塊中的__name__
當一個程序導入另一個模塊時,實際上會把這個模塊從頭到尾執行一遍。
這樣就帶來個一個問題:一般情況下,我在模塊中爲了測試每個函數的功能是否正常,需要添加一些測試的程序,如果不把這些程序去除,在被其它程序導入時就會執行了這些程序,顯然這是我們不希望看到的。
__name__
的使用在不刪除模塊內測試代碼的前提下解決了上述問題。我們先來做個測試,在utils.py
模塊中打印一個變量__name__
如下:
# utils.py
def test1():
print('-----test1-----')
def test2():
print('-----test2-----')
print(__name__)
# main.py
import utils # 注意,這裏我除了導入模塊之外其他什麼也沒做
# 由於導入模塊時會把整個模塊文件執行一遍,因此會直接調用print(__name__)這句話。
當我執行如下兩條命令時,請注意__name__
的值是如何變化的:
>>> python utils.py
>>> __main__
>>>
>>> python main.py
>>> utils
通過上述執行過程我們可以看出,當執行utils.py
文件時,__name__
的值爲'_main__'
,當執行main.py
時,utils.py
裏面__name__
的值爲'utils'
。因此我們只需要在utils.py
的測試代碼中加上如下代碼即可:
# utils.py
def test1():
print('-----test1-----')
def test2():
print('-----test2-----')
if __name__ == '__main__':
test1()
print(__name__)
上述判斷語句添加完之後,當再有其他程序導入utils
模塊時,就不會執行utils
裏面測試代碼的部分了。當直接執行utils.py
文件時,纔會執行測試代碼的部分。
我們總結一下,一般來說,一個標準一點的python
程序具有如下的格式:
import xxxx
class ClassName(object):
def __init__():
pass
def func1():
pass
def
def func():
pass
def func2():
pass
def main():
pass
if __name__ == '__main__':
main() #注意這裏的main()和c語言中的不一樣,只是借用了C語言中的語境,表達一個程序的開始而已。
模塊中的__all__
再回顧一下我們的utils.py
模塊
# utils.py test1和test2是我希望被別人重複利用的代碼
def test1():
print('-----test1-----')
def test2():
print('-----test2-----')
上述模塊在使用from utils import *
的方式被導入時候,所有的函數都會被導入,如果我不希望部分函數被外部使用,而只是公開我想要給大家使用的代碼,可以使用__all__
變量來完成,其使用方式如下:
# utils.py
__all__ = ['TestClass', 'test1'] # 這裏把函數,類的名字當成一個元素放在列表裏。
# 當其他函數使用from utils import *時,只能訪問到`__all__`變量裏面的內容
# 這裏test2函數就不能被其他模塊使用了
class TestClass(object):
pass
def test1():
print('-----test1-----')
def test2():
print('-----test2-----')
if __name__ == '__main__':
pass
此時當main.py
再次調用utils
模塊時候,就不能訪問test2
函數了。測試如下:
In [1]: from utils import *
In [2]: test1()
-----test1--------
In [3]: test2()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-3-35ebc1c532fc> in <module>()
----> 1 test2()
NameError: name 'test2' is not defined
In [4]: TestClass()
Out[4]: <utils.TestClass at 0x7fcf5055ae90>
一個需要注意的地方是:
__all__
變量只用來約束from xxx import *
的形式,而其他形式還是可以正常調用的。
如:
In [10]: from utils import test2
In [11]: test2()
------test2--------
包的建立及導入
如果想快速瞭解使用方法,請直接看總結部分。
包的建立
爲了將具有相關聯功能的模塊放在一起管理,將這些模塊放在同一個文件夾下。
比如有sengMsg.py
和revMsg.py
兩個模塊,由於他們都是和消息相關的模塊,因此我用msg
文件夾來把它們放在一起。如下:
msg/
├── revMsg.py
└── sendMsg.py
此時我們還不能直接通過import
的方式直接使用msg這兩個模塊。比如import msg
是無法使用裏面的模塊的。
注意:
在python3
中,直接調用import msg
不會出錯,但是卻無法導入模塊並使用模塊裏面的功能。
# python3
In [1]: import msg
In [2]: msg.revMsg.test1()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-2-4c1d6211c490> in <module>()
----> 1 msg.revMsg.test1()
AttributeError: module 'msg' has no attribute 'revMsg'
在
python2
中,即使是調用import msg
也會直接報錯,因爲它們都將msg
當做一個普通的文件夾處理
# python2
[1]: import msg
---------------------------------------------------------------------------
ImportError Traceback (most recent call last)
<ipython-input-12-40dac918f9d8> in <module>()
----> 1 import msg
ImportError: No module named msg
__init__.py文件
爲了解決上述問題,需要在msg
文件夾下新建一個__init__.py
,告訴python解釋器不要把msg
當成一個普通的文件夾處理,而當做一個包對待。
注意:
python3
在沒有__init__.py
文件時已經默認msg
是一個包,因此上面一段話僅對python2
來說有用。
此時我們可以認爲含有一個__init__.py
文件,並且具有很多模塊的一個文件夾,這個整體叫做包。
msg/
├── __init__.py
├── revMsg.py
└── sendMsg.py
此時,我們就可以直接導入裏面的模塊了。
In [1]: from msg import revMsg
In [2]: revMsg.test1()
----test1 revMsg----
但是如果通過如下方式導入時即
In [1]: import msg
In [2]: msg.sengMsg.test1()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-2-be5fc6eec2f7> in <module>()
----> 1 msg.sengMsg.test1()
AttributeError: 'module' object has no attribute 'sengMsg'
還是無法導入。和模塊一樣,當一個包被導入的時候,裏面的__init__.py
文件會被執行一遍,因此需要在裏面添加如下內容,從而使得我們能夠去除掉述導包時出現的錯誤:
# __init__.py
from . import (sendMsg, revMsg) # 這種方式是python2和python3兼容的,因此建議使用這種方式。
此時就可以了:
In [2]: import msg
In [3]: msg.revMsg.test1()
----test1 revMsg----
__all__變量
和模塊中的__all__
變量一樣,我們可以在__init__.py
中寫入__all__ = ['xxx', 'xxx']
的形式定義import *
時候可以使用哪些模塊。
# __init__.py
__all__ = ['revMsg']
此時通過from msg import *
方式導入模塊只能是revMsg
模塊。
注意:
__all__
變量同樣不會影響from msg.sendMsg import test1
等類似方式的調用。只會影響到import *
方式的調用。
我們總結一個
__init__.py
文件下最好有兩個內容:
__all__
變量指定from xxx import *
時候可以使用哪些模塊。from .xx import (xxx, xxxx, xxxx)
的內容,指定其他時候可以使用哪些模塊。
總結
關於模塊和包的創建以及導入我們總結如下:
-
一個
xx.py
文件被稱爲一個模塊。 -
將實現相關聯功能的模塊放在一起,再添加一個
__init__.py
文件,放在一個文件夾中,稱之爲一個包。 -
__all__
變量在模塊中可以定義導入該模塊時使用from xx import *
時導入模塊內的哪些內容。 -
__all__
變量在__init__.py
文件中可以定義使用from xx import *
時導入哪些模塊。 -
當導入模塊時,模塊的對應文件會被從頭到尾執行一遍,因此需要使用
__name__
變量的性質屏蔽掉測試代碼。 -
當導入包時,裏面的
__init__.py
文件會從頭到尾被執行一遍,因此可以在這裏加上導包的命令使得當一個程序導入該包時可以導入包裏面的模塊。 -
當然
__init__.py
也可以是一個空文件,只不過此時不能通過只導一個包的形式(import xxx
)來調用包裏面的模塊(xxx.xxx_model
).
最後我們在來看一個skimage
中的其中一個包是怎麼寫的:
# xxx/site-packages/skimage/segmentation/__init__.py
from .random_walker_segmentation import random_walker #表示從當前文件夾下的random_walker_segmentation模塊中導入random_walker函數
from .active_contour_model import active_contour
from ._felzenszwalb import felzenszwalb
from .slic_superpixels import slic
from ._quickshift import quickshift
from .boundaries import find_boundaries, mark_boundaries
from ._clear_border import clear_border
from ._join import join_segmentations, relabel_from_one, relabel_sequential
from ..morphology import watershed # 表示從上一層路徑下的morphology包中導入watershed模塊
# 當使用from segmentation import *時,下面這些函數可以被使用
__all__ = ['random_walker',
'active_contour',
'felzenszwalb',
'slic',
'quickshift',
'find_boundaries',
'mark_boundaries',
'clear_border',
'join_segmentations',
'relabel_from_one',
'relabel_sequential',
'watershed']
發佈和安裝自定義模塊
這裏只介紹一種通過setup.py
文件製作包的方式,不能通過pip
或conda
命令安裝,但是基本上對於目前的應用來說已經夠用了。因此其他方式不再介紹。
製作
-
首先確保包的目錄結構如下:
. ├── msg │ ├── __init__.py │ ├── revMsg.py │ └── sendMsg.py └── setup.py
-
然後編輯
setup.py
文件如下:from distutils.core import setup setup(name = 'xuyangcao', version = '1.0', description = 'xuyangcao\'s model', author = 'xuyangcao', py_modules = ['msg.sendMsg', 'msg.revMsg'])
-
build
執行setup.py
構建模塊,命令如下:python setup.py build
執行後其目錄結構變化如下:
. ├── build │ └── lib │ └── msg │ ├── __init__.py │ ├── revMsg.py │ └── sendMsg.py ├── msg │ ├── __init__.py │ ├── revMsg.py │ └── sendMsg.py └── setup.py
發現build 文件夾下的東西就是原始文件夾裏面內容的一個拷貝。
-
sdist
build
結束後,再執行如下命令:python setup.py sdist
運行完之後目錄結構如下:
. ├── build │ └── lib │ └── msg │ ├── __init__.py │ ├── revMsg.py │ └── sendMsg.py ├── dist │ └── xuyangcao-1.0.tar.gz ├── MANIFEST ├── msg │ ├── __init__.py │ ├── revMsg.py │ └── sendMsg.py └── setup.py
我們需要的東西就在
./dist
下面的壓縮包裏。
發佈與安裝
發佈:
將./dist/xuyangcao-1.0.tar.gz
解壓,發佈到需要的地方即可。
安裝
這個應該是比較熟悉的了,我們安裝其他第三方模塊時肯定也用過這種方式。
解壓縮壓縮包裏的文件,其目錄結構如下:
.
├── build
│ └── lib
│ └── msg
│ ├── __init__.py
│ ├── revMsg.py
│ └── sendMsg.py
├── msg
│ ├── __init__.py
│ ├── revMsg.py
│ └── sendMsg.py
├── PKG-INFO
└── setup.py
接下來就可以安裝了,直接輸入我們熟悉的命令:
python setup.py install
大功告成!
測試一下吧
In [2]: import msg
In [3]: msg.revMsg.test1()
----test1 revMsg----
成功!