天天看點

python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包

子產品自定義

上節說了有關子產品的知識,當時所說的子產品都是内置子產品,現在來看自己定制的子產品,即子產品也可以自定義。

子產品的自定義就是指寫一段python檔案,一般情況下裡面包含了可執行的語句和函數的定義,其實自定義的子產品和内置子產品的效果一樣,都是使以後程式的操作更加友善,不用重複敲一樣的代碼,直接導入子產品就可以使用。導入方式與内置子產品相同,import+檔案名,當然,自定義的子產品增加了許多其他的導入方式,下文我們會詳細介紹。

需要特别注意的是:我們自定義的子產品名不應該與系統内置子產品重名。

現在我們來自定義一個子產品:

#my_moudle.py

print('from the my_moudle.py')

money=1000

defread1():print('my_moudle->read1->money',money)defread2():print('my_moudle->read2 calling read1')

read1()defchange():globalmoney

money=0

接下來就是示例子產品的導入操作:

#另一個檔案

import my_moudle #隻在第一次導入時才執行my_moudle.py内代碼,此處的顯式效果是隻列印一次'from the my_moudle.py',當然其他的頂級代碼也都被執行了,隻不過沒有顯示效果.

import my_moudle#都沒被執行

import my_moudle#都沒被執行

import my_moudle#都沒被執行

'''執行結果:

from the my_moudle.py'''

注:我們可以從sys.module中找到目前已經加載的子產品,sys.module是一個字典,内部包含子產品名與子產品對象的映射,該字典決定了導入子產品時是否需要重新導入。

每一個子產品都是獨立的命名空間,程式在導入子產品的時候就已經開辟了一個獨立的名稱空間,這樣也就是說如果我們在程式的全局中給一個變量指派,但恰好子產品中也存在這個變量時,子產品中的該變量的值不會被更改,我們指派的那個變量存在于全局中,也就是說無論我們在檔案中怎麼操作,都不會改變子產品中的内容。

注:其實是有一個方式可以改變子產品中的内容的,但我們不會去那麼做,因為這意味着你辛苦建立的子產品會被别人篡改,就存在着很大的風險

下面我們進行一個小測試,自定義的子產品即上文中的my_moudle

python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包
python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包

#測試一:money與my_moudle.money不沖突#demo.py

importmy_moudle

money=10

print(my_moudle.money)#全局中的指派不會改變子產品中相同變量的值

'''執行結果:

from the my_moudle.py

1000'''

#測試二:read1與my_moudle.read1不沖突#demo.py

importmy_moudledefread1():print('========')

my_moudle.read1()#就算新定義了一個與子產品中的函數同名的函數,也不會改變什麼,隻是在全局中存在了一個新的函數

'''執行結果:

from the my_moudle.py

my_moudle->read1->money 1000'''

#測試三:執行my_moudle.change()操作的全局變量money仍然是my_moudle中的#demo.py

importmy_moudle

money=1my_moudle.change()print(money)#此時子產品中的變量值被改變,這就是改變子產品内容的方法,不推薦使用

'''執行結果:

from the my_moudle.py

1'''

View Code

子產品可以起别名,方式為import my_moudle as mmd,之後的使用中就可以用mmd來代替my_moudles

這有什麼用呢?來看下面兩種應用場景:

應用場景一:

有兩種sql子產品mysql和oracle,根據使用者的輸入,選擇不同的sql功能

#mysql.py(第一個檔案,我們定義的myspl子產品)

defsqlparse():print('from mysql sqlparse')#oracle.py(第二個檔案,我們定義的oracle子產品)

defsqlparse():print('from oracle sqlparse')#test.py(第三個檔案,是我們寫程式的檔案)

db_type=input('>>:')if db_type == 'mysql':importmysql as dbelif db_type == 'oracle':importoracle as db

db.sqlparse()#這樣做就可以在之後的程式中隻用db作為子產品名,而db的功能其實是根據使用者選擇的子產品相同#即我們用了一個類似于虛拟的名字,但卻可以根據情況不同随時變身然後行使不同的能力

應用場景二:

為已經導入的子產品起别名的方式對編寫可擴充的代碼很有用,假設有兩個子產品xmlreader.py和csvreader.py,它們都定義了函數read_data(filename):用來從檔案中讀取一些資料,但采用不同的輸入格式。可以編寫代碼來選擇性地挑選讀取子產品

if file_format == 'xml':importxmlreader as readerelif file_format == 'csv':importcsvreader as reader

data=reader.read_date(filename)#神奇吧,我們隻用reader但是卻可以發揮不同的效果

注:子產品在導入時不僅可以一行導入一個子產品,還可以一行導入多個,如:import sys,os,re

子產品導入的另一種方法:from+子產品名+import+要導入的内容

我們知道import my_moudle的導入方式,會将源檔案的名稱空間'my_moudle'帶到目前名稱空間中,目前名稱空間中有所有源檔案名稱空間的内容,并且使用時必須是my_moudle.名字的方式。而from 語句相當于import,也會建立新的名稱空間,但是是将my_moudle中import後面要導入到内容直接導入到目前的名稱空間中,在目前名稱空間中,直接使用導入到内容的名字就可以了。

下面我們來測試這種方式導入的子產品的一些特性:(與直接導入子產品有相同之處也有不同支援)

python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包
python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包

#測試一:導入的函數read1,執行時仍然回到my_moudle.py中尋找全局變量money

from my_moudle importread1

money=1000read1()'''執行結果:

from the my_moudle.py

spam->read1->money 1000

與import my_moudle的性質相同'''

#測試二:導入的函數read2,執行時需要調用read1(),仍然回到my_moudle.py中找read1()#demo.py

from my_moudle importread2def read1():#在全局中又定義的read1,但對子產品無影響

print('==========')

read2()'''執行結果:

from the my_moudle.py

my_moudle->read2 calling read1

my_moudle->read1->money 1000'''

#測試三:導入的函數read1,被目前位置定義的read1覆寫掉了#demo.py

from my_moudle importread1defread1():print('==========')

read1()'''執行結果:

from the my_moudle.py

=========='''

#注意,這裡的read1被更改了,也就是說這種導入子產品的方式是有被覆寫的潛在可能的,是以我們要視需求選擇導入的方式

View Code

注:python中的變量指派不是一種存儲操作,而隻是一種綁定關系。是以變量的值被修改就是變量更改了其綁定的值,而記憶體中沒有被綁定的值就會被回收,從記憶體中抹去

這種導入方法也支援取别名,也支援一行導入多個,與直接導入子產品相同

子產品的還有一種導入方式是from+子產品名+import+*,這個方式就是将子產品中除了下劃線開頭的名字的内容,其他的都導入到目前位置。當然,作為子產品的那個檔案,如果有不想被人調用的東西,在檔案中加入__all__=[不想被導入的内容,如:‘money’,‘read1’],這樣就不會被以from+子產品名+import+*的方式導入。但是!大部分情況下我們的python程式不應該使用這種導入方式,因為*你不知道你導入什麼名字,很有可能會覆寫掉你之前已經定義的名字。而且可讀性極其的差,在互動式環境中導入時沒有問題。是以啊,盡量使用上面兩種導入方式吧。

考慮到性能的原因,每個子產品隻被導入一次,放入字典sys.module中,如果你改變了子產品的内容,你必須重新開機程式,python不支援重新加載或解除安裝之前導入的子產品,

有的同學可能會想到直接從sys.module中删除一個子產品不就可以解除安裝了嗎,注意了,你删了sys.module中的子產品對象仍然可能被其他程式的元件所引用,因而不會被清除。

子產品也可以當做腳本運作,我們可以通過子產品的全局變量__name__來檢視子產品名:

當子產品做為腳本運作時:

print(__name__)#'__main__'

當做為子產品導入到目前檔案時:

print(__name__)= 子產品名

作用:用來控制.py檔案在不同的應用場景下執行不同的邏輯

deffib(n):

a, b= 0, 1

while b

a, b= b, a+bprint()if __name__ == "__main__":print(__name__)

num= input('num :')

fib(int(num))

子產品搜尋路徑

python解釋器在啟動時會自動加載一些子產品,可以使用sys.modules檢視。在第一次導入某個子產品時(比如my_moudle),會先檢查該子產品是否已經被加載到記憶體中(目前執行檔案的名稱空間對應的記憶體),如果有則直接引用。如果沒有,解釋器則會查找同名的内模組化塊,如果還沒有找到就從sys.path給出的目錄清單中依次尋找my_moudle.py檔案。

是以總結子產品的查找順序是:記憶體中已經加載的子產品->内置子產品->sys.path路徑中包含的子產品

在初始化後,python程式可以修改sys.path,路徑放到前面的優先于标準庫被加載。

importsys

sys.path.append('/a/b/c/d')

sys.path.insert(0,'/x/y/z') #排在前的目錄,優先被搜尋

注意:搜尋時按照sys.path中從左到右的順序查找,位于前的優先被查找,sys.path中還可能包含.zip歸檔檔案和.egg檔案,python會把.zip歸檔檔案當成一個目錄去處理。

#首先制作歸檔檔案:zip module.zip foo.py bar.py

importsys

sys.path.append('module.zip')importfoo,bar#也可以使用zip中目錄結構的具體位置

sys.path.append('module.zip/lib/python')#windows下的路徑不加r開頭,會文法錯誤

sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\a')

至于.egg檔案是由setuptools建立的包,這是按照第三方python庫和擴充時使用的一種常見格式,.egg檔案實際上隻是添加了額外中繼資料(如版本号,依賴項等)的.zip檔案。

需要強調的一點是:隻能從.zip檔案中導入.py,.pyc等檔案。使用C編寫的共享庫和擴充塊無法直接從.zip檔案中加載(此時setuptools等打包系統有時能提供一種規避方法),且從.zip中加載檔案不會建立.pyc或者.pyo檔案,是以一定要事先建立他們,來避免加載子產品是性能下降。

編譯Python檔案

為了提高加載子產品的速度。python解釋器會在__pycache__目錄中下緩存每個子產品編譯後的版本,格式為:module.version.pyc。通常會包含python的版本号。例如,在CPython3.3版本下,my_moudle.py子產品會被緩存成__pycache__/my_moudle.cpython-33.pyc。這種命名規範保證了編譯後的結果多版本共存。

Python檢查源檔案的修改時間與編譯的版本進行對比,如果過期就需要重新編譯。這是完全自動的過程。并且編譯的子產品是平台獨立的,是以相同的庫可以在不同的架構的系統之間共享,即pyc使一種跨平台的位元組碼,是由python虛拟機來執行的,但是pyc的内容跟python的版本相關,不同的版本編譯後的pyc檔案不同,2.5編譯的pyc檔案不能到3.5上執行,并且pyc檔案是可以反編譯的,因而它的出現僅僅是用來提升子產品的加載速度的。

了解部分:

python解釋器在以下兩種情況下不檢測緩存

1 如果是在指令行中被直接導入子產品,則按照這種方式,每次導入都會重新編譯,并且不會存儲編譯後的結果(python3.3以前的版本應該是這樣)

python -m my_moudle.py

2 如果源檔案不存在,那麼緩存的結果也不會被使用,如果想在沒有源檔案的情況下來使用編譯後的結果,則編譯後的結果必須在源目錄下

提示:

1.子產品名區分大小寫,foo.py與FOO.py代表的是兩個子產品

2.你可以使用-O或者-OO轉換python指令來減少編譯子產品的大小

-O轉換會幫你去掉assert語句

-OO轉換會幫你去掉assert語句和__doc__文檔字元串

由于一些程式可能依賴于assert語句或文檔字元串,你應該在在确認需要的情況下使用這些選項。

-O轉換會幫你去掉assert語句

-OO轉換會幫你去掉assert語句和__doc__文檔字元串

由于一些程式可能依賴于assert語句或文檔字元串,你應該在在确認需要的情況下使用這些選項。

3.在速度上從.pyc檔案中讀指令來執行不會比從.py檔案中讀指令執行更快,隻有在子產品被加載時,.pyc檔案才是更快的

4.隻有使用import語句是才将檔案自動編譯為.pyc檔案,在指令行或标準輸入中指定運作腳本則不會生成這類檔案,因而我們可以使用compieall子產品為一個目錄中的所有子產品建立.pyc檔案

python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包
python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包

子產品可以作為一個腳本(使用python -m compileall)編譯Python源

python-m compileall /module_directory 遞歸着編譯

如果使用python-O -m compileall /module_directory -l則隻一層

指令行裡使用compile()函數時,自動使用python-O -m compileall

詳見:https://docs.python.org/3/library/compileall.html#module-compileall

了解

附:dir()可以用來查找子產品中定義的名字,傳回一個有序字元串清單。dir()不會列舉出内建函數或者變量的名字,它們都被定義到了标準子產品builtin中,可以列舉出它們,

包其實也是子產品的一種類型,不過不同的是包不是指一個檔案,而是檔案夾的類型

包是一種通過使用“.子產品名”來組織python子產品名稱空間的方式。

1. 無論是import形式還是from...import形式,凡是在導入語句中(而不是在使用時)遇到帶點的,都要第一時間提高警覺:這是關于包才有的導入文法

2. 包是目錄級的(檔案夾級),檔案夾是用來組成py檔案(包的本質就是一個包含__init__.py檔案的目錄)

3. import導入檔案時,産生名稱空間中的名字來源于檔案,import 包,産生的名稱空間的名字同樣來源于檔案,即包下的__init__.py,導入包本質就是在導入該檔案

強調:

1. 在python3中,即使包下沒有__init__.py檔案,import 包仍然不會報錯,而在python2中,包下一定要有該檔案,否則import 包報錯

2. 建立包的目的不是為了運作,而是被導入使用,記住,包隻是子產品的一種形式而已,包即子產品

包A和包B下有同名子產品也不會沖突,如A.a與B.a來自倆個命名空間

假設我們有這麼一個包:

python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包

檔案中的内容如下:

#policy.py

defget():print('from policy.py')#versions.py

defcreate_resource(conf):print('from version.py:',conf)#manage.py

defmain():print('from manage.py')#models.py

defregister_models(engine):print('from models.py:',engine)

下面我們來學習有關包的知識點。

import和from+檔案夾的名字+import+檔案名

舉例說明:

#import

importglance.db.models

glance.db.models.register_models('mysql')#from...import...

from glance.db importmodels

models.register_models('mysql')from glance.db.models importregister_models

register_models('mysql')#需要注意的是from後import導入的子產品,必須是明确的一個不能帶點,否則會有文法錯誤,如:from a import b.c是錯誤文法

不管是哪種方式,隻要是第一次導入包或者是包的任何其他部分,都會依次執行包下的__init__.py檔案(我們可以在每個包的檔案内都列印一行内容來驗證一下),這個檔案可以為空,但是也可以存放一些初始化包的代碼。

from glance.api import *

在講子產品時,我們已經讨論過了從一個子產品内導入所有*,此處我們研究從一個包導入所有*。

此處是想從包api中導入所有,實際上該語句隻會導入包api下__init__.py檔案中定義的名字,我們可以在這個檔案中定義__all___:

#在__init__.py中定義

x=10

deffunc():print('from api.__init.py')__all__=['x','func','policy']#policy是與__init__同級的檔案,這裡引入policy但不引入version是為了舉例說明

#此時我們在于glance同級的檔案中執行from glance.api import *就導入__all__中的内容(versions仍然不能導入)。

注意:

1.關于包相關的導入語句也分為import和from ... import ...兩種,但是無論哪種,無論在什麼位置,在導入時都必須遵循一個原則:凡是在導入時帶點的,點的左邊都必須是一個包,否則非法。可以帶有一連串的點,如item.subitem.subsubitem,但都必須遵循這個原則。

2.對于導入後,在使用時就沒有這種限制了,點的左邊可以是包,子產品,函數,類(它們都可以用點的方式調用自己的屬性)。

3.對比import item 和from item import name的應用場景:

如果我們想直接使用name那必須使用後者。

絕對導入和相對導入

我們的最頂級包glance是寫給别人用的,然後在glance包内部也會有彼此之間互相導入的需求,這時候就有絕對導入和相對導入兩種方式

絕對導入:以glance作為起始

相對導入:用.或者..的方式最為起始,同級隻用一個.就可以,越級就要用..了(隻能在一個包中使用,不能用于不同目錄内)

舉個栗子

#我們在glance/api/version.py中想要導入glance/cmd/manage.py

#在glance/api/version.py

#絕對導入

from glance.cmd importmanage

manage.main()#相對導入

from ..cmd importmanage

manage.main()

注:在使用pycharm時,有的情況會為我們多做一些事情,這是軟體相關的東西,會影響我們對子產品導入的了解,因而在測試時,一定要回到指令行去執行,模拟我們生産環境

特别需要注意的是:可以用import導入内置或者第三方子產品(已經在sys.path中),但是要絕對避免使用import來導入自定義包的子子產品(沒有在sys.path中),應該使用from... import ...的絕對或者相對導入,且包的相對導入隻能用from的形式。

這裡利用圖檔對絕對導入和相對導入做一份相對詳細的解釋:

python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包
python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包

單獨導入包

單獨導入包名稱時不會導入包中所有包含的所有子子產品,解決的方法是在包的子檔案夾中的子檔案寫入導的過程,如:

#在與glance同級的test.py中

importglance

glance.cmd.manage.main()'''執行結果:

AttributeError: module 'glance' has no attribute 'cmd''''

#解決方法#glance/__init__.py

from . importcmd#glance/cmd/__init__.py

from . importmanage#在于glance同級的test.py中執行時就可以用了

importglance

glance.cmd.manage.main()

有關__all__和from...import*的使用方法

python中的子產品和包是什麼如何自定義并使用_what's the python之自定義子產品和包