導入模塊 循環導入問題 模塊


 視頻筆記

一 模塊介紹

  1、什么是模塊:

    模塊就是一系列功能的集合體,分為三大類

      I:內置的模塊
      II:第三方的模塊
      III:自定義的模塊

      ps:模塊有四種形式
      1 使用python編寫的.py文件
      2 已被編譯為共享庫或DLL的C或C++擴展
      3 把一系列模塊組織到一起的文件夾(注:文件夾下有一個__init__.py文件,該文件夾稱之為包)
      4 使用C編寫並鏈接到python解釋器的內置模塊

  2、為何要有模塊:

    I:內置與第三的模塊拿來就用,無需定義,這種拿來主義,可以極大地提升自己的開發效率

    II:自定義的模塊

      可以將程序的各部分功能提取出來放到一模塊中為大家共享使用,好處是減少了代碼冗余,程序組織結構更加清晰

  3、如何用模塊

    3.1import 模塊名  如:import foo

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

      1、執行源文件代碼

       2、產生一個新的名稱空間用於存放源文件執行過程中產生的名字

       3、在當前執行文件所在的名稱空間中得到一個名字foo,該名字指向2中產生的名稱空間

       之后的導入,都是直接引用首次導入產生的foo.py名稱空間,不會重復執行代碼

    1.引用

print(foo.x)
print(foo.get)
print(foo.change)

    強調1:模塊名.名字,是指名道姓地問某一個模塊要名字對應的值,不會與當前名稱空間中的名字發生沖突

    強調2:無論是查看還是修改操作的都是模塊本身,與調用位置無關

    2.可以以逗號為分隔符在一行導入多個模塊
      建議如下所示導入多個模塊
      import time
      import foo
      import m
      不建議在一行同時導入多個模塊
      import time,foo,m

    3.導入模塊的規范

      I. python內置模塊

      II. 第三方模塊

      III. 程序員自定義模塊

    4.模塊中的as

      import ...as ...

      import foo as f 

      f.get()

    5.自定義模塊的命名應該采用純小寫+下划線的風格

    6.可以在函數內導入模塊

      def func():

        import foo

    7.impot導入模塊在使用時必須加前綴"模塊."

    優點:肯定不會與當前名稱空間中的名字沖突

    缺點:加前綴顯得麻煩

    3.2 from-import 語句

    from...import...與import語句基本一致,唯一不同的是:使用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 ... import ...導入也發生了三件事

      1、產一個模塊的名稱空間

      2、運行foo.py將運行過程中產生的名字都丟到模塊的名稱空間去

      3、在當前名稱空間拿到一個名字,該名字與模塊名稱空間中的某一個內存地址

    from...impot...導入模塊在使用時不用加前綴

      優點:代碼更精簡

      缺點:容易與當前名稱空間混淆

    另外from語句支持from foo import 語法代表將foo中所有的名字都導入到當前位置:不推薦使用

 
from foo import * #把foo中所有的名字都導入到當前執行文件的名稱空間中,在當前位置直接可以使用這些名字

a=x
get()
change()
obj=Foo()
 

    模塊的編寫者可以在自己的文件中定義__all__變量用來控制*代表的意思

 
#foo.py
__all__=['x','get'] #該列表中所有的元素必須是字符串類型,每個元素對應foo.py中的一個名字
x=1
def get():
    print(x)
def change():
    global x
    x=0
class Foo:
    def func(self):
       print('from the func')
 

    這樣我們在另外一個文件中使用*導入時,就只能導入__all__定義的名字了

 
from foo import * #此時的*只代表x和get

x #可用
get() #可用
change() #不可用
Foo() #不可用
 

    關於import和from...import...的區別

    3.3 模塊的搜索路徑優先級

    無論是import還是from...import在導入模塊時都涉及到查找問題

    優先級:1、內存(內置模塊)2、硬盤:按照sys.path中存放的文件的順序依次查找要導入的模塊

import sys
print(sys.path)
#值為一個列表,存放了一系列的對文件夾
#其中第一個文件夾是當前執行文件所在的文件夾

    了解:sys.modules查看已經加載到內存中的模塊

 
import sys
import foo # foo=模塊的內存地址
def func():
    import foo # foo=模塊的內存地址
func()
print('foo' in sys.modules)
 

    找foo.py就把foo.py的文件夾添加到環境變量中

sys.path.append(r'路徑')

     

 

    3.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會拋出異常
正在導入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中,所以報錯
 

    測試二

 
#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並執行

 

 理論筆記

2.2 from-import 語句

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語句支持from foo import 語法,代表將foo中所有的名字都導入到當前位置

from foo import * #把foo中所有的名字都導入到當前執行文件的名稱空間中,在當前位置直接可以使用這些名字 a=x get() change() obj=Foo()

如果我們需要引用模塊中的名字過多的話,可以采用上述的導入形式來達到節省代碼量的效果,但是需要強調的一點是:只能在模塊最頂層使用的方式導入,在函數內則非法,並且的方式會帶來一種副作用,即我們無法搞清楚究竟從源文件中導入了哪些名字到當前位置,這極有可能與當前位置的名字產生沖突。模塊的編寫者可以在自己的文件中定義__all__變量用來控制*代表的意思

#foo.py __all__=['x','get'] #該列表中所有的元素必須是字符串類型,每個元素對應foo.py中的一個名字 x=1 def get(): print(x) def change(): global x x=0 class Foo: def func(self): print('from the func')

這樣我們在另外一個文件中使用*導入時,就只能導入__all__定義的名字了

from foo import * #此時的*只代表x和get x #可用 get() #可用 change() #不可用 Foo() #不可用

2.3 其他導入語法(as)

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

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

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

from foo import get as get_x get_x()

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

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) #最終調用的方式是一致的

2.4 循環導入問題

 

循環導入問題指的是在一個模塊加載/導入的過程中導入另外一個模塊,而在另外一個模塊中又返回來導入第一個模塊中的名字,由於第一個模塊尚未加載完畢,所以引用失敗、拋出異常,究其根源就是在python中,同一個模塊只會在第一次導入時執行其內部代碼,再次導入該模塊時,即便是該模塊尚未完全加載完畢也不會去重復執行內部代碼

我們以下述文件為例,來詳細分析循環/嵌套導入出現異常的原因以及解決的方案

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會拋出異常 正在導入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中,所以報錯

 

測試二

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

解決方案

# 方案一:導入語句放到最后,保證在導入時,所有名字都已經加載過 # 文件: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()

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

 

2.5 搜索模塊的路徑與優先級

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

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

2、包含一系列模塊的包

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

4、使用C或C++編譯的擴展模塊

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

>>> sys.path ['', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python35.zip', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5', ..., '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages'

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

 

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

import sys sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……) import foo #無論foo.py在何處,我們都可以導入它了

2.6 區分py文件的兩種用途

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

 

作為模塊foo.py的開發者,可以在文件末尾基於__name__在不同應用場景下值的不同來控制文件執行不同的邏輯

#foo.py ... if __name__ == '__main__': foo.py被當做腳本執行時運行的代碼 else: foo.py被當做模塊導入時運行的代碼

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

2.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() #在被當做腳本執行時,執行此處的代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM