python基礎(十九):模塊

一、模塊介紹

在Python中,一個py文件就是一個模塊,文件名爲xxx.py模塊名則是xxx,導入模塊可以引用模塊中已經寫好的功能。

將程序模塊化會使得程序的組織結構清晰,維護起來更加方便。比起直接開發一個完整的程序,單獨開發一個小的模塊也會更加簡單,並且程序中的模塊與電腦中的零部件稍微不同的是:程序中的模塊可以被重複使用。所以總結下來,使用模塊既保證了代碼的重用性,又增強了程序的結構性和可維護性。另外除了自定義模塊外,我們還可以導入使用內置或第三方模塊提供的現成功能,這種“拿來主義”極大地提高了程序員的開發效率。

二、模塊的使用

1、import語句

(1)基本用法

有如下示範文件

#文件名:foo.py
x=1
def get():
    print(x)
def change():
    global x
    x=0

要想在另外一個py文件中引用foo.py中的功能,需要使用import foo,首次導入模塊會做三件事:

  • 執行源文件代碼
  • 產生一個新的名稱空間用於存放源文件執行過程中產生的名字
  • 在當前執行文件所在的名稱空間中得到一個名字foo,該名字指向新創建的模塊名稱空間,若要引用模塊名稱空間中的名字,需要加上該前綴,如下
#本程序文件名爲test.py
import foo #導入模塊foo
a=foo.x #引用模塊foo中變量x的內存地址賦值給當前名稱空間中的名字a
foo.get() #調用模塊foo的get函數
foo.change() #調用模塊foo中的change函數

import foo 此代碼執行後的內存圖解:
在這裏插入圖片描述加上foo.作爲前綴就相當於指名道姓地說明要引用foo名稱空間中的名字,所以肯定不會與當前執行文件所在名稱空間中的名字相沖突,並且若當前執行文件的名稱空間中存在x,執行foo.get()或foo.change()操作的都是源文件中的全局變量x。

需要強調一點是,一個程序文件中第一次導入模塊已經將其加載到內存空間了,此次導入之後的重複導入會直接引用內存中已存在的模塊,不會重複執行文件,通過import sys,打印sys.modules的值可以看到內存中已經加載的模塊名。

提示:

#1、在Python中模塊也屬於第一類對象,可以進行賦值、以數據形式傳遞以及作爲容器類型的元素等操作。
#2、模塊名應該遵循小寫形式,標準庫從python2過渡到python3做出了很多這類調整,比如ConfigParser、Queue、SocketServer全更新爲純小寫形式。
(2)三種模塊
# 三種模塊的導入順序(約定俗成)
1. python內置模塊
2. 第三方模塊
3. 程序員自定義模塊

#例如
import sys
import time

import 第三方模塊1
import 第三方模塊2

import 自定義模塊1
import 自定義模塊2

​ 當然,我們也可以在函數內導入模塊,對比在文件開頭導入模塊屬於全局作用域,在函數內導入的模塊則屬於局部的作用域。

2、from … import … 語句

(1)基本用法

from…import…與import語句基本一致,唯一不同的是:使用import foo導入模塊後,引用模塊中的名字都需要加上foo.作爲前綴,而使用from foo import x,get,change,Foo則可以在當前執行文件中直接引用模塊foo中的名字,如下:

from foo import x,get,change #將模塊foo中的x和get導入到當前名稱空間
a=x #直接使用模塊foo中的x賦值給a
get() #直接執行foo中的get函數
change() #即便是當前有重名的x,修改的仍然是源文件中的x

from foo import x,get,change此代碼執行後的內存圖解:
在這裏插入圖片描述

#文件名:foo.py
x=1
def get():
    print(x)
def change():
    global x
    x=0

#文件名:test.py
from foo import x,get,change
print(x) # 執行結果:1
change() #改變x值爲0
print(x) # 執行結果:1
from foo import x
print(x) # 執行結果:0

#模塊中的x值改變,並不影響test.py中的x指向1的內存地址,這兩個x不是一個x了。除非重新導入模塊,纔會改變x的值。

無需加前綴的好處是使得我們的代碼更加簡潔壞處則是容易與當前名稱空間中的名字衝突,如果當前名稱空間存在相同的名字,則後定義的名字會覆蓋之前定義的名字。

(2)from 模塊名 import *
#foo.py
__all__=['x','get'] #該列表中所有的元素必須是字符串類型,每個元素對應foo.py中的一個名字。在列表中填入,from foo import * 允許導入的此文件的名字,默認列表中全部名字都有!
x=1
def get():
    print(x)
def change():
    global x
    x=0
class Foo:
    def func(self):
       print('from the func')

#test.py文件
from foo import * #默認是把foo中所有的名字都導入到當前執行文件的名稱空間中,在當前位置直接可以使用這些名字,但是你可以通過在模塊中改變__all__變量中列表的值,達到控制*號導入部分名字,而不是全部名字

a=x	#可用
get() #可用
change() #不可用

3、其他導入語法(as別名)

我們還可以在當前位置爲導入的模塊起一個別名

import foo as f #爲導入的模塊foo在當前位置起別名f,以後再使用時就用這個別名f
f.x
f.get()

還可以爲導入的一個名字起別名

from foo import get as get_x
get_x()

別名的優點:通常在被導入的名字過長時採用起別名的方式來1、精簡代碼,另外爲被導入的名字2、起別名可以很好地避免與當前名字發生衝突,還有很重要的一點就是:3、可以保持調用方式的一致性,例如我們有兩個模塊json和pickle同時實現了load方法,作用是從一個打開的文件中解析出結構化的數據,但解析的格式不同,可以用下述代碼有選擇性地加載不同的模塊,第3個優點的代碼解釋:

if data_format == 'json':
    import json as serialize #如果數據格式是json,那麼導入json模塊並命名爲serialize
elif data_format == 'pickle':
    import pickle as serialize #如果數據格式是pickle,那麼導入pickle模塊並命名爲serialize

data=serialize.load(fn) #最終調用的方式是一致的

4、循環導入問題

循環導入問題指的是在一個模塊加載/導入的過程中導入另外一個模塊,而在另外一個模塊中又返回來導入第一個模塊中的名字,由於第一個模塊尚未加載完畢,所以引用失敗、拋出異常,
我們以下述文件爲例,來詳細分析循環/嵌套導入出現異常的原因以及解決的方案

m1.py

print('正在導入m1')
from m2 import y

x='m1'

m2.py

print('正在導入m2')
from m1 import x

y='m2'

run.py

import m1
(1)測試一(採用執行run.py文件)
#1、執行run.py會拋出異常
正在導入m1
正在導入m2
Traceback (most recent call last):
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/aa.py", line 1, in <module>
    import m1
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module>
    from m2 import y
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in <module>
    from m1 import x
ImportError: cannot import name 'x'

#2、分析
先執行run.py--->執行import m1,開始導入m1並運行其內部代碼--->打印內容"正在導入m1"
--->執行from m2 import y 開始導入m2並運行其內部代碼--->打印內容“正在導入m2”--->執行from m1 import x,由於m1已經被導入過了,所以不會重新導入,所以直接去m1中拿x,然而x此時並沒有存在於m1中,所以報錯
(2)測試二(採用執行兩個模塊文件中其中之一)
#1、執行文件不等於導入文件,比如執行m1.py不等於導入了m1
直接執行m1.py拋出異常
正在導入m1
正在導入m2
正在導入m1
Traceback (most recent call last):
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module>
    from m2 import y
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in <module>
    from m1 import x
  File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module>
    from m2 import y
ImportError: cannot import name 'y'

#2、分析
執行m1.py,打印“正在導入m1”,執行from m2 import y ,導入m2進而執行m2.py內部代碼--->打印"正在導入m2",執行from m1 import x,此時m1是第一次被導入,執行m1.py並不等於導入了m1,於是開始導入m1並執行其內部代碼--->打印"正在導入m1",執行from m1 import y,由於m1已經被導入過了,所以無需繼續導入而直接問m2要y,然而y此時並沒有存在於m2中所以報錯

總的來說:無論是測試一還是測試二,報錯原因都一樣,就是兩個模塊相互導,因爲都想要對方導入模塊之後的名字,因此兩個模塊文件都卡在了import語句哪裏,永遠運行不到後面的變量賦值,兩個測試唯一的不同:在於直接導入模塊文件而報錯和執行模塊文件而報錯的區別

(3)循環導入報錯的解決方案
# 方案一:導入語句放到最後,保證在導入時,所有名字都已經加載過
# 文件:m1.py
print('正在導入m1')

x='m1'

from m2 import y

# 文件:m2.py
print('正在導入m2')
y='m2'

from m1 import x

# 文件:run.py內容如下,執行該文件,可以正常使用
import m1
print(m1.x)
print(m1.y)
# 方案二:導入語句放到函數中,只有在調用函數時纔會執行其內部代碼,定義階段只檢查語法問題。
# 文件:m1.py
print('正在導入m1')

def f1():
    from m2 import y
    print(x,y)

x = 'm1'

# 文件:m2.py
print('正在導入m2')

def f2():
    from m1 import x
    print(x,y)

y = 'm2'

# 文件:run.py內容如下,執行該文件,可以正常使用
import m1

m1.f1()

# 注意:函數解決方案,也有侷限性,因爲模塊在函數中導入,那麼模塊便只能局部使用。

循環導入問題大多數情況是因爲程序設計失誤導致,上述解決方案也只是在爛設計之上的無奈之舉,在我們的程序中應該儘量避免出現循環/嵌套導入,如果多個模塊確實都需要共享某些數據,可以將共享的數據集中存放到某一個地方,然後進行導入

5、搜索模塊的路徑與優先級

模塊其實分爲四個通用類別,分別是:

1.使用純Python代碼編寫的py文件

2.包含一系列模塊的包

3.使用C編寫並鏈接到Python解釋器中的內置模塊

4.使用C或C++編譯的擴展模塊
(1)模塊查詢及sys.path的介紹

在導入一個模塊時,如果該模塊已加載到內存中,則直接引用,否則會優先查找內置模塊,然後按照從左到右的順序依次檢索sys.path中定義的路徑,直到找模塊對應的文件爲止,否則拋出異常。sys.path也被稱爲模塊的搜索路徑,它是一個列表類型

>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']

列表中的每個元素其實都可以當作一個目錄來看:在列表中會發現有.zip或.egg結尾的文件,二者是不同形式的壓縮文件,事實上Python確實支持從一個壓縮文件中導入模塊,我們也只需要把它們都當成目錄去看即可

(2)sys.path的使用

sys.path中的第一個路徑通常爲空,代表執行文件所在的路徑(執行文件執行時,None被賦值成執行文件所在的路徑),所以在被導入模塊與執行文件在同一目錄下時肯定是可以正常導入的,而針對被導入的模塊與執行文件在不同路徑下的情況,爲了確保模塊對應的源文件仍可以被找到,需要將源文件foo.py所在的路徑添加到sys.path中,假設foo.py所在的路徑爲/pythoner/projects/

import sys
sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……)

import foo #無論foo.py在何處,我們都可以導入它了

6、區分py文件的兩種用途

一個Python文件有兩種用途,一種被當主程序/腳本執行,另一種被當模塊導入,爲了區別同一個文件的不同用途,每個py文件都內置了__name__變量,該變量在py文件被當做腳本執行時賦值爲“__main__”,在py文件被當做模塊導入時賦值爲模塊名

#foo.py
x=1
def get():
    print(x)
def change():
    global x
    x=0

if __name__ == '__main__':
    # foo.py被當做腳本執行時運行的代碼,一般寫測試模塊功能的代碼
	change()
	get()  
else:
    # foo.py被當做模塊導入時運行的代碼,一般這裏什麼都不用幹
    pass

通常我們會在if的子代碼塊中編寫針對模塊功能的測試代碼,這樣foo.py在被當做腳本運行時,就會執行測試代碼,而被當做模塊導入時則不用執行測試代碼。

7、編寫一個規範的模塊

我們在編寫py文件時,需要時刻提醒自己,該文件既是給自己用的,也有可能會被其他人使用,因而代碼的可讀性與易維護性顯得十分重要,爲此我們在編寫一個模塊時最好按照統一的規範去編寫,如下

#!/usr/bin/env python #通常只在類unix環境有效,作用是可以使用腳本名來執行,而無需直接調用解釋器。

"The module is used to..." #模塊的文檔描述

import sys #導入模塊

x=1 #定義全局變量,如果非必須,則最好使用局部變量,這樣可以提高代碼的易維護性,並且可以節省內存提高性能

class Foo: #定義類,並寫好類的註釋
    'Class Foo is used to...'
    pass

def test(): #定義函數,並寫好函數的註釋
    'Function test is used to…'
    pass

if __name__ == '__main__': #主程序
    test() #在被當做腳本執行時,執行此處的代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章