1. 程式人生 > >python 3 包與模塊

python 3 包與模塊

ack lpar syn 文件 shell turn hang insert return

包與模塊

一 、模塊

1 什麽是模塊?

常見的場景:一個模塊就是一個包含了python定義和聲明的文件,文件名就是模塊名字加上.py的後綴。

import加載的模塊分為四個通用類別: 

  1 使用python編寫的代碼(.py文件)

  2 已被編譯為共享庫或DLL的C或C++擴展

  3 包好一組模塊的包

  4 使用C編寫並鏈接到python解釋器的內置模塊

2 為何要使用模塊

退出python解釋器然後重新進入,那麽你之前定義的函數或者變量都將丟失,因此我們通常將程序寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。隨著程序的發展,功能越來越多,為了方便管理,我們通常將程序分成一個個的文件,這樣做程序的結構更清晰,方便管理。這時我們不僅僅可以把這些文件當做腳本去執行,還可以把他們當做模塊來導入到其他的模塊中,實現了功能的重復利用,

3、使用模塊 import 導入

示例文件:spam.py,文件名spam.py,模塊名spam

#spam.py

技術分享
print(from the spam.py)

money=1000

def read1():

    print(spam->read1->money,money)

def read2():

    print(spam->read2 calling read)

    read1()

def change():

    global money

    money=0
View Code

模塊可以包含可執行的語句和函數的定義,這些語句的目的是初始化模塊,它們只在模塊名第一次遇到導入import語句時才執行(import語句是可以在程序中的任意位置使用的,且針對同一個模塊很import多次,為了防止你重復導入,python的優化手段是:第一次導入後就將模塊名加載到內存了,後續的import語句僅是對已經加載大內存中的模塊對象增加了一次引用,不會重新執行模塊內的語句),如下

#test.py

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

import spam

import spam

import spam

‘‘‘

執行結果:

from the spam.py

‘‘‘

可以從sys.module中找到當前已經加載的模塊,sys.module是一個字典,內部包含模塊名與模塊對象的映射,該字典決定了導入模塊時是否需要重新導入。每個模塊都是一個獨立的名稱空間,定義在這個模塊中的函數,把這個模塊的名稱空間當做全局名稱空間,這樣我們在編寫自己的模塊時,就不用擔心我們定義在自己模塊中全局變量會在被導入時,與使用者的全局變量沖突

測試一:money與spam.money不沖突

#test.py

import spam

money=10

print(spam.money)

‘‘‘

執行結果:

from the spam.py

1000

‘‘‘

測試二:read1與spam.read1不沖突

#test.py

import spam

def read1():

print(‘========‘)

spam.read1()

‘‘‘

執行結果:

from the spam.py

spam->read1->money 1000

‘‘‘

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

#test.py

import spam

money=1

spam.change()

print(money)

‘‘‘

執行結果:

from the spam.py

1

‘‘‘

首次導入模塊spam時會做三件事:

1.為源文件(spam模塊)創建新的名稱空間,在spam中定義的函數和方法若是使用到了global時訪問的就是這個名稱空間。

2.在新創建的命名空間中執行模塊中包含的代碼,見初始導入import spam

1 提示:導入模塊時到底執行了什麽?

2

3 In fact function definitions are also ‘statements’ that are ‘executed’; the execution of a module-level function definition enters the function name in the module’s global symbol table.

4 事實上函數定義也是“被執行”的語句,模塊級別函數定義的執行將函數名放入模塊全局名稱空間表,用globals()可以查看

3.創建名字spam來引用該命名空間

1 這個名字和變量名沒什麽區別,都是‘第一類的’,且使用spam.名字的方式可以訪問spam.py文件中定義的名字,spam.名字與test.py中的名字來自兩個完全不同的地方。

為模塊名起別名,相當於m1=1;m2=m1

1 import spam as sm

2 print(sm.money)

#mysql.py

def sqlparse():

    print(from mysql sqlparse)

#oracle.py

def sqlparse():

    print(from oracle sqlparse)

#test.py

db_type=input(>>: )

if db_type == mysql:

    import mysql as db

elif db_type == oracle:

    import oracle as db

db.sqlparse()

示範用法二:

為已經導入的模塊起別名的方式對編寫可擴展的代碼很有用,假設有兩個模塊xmlreader.py和csvreader.py,它們都定義了函數read_data(filename):用來從文件中讀取一些數據,但采用不同的輸入格式。可以編寫代碼來選擇性地挑選讀取模塊,

例如

 if file_format == xml:

     import xmlreader as reader

 elif file_format == csv:

     import csvreader as reader

 data=reader.read_date(filename)

在一行導入多個模塊

import sys,os,re

from ... import...

對比import spam,會將源文件的名稱空間‘spam‘帶到當前名稱空間中,使用時必須是spam.名字的方式而from 語句相當於import,也會創建新的名稱空間,但是將spam中的名字直接導入到當前的名稱空間中,在當前名稱空間中,直接使用名字就可以了、

from spam import read1,read2

這樣在當前位置直接使用read1和read2就好了,執行時,仍然以spam.py文件全局名稱空間

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

#test.py

from spam import read1

money=1000

read1()

‘‘‘

執行結果:

from the spam.py

spam->read1->money 1000

‘‘‘

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

#test.py

from spam import read2

def read1():

print(‘==========‘)

read2()

‘‘‘

執行結果:

from the spam.py

spam->read2 calling read

spam->read1->money 1000

‘‘‘

如果當前有重名read1或者read2,那麽會有覆蓋效果。

測試三:導入的函數read1,被當前位置定義的read1覆蓋掉了

#test.py

from spam import read1

def read1():

print(‘==========‘)

read1()

‘‘‘

執行結果:

from the spam.py

==========

‘‘‘

需要強調的一點是:python中的變量賦值不是一種存儲操作,而只是一種綁定關系,如下:

from spam import money,read1

money=100 #將當前位置的名字money綁定到了100

print(money) #打印當前的名字

read1() #讀取spam.py中的名字money,仍然為1000

‘‘‘

from the spam.py

100

spam->read1->money 1000

‘‘‘

也支持as

from spam import read1 as read

支持導入多行

1 from spam import (read1,

2 read2,

3 money)

rom spam import * 把spam中所有的不是以下劃線(_)開頭的名字都導入到當前位置,大部分情況下我們的python程序不應該使用這種導入方式,因為*你不知道你導入什麽名字,很有可能會覆蓋掉你之前已經定義的名字。而且可讀性極其的差,在交互式環境中導入時沒有問題。

from spam import * #將模塊spam中所有的名字都導入到當前名稱空間

print(money)

print(read1)

print(read2)

print(change)

‘‘‘

執行結果:

from the spam.py

1000

<function read1 at 0x1012e8158>

<function read2 at 0x1012e81e0>

<function change at 0x1012e8268>

‘‘‘

可以使用__all__來控制*(用來發布新版本)

在spam.py中新增一行

__all__=[‘money‘,‘read1‘] #這樣在另外一個文件中用from spam import *就這能導入列表中規定的兩個名字

如果spam.py中的名字前加_,即_money,則from spam import *,則_money不能被導入考慮到性能的原因,每個模塊只被導入一次,放入字典sys.module中,如果你改變了模塊的內容,你必須重啟程序,python不支持重新加載或卸載之前導入的模塊,有的同學可能會想到直接從sys.module中刪除一個模塊不就可以卸載了嗎,註意了,你刪了sys.module中的模塊對象仍然可能被其他程序的組件所引用,因而不會被清楚。

特別的對於我們引用了這個模塊中的一個類,用這個類產生了很多對象,因而這些對象都有關於這個模塊的引用。

如果只是你想交互測試的一個模塊,使用 importlib.reload(), e.g. import importlib; importlib.reload(modulename),這只能用於測試環境。

技術分享
def func1():

    print(func1)

 

1 import time,importlib

2 import aa

3

4 time.sleep(20)

5 # importlib.reload(aa)

6 aa.func1()
View Code

把模塊當做腳本執行

我們可以通過模塊的全局變量__name__來查看模塊名:

當做腳本運行:

__name__ 等於‘__main__‘

當做模塊導入:

__name__=

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

if __name__ == ‘__main__‘:

#fib.py

技術分享
def fib(n):    # write Fibonacci series up to n

    a, b = 0, 1

    while b < n:

        print(b, end= )

        a, b = b, a+b

    print()

 

def fib2(n):   # return Fibonacci series up to n

    result = []

    a, b = 0, 1

    while b < n:

        result.append(b)

        a, b = b, a+b

    return result

 

if __name__ == "__main__":

    import sys

    fib(int(sys.argv[1]))
View Code

執行

1 #python fib.py <arguments>

2 python fib.py 50 #在命令行

模塊搜索路徑

python解釋器在啟動時會自動加載一些模塊,可以使用sys.modules查看

在第一次導入某個模塊時(比如spam),會先檢查該模塊是否已經被加載到內存中(當前執行文件的名稱空間對應的內存),如果有則直接引用,如果沒有,解釋器則會查找同名的內建模塊,如果還沒有找到就從sys.path給出的目錄列表中依次尋找spam.py文件。

所以總結模塊的查找順序是:內存中已經加載的模塊->內置模塊->sys.path路徑中包含的模塊

sys.path的初始化的值來自於:

The directory containing the input script (or the current directory when no file is specified).

PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).

The installation-dependent default.

需要特別註意的是:我們自定義的模塊名不應該與系統內置模塊重名。雖然每次都說,但是仍然會有人不停的犯錯。

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

1 >>> import sys

2 >>> sys.path.append(‘/a/b/c/d‘)

3 >>> sys.path.insert(0,‘/x/y/z‘) #排在前的目錄,優先被搜索

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

import sys

sys.path.append(‘module.zip‘)

import foo,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文件,因此一定要事先創建他們,來避免加載模塊是性能下降。

搜索路徑:

當一個命名為spam的模塊被導入時解釋器首先會從內建模塊中尋找該名字找不到,則去sys.path中找該名字sys.path從以下位置初始化執行文件所在的當前目錄PTYHONPATH(包含一系列目錄名,與shell變量PATH語法一樣)依賴安裝時默認指定的

註意:在支持軟連接的文件系統中,執行腳本所在的目錄是在軟連接之後被計算的,換句話說,包含軟連接的目錄不會被添加到模塊的搜索路徑中

在初始化後,我們也可以在python程序中修改sys.path,執行文件所在的路徑默認是sys.path的第一個目錄,在所有標準庫路徑的前面。這意味著,當前目錄是優先於標準庫目錄的,需要強調的是:我們自定義的模塊名不要跟python標準庫的模塊名重復,除非你是故意的,去你妹的。

二、包

執行文件為test.py,內容

#test.py

import aaa

同級目錄下創建目錄aaa,然後自建空__init__.py(或者幹脆建包)

需求:驗證導入包就是在導入包下的__init__.py

先執行看結果

再在__init__.py添加打印信息後,重新執行

需求:

aaa.x

aaa.y

解決:在__init__.py中定義名字x和y

在aaa下建立m1.py和m2.py

#m1.py

def f1():

print(‘from 1‘)

#m2.py

def f2():

print(‘from 2‘)

需求:

aaa.m1 #進而aaa.m1.func1()

aaa.m2 #進而aaa.m2.func2()

解決:在__init__.py中定義名字m1和m2,先定義一個普通變量,再引出如何導入模塊名

在aaa下新建包bbb

需求:

aaa.bbb

解決在aaa的__init__.py內導入名字bbb

在bbb下建立模塊m3.py

#m3.py

def f3():

print(‘from 3‘)

需求:

aaa.bbb.m3 #進而aaa.bbb.m3.f3()

解決:是bbb下的名字m3,因而要在bbb的__init__.py文件中導入名字m3

絕對導入變成相對導入

需求:

aaa.m1()

aaa.m2()

aaa.m3()

解決:在aaa的__init__.py中拿到名字m1、m2、m3

包內模塊直接的相對導入,強調包的本質:包內的模塊是用來被導入的,而不是被執行的.

用戶無法區分模塊是文件還是一個包,我們定義包是為了方便開發者維護.

將包整理當做一個模塊,移動到別的目錄下,操作sys.path.

包是一種通過使用‘.模塊名’來組織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來自倆個命名空間

glance/ #Top-level package

├── __init__.py #Initialize the glance package

├── api #Subpackage for api

│ ├── __init__.py

│ ├── policy.py

│ └── versions.py

├── cmd #Subpackage for cmd

│ ├── __init__.py

│ └── manage.py

└── db #Subpackage for db

├── __init__.py

└── models.py

#文件內容

#policy.py

def get():

    print(from policy.py)

 #versions.py

def create_resource(conf):

    print(from version.py: ,conf)

 #manage.py

def main():

    print(from manage.py)

#models.py

def register_models(engine):

    print(from models.py: ,engine)    

註意事項

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

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

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

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

import

我們在與包glance同級別的文件中測試

1 import glance.db.models

2 glance.db.models.register_models(‘mysql‘)

rom ... import ...

需要註意的是from後import導入的模塊,必須是明確的一個不能帶點,否則會有語法錯誤,如:from a import b.c是錯誤語法

我們在與包glance同級別的文件中測試

1 from glance.db import models

2 models.register_models(‘mysql‘)

3

4 from glance.db.models import register_models

5 register_models(‘mysql‘)

__init__.py文件

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

from glance.api import *

在講模塊時,我們已經討論過了從一個模塊內導入所有*,此處我們研究從一個包導入所有*。

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

#在__init__.py中定義

x=10

def func():

print(‘from api.__init.py‘)

__all__=[‘x‘,‘func‘,‘policy‘]

此時我們在於glance同級的文件中執行from glance.api import *就導入__all__中的內容(versions仍然不能導入)。

對導入和相對導入

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

絕對導入:以glance作為起始

相對導入:用.或者..的方式最為起始(只能在一個包中使用,不能用於不同目錄內)

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

在glance/api/version.py

絕對導入

from glance.cmd import manage

manage.main()

相對導入

from ..cmd import manage

manage.main()

測試結果:註意一定要在於glance同級的文件中測試

from glance.api import versions

註意:在使用pycharm時,有的情況會為你多做一些事情,這是軟件相關的東西,會影響你對模塊導入的理解,因而在測試時,一定要回到命令行去執行,模擬我們生產環境,你總不能拿著pycharm去上線代碼吧!!!

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

比如我們想在glance/api/versions.py中導入glance/api/policy.py,有的同學一抽這倆模塊是在同一個目錄下,十分開心的就去做了,它直接這麽做

比如我們想在glance/api/versions.py中導入glance/api/policy.py,有的同學一抽這倆模塊是在同一個目錄下,十分開心的就去做了,它直接這麽做

1 #在version.py中

2

3 import policy

4 policy.get()

沒錯,我們單獨運行version.py是一點問題沒有的,運行version.py的路徑搜索就是從當前路徑開始的,於是在導入policy時能在當前目錄下找到但是你想啊,你子包中的模塊version.py極有可能是被一個glance包同一級別的其他文件導入,比如我們在於glance同級下的一個test.py文件中導入version.py,如下

from glance.api import versions

‘‘‘

執行結果:

ImportError: No module named ‘policy‘

‘‘‘

‘‘‘

分析:

此時我們導入versions在versions.py中執行

import policy需要找從sys.path也就是從當前目錄找policy.py,

這必然是找不到的

‘‘‘

單獨導入包

單獨導入包名稱時不會導入包中所有包含的所有子模塊,如

在與glance同級的test.py中

import glance

glance.cmd.manage.main()

‘‘‘

執行結果:

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

‘‘‘

復制代碼

解決方法:

1 #glance/__init__.py

2 from . import cmd

3

4 #glance/cmd/__init__.py

5 from . import manage

執行:

1 #在於glance同級的test.py中

2 import glance

3 glance.cmd.manage.main()

千萬別問:__all__不能解決嗎,__all__是用於控制from...import *

python 3 包與模塊