python基礎–自定義模組、import、from……import……

自定義模組、import、from……import……

1)模組的定義和分類

1.模組是什麼?

我們知道一個函數封裝了一個功能,軟體可能是有多個函數組成的。我們說一個函數就是一個功能,那麼把一些常用的函數放在一個py文件中,那麼這個文件就稱之為模組。模組就是一些列常用功能的集合體。

什麼是模組:本質就是.py文件,封裝語句的最小單位。

2.為什麼要使用模組

  1. 從文件級別組織程式,更方便管理 隨著程式的發展,功能越來越多,為了方便管理,我們通常將程式分成一個個的文件,這樣做程式的結構更清晰,方便管理。這時我們不僅僅可以把這些文件當做腳本去執行,還可以把他們當做模組來導入到其他的模組中,實現了功能的重複利用
  2. 拿來主義,提升開發效率 同樣的原理,我們也可以下載別人寫好的模組然後導入到自己的項目中使用,這種拿來主義,可以極大地提升我們的開發效率,避免重複造輪子。

ps:人們常說的腳本是什麼?

如果你退出python解釋器然後重新進入,那麼你之前定義的函數或者變數都將丟失,因此我們通常將程式寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。

所以,腳本就是一個python文件,比如你之前寫的購物車,模擬部落格園登錄系統的文件等等。

3.模組的分類

Python語言中,模組分為三類。

第一類:內置模組,也叫做標準庫。此類模組就是python解釋器給你提供的,比如我們之前見過的time模組,os模組。標準庫的模組非常多(200多個,每個模組又有很多功能),我們這幾天就講常用的十幾種,後面課程中還會陸續的講到。

第二類:第三方模組,第三方庫。一些python大神寫的非常好用的模組,必須通過pip install 指令安裝的模組,比如BeautfulSoup, Django,等等。大概有6000多個。

第三類:自定義模組。我們自己在項目中定義的一些模組。

我們先定義一個模組,定義一個模組其實很簡單就是寫一個文件,裡面寫一些程式碼(變數,函數)即可。此文件的名字為tbjx.py,文件內容如下:

print('from the tbjx.py')
name = '太白金星'

def read1():
    print('tbjx模組:',name)

def read2():
    print('tbjx模組')
    read1()

def change():
    global name
    name = 'barry'

2)自定義模組

自定義模組:定義一個模組其實很簡單就是寫一個文件,裡面寫一些程式碼(變數,函數)即可。此文件的名字為tbjx.py,文件內容如下:
模組中出現的變數,for循環,if結構,函數定義。。。。稱為模組的成員。

自定義模組:實際上就是定義.py文件,其中可以包含:變數定義,可執行語句,for循環,函數定義等等,他們
統稱模組的成員

模組的運行方式:
1.腳本方式:python xxx.py(直接用解釋器執行) 或者在pycharm軟體run運行(右鍵運行)
2.模組方式:被其它的模組導入。為導入它的模組提供資源(變數,函數定義,類定義等)。

# b.py文件
# 可執行語句
a = 1
print(a)
for x in range(10):
    print(x)
# 函數的定義
def f():
    print('hello world')
f()

自定義模組被其他模組導入時,其中的可執行語句會立即執行。
但是函數的定義和類的定義等是不會立即執行的。

但是我們在實際的開發的過程中,不是我們import模組就執行了。

而是在實際開發的時候用到什麼就執行什麼。

所以在模組中,我們一般不會寫可執行語句,而是寫變數的定義,函數定義和類定義等不會立即執行的語句。

我們在實際的時候,不能直接可執行語句,只有變數、函數、類定義等等。被其它的模組導入。為導入它的模組提供資源(變數,函數定義,類定義等)。

# test_import.py文件
import b
# 輸出的結果為:
'''
1
0
1
2
3
4
5
6
7
8
9
hello world
'''
# import bb # ModuleNotFoundError: No module named 'bb'

我們再看一下下面這個例子,我們沒有寫可執行語句,而是變數、函數、類等的定義,不會再import時,就直接執行。

# b.py文件
# 可執行語句
a = 1
# 函數的定義
def f():
    print('hello world')
import b
print(b.a)
b.f()
print(b.f())
'''
輸出的結果為:
1
hello world
hello world
None
'''

python中提供一種可以判斷自定義模組是屬於開發階段還是使用階段。’name

系統導入模組的路徑

1.記憶體中:如果之前成功導入過某個模組,直接使用已經存在的模組

2.內置路徑中:安全路徑下:Lib

PYTHONPATH:import是尋找模組的路徑

3.sys.path:是一個路徑的列表

如果三個都找不到,就報錯。

動態修改sys.path

os.path.dirname():獲取到某一個文件的父路徑。

通常獲取當前腳本(模組)的相對位置,可以獲取到每一個文件。

# 查看sys.path內容
# import sys
# print(sys.path)

# 添加b.py所在的路徑到sys.path中
# import sys
# sys.path.append(r'D:\Program Files (x86)\DjangoProjects\basic\day15\bbb')
# import bb
# print(bb.a) # 輸出的結果為:get it
# # 使用相對位置找到bbb文件夾中的bb
# print(__file__) # 獲取當前文件的絕對路徑;D:/Program Files (x86)/DjangoProjects/basic/day15/test_imoirt.py
# # 使用os模組獲取一個路徑的父路徑
# import os
# print(os.path.dirname(__file__)) # 獲取當前文件的父路徑 D:/Program Files (x86)/DjangoProjects/basic/day15
# print(os.path.dirname(__file__)+r'/bbb') # D:/Program Files (x86)/DjangoProjects/basic/day15/bbb
import sys
import os
sys.path.append(os.path.dirname(__file__)+'/bbb')
a = 1
def main():
    print(a)
    for x in range(3):
        print(x)
    f()

# __name__屬性的使用。
if __name__ == '__main__':
    main()
'''
輸出的結果為:
1
0
1
2
hello world
'''

'''
__name__屬性的使用:
在腳本方式運行的時候:__name__是固定的字元串:__main__
在模組導入運行的時候,__name__就是被導入模組的名字,沒有文件的後綴名.py。
在模組方式導入時,__name__就是本模組的名字。
通過__name__屬性,我們就可以決定可執行文件中的語句該不該被執行。
'''
'''
自定義模組
'''
# age = 10
#
#
# def f1():
#     print('hello')
#
#
# # 測試函數,在開發階段,對本模組中的功能進行測試。
# # 這個測試函數一般我們是寫成main函數的形式。
# def main():
#     print(age)
#     f1()
#
#
# # 可以快速生成。
# if __name__ == '__main__':
#     main()
'''
對於一個新的py文件或者是一個新的模組時,我們一上來要寫下面兩個東西.
然後根據自己的需求,去寫這個模組對應的別的東西。例如變數的定義,函數的定義等等
'''
def main():
    pass
if __name__ == '__main__':
    main()

Python中引用模組是按照一定的規則以及順序去尋找的,這個查詢順序為:先從記憶體中已經載入的模組進行尋找找不到再從內置模組中尋找,內置模組如果也沒有,最後去sys.path中路徑包含的模組中尋找。它只會按照這個順序從這些指定的地方去尋找,如果最終都沒有找到,那麼就會報錯。

記憶體中已經載入的模組->內置模組->sys.path路徑中包含的模組

模組的查找順序

  1. 在第一次導入某個模組時(比如tbjx),會先檢查該模組是否已經被載入到記憶體中(當前執行文件的名稱空間對應的記憶體),如果有則直接引用(ps:python解釋器在啟動時會自動載入一些模組到記憶體中,可以使用sys.modules查看)
  2. 如果沒有,解釋器則會查找同名的內置模組
  3. 如果還沒有找到就從sys.path給出的目錄列表中依次尋找tbjx.py文件。

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

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

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

接下來我們就開始講解python常用的內置模組,由於Python常用的模組非常多,我們不可能將所有的模組都講完, 所以只針對於工作中經常用到模組進行講解。剩下的模組可以在課餘時間自學。

3)import使用

1.import使用

import 翻譯過來是一個導入的意思。

這裡一定要給同學強調那個文件執行文件,和哪個文件是被執行模組。

模組可以包含可執行的語句和函數的定義,這些語句的目的是初始化模組,它們只在模組名第一次遇到導入import語句時才執行(import語句是可以在程式中的任意位置使用的,且針對同一個模組很import多次,為了防止你重複導入,python的優化手段是:第一次導入後就將模組名載入到記憶體了,後續的import語句僅是對已經載入到記憶體中的模組對象增加了一次引用,不會重新執行模組內的語句),如下 import tbjx #只在第一次導入時才執行tbjx.py內程式碼,此處的顯式效果是只列印一次’from the tbjx.py’,當然其他的頂級程式碼也都被執行了,只不過沒有顯示效果.

image-20200702180526449
程式碼示例:
import tbjx
import tbjx
import tbjx
import tbjx
import tbjx

執行結果:只是列印一次:
from the tbjx.py

2.第一次導入模組執行三件事

  • 創建一個以模組名命名的名稱空間。

  • 執行這個名稱空間(即導入的模組)裡面的程式碼。

  • 通過此模組名. 的方式引用該模組裡面的內容(變數,函數名,類名等)。 這個名字和變數名沒什麼區別,都是『第一類的』,且使用tbjx.名字的方式可以訪問tbjx.py文件中定義的名字,tbjx.名字與test.py中的名字來自兩個完全不同的地方。

    ps:重複導入會直接引用記憶體中已經載入好的結果

3. 被導入模組有獨立的名稱空間

每個模組都是一個獨立的名稱空間,定義在這個模組中的函數,把這個模組的名稱空間當做全局名稱空間,這樣我們在編寫自己的模組時,就不用擔心我們定義在自己模組中全局變數會在被導入時,與使用者的全局變數衝突。

示例:

image-20200702180415720
當前是meet.py

import tbjx.py
name = 'alex'
print(name)
print(tbjx.name)
'''
from the tbjx.py
alex
太白金星
'''

def read1():
    print(666)
tbjx.read1()
'''
from the tbjx.py
tbjx模組: 太白金星
'''

name = '日天'
tbjx.change()
print(name)
print(tbjx.name)
'''
from the tbjx.py
日天
barry
'''

4.為模組起別名

1. 好處可以將很長的模組名改成很短,方便使用.

import tbjx as t
t.read1()
from xxx import xxx as xxx

2. 有利於程式碼的擴展和優化。

#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()

5.導入多個模組

我們以後再開發過程中,免不了會在一個文件中,導入多個模組,推薦寫法是一個一個導入。

import os,sys,json   # 這樣寫可以但是不推薦
推薦寫法
import os
import sys
import json

  多行導入:易於閱讀 易於編輯 易於搜索 易於維護。

4)from……import……

1.from……import……使用

from ... import ... 的使用示例。
from tbjx import name, read1
print(name)
read1()
'''
執行結果:
from the tbjx.py
太白金星
tbjx模組: 太白金星
'''

2.from…import… 與import對比

唯一的區別就是:使用from…import…則是將spam中的名字直接導入到當前的名稱空間中,所以在當前名稱空間中,直接使用名字就可以了、無需加前綴:tbjx.

from…import…的方式有好處也有壞處

好處:使用起來方便了

壞處:容易與當前執行文件中的名字衝突

示例演示:

  1. 執行文件有與模組同名的變數或者函數名,會有覆蓋效果。
name = 'oldboy'
from tbjx import name, read1, read2
print(name)  
'''
執行結果:
太白金星
'''
----------------------------------------
from tbjx import name, read1, read2
name = 'oldboy'
print(name)  

'''
執行結果:
oldboy
'''
----------------------------------------
def read1():
    print(666)
from tbjx import name, read1, read2
read1()
'''
執行結果:
tbjx模組: 太白金星
'''
----------------------------------------
from tbjx import name, read1, read2
def read1():
    print(666)
read1()
'''
執行結果:
tbjx模組: 666
'''

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

#測試一:導入的函數read1,執行時仍然回到tbjx.py中尋找全局變數 'alex'
#test.py
from tbjx import read1
name = 'alex'
read1()
'''
執行結果:
from the spam.py
spam->read1->name = '太白金星'
'''

#測試二:導入的函數read2,執行時需要調用read1(),仍然回到tbjx.py中找read1()
#test.py
from tbjx import read2
def read1():
    print('==========')
read2()

'''
執行結果:
from the tbjx.py
tbjx->read2 calling read
tbjx->read1->tbjx 'barry'
'''
通過這種方式引用模組也可以對模組進行改名。
from tbjx import read1 as read
read()

3.一行導入多個

from tbjx import read1,read2,name

4.from……import*

from spam import * 把tbjx中所有的不是以下劃線(_)開頭的名字都導入到當前位置

大部分情況下我們的python程式不應該使用這種導入方式,因為*你不知道你導入什麼名字,很有可能會覆蓋掉你之前已經定義的名字。而且可讀性極其的差,在互動式環境中導入時沒有問題。

可以使用all來控制*(用來發布新版本),在tbjx.py中新增一行

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

5.模組循環導入問題

模組循環/嵌套導入拋出異常的根本原因是由於在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

#測試一
執行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'


#測試一結果分析
先執行run.py--->執行import m1,開始導入m1並運行其內部程式碼--->列印內容"正在導入m1"
--->執行from m2 import y 開始導入m2並運行其內部程式碼--->列印內容「正在導入m2」--->執行from m1 import x,由於m1已經被導入過了,所以不會重新導入,所以直接去m1中拿x,然而x此時並沒有存在於m1中,所以報錯


#測試二:執行文件不等於導入文件,比如執行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'


#測試二分析
執行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中所以報錯

# 解決方法:
方法一:導入語句放到最後
#m1.py
print('正在導入m1')

x='m1'

from m2 import y

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

from m1 import x

方法二:導入語句放到函數中
#m1.py
print('正在導入m1')

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

x = 'm1'

# f1()

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

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

y = 'm2'

#run.py
import m1
m1.f1()

5)import和from……import……總結

# 導入模組的多種方式:
# import xxx導入一個模組的所有成員
# import aaa,bbb,....一次性導入多個模組的成員,不推薦這種寫法,分開寫比較好。因人而異。import os,sys等
# from xxx import aaa.. 從某個模組中導入指定的成員。最大化利用。有用就導入,沒有使用我們就不用去導入。
# from xxx import a,b,c 從某個模組中導入多個成員。
# from xxx import * 從某個模組匯總導入所有成員。
# import xxx 和 from xxx import *
# 第一種方式在使用其中成員時,必須使用模組名作為前提。不容易產生命名衝突
# 第二種方式在使用其中成員時,不用使用模組名作為前提,直接使用成員名即可。容易產生命名衝突,在後面定義的成員生效,把前面的覆蓋了。
# 怎麼解決名稱衝突的問題
# 改用import xxx 這種方式導入
# 自己避免使用同名(alias的縮寫)
# 使用別名解決衝突 from xxx import xxx as xxx
#
# 也可以給模組起別名 import my_module as m  import xxx as xxx,為了方便簡化書寫。


# from xxx import * 控制成員被導入(__all__只是適合控制這一種導入成員的方式,其餘方式都是不可以用的)
# 默認情況下,所有的成員都會被導入
# __all__是一個列表,用於表示本模組可被外界使用的成員。元素是成員名組成的字元串。
# __all__ = []
# __all__ = [
#     'age',
#     'age2'
# ]

# 相對導入:相對導入時導入的是同項目下的模組。
# 只有一種的導入的方式
# from xxx import xxx

# import os
# import sys
# # 把項目所在的父路徑加到sys.path中,python的解釋器中。os是作業系統相關的路徑。
# sys.path.append(os.path.dirname(__file__))
# from xx.y import yy
# print(yy.age2)

# # 使用相對位置找到bbb文件夾中的bb
# print(__file__) # 當前文件的絕對路徑;D:/Program Files (x86)/DjangoProjects/basic/day15/test_imoirt.py
# # 使用os模組獲取一個路徑的父路徑
# import os
# print(os.path.dirname(__file__)) # 獲取當前文件的父路徑 D:/Program Files (x86)/DjangoProjects/basic/day15
# print(os.path.dirname(__file__)+r'/bbb') # D:/Program Files (x86)/DjangoProjects/basic/day15/bbb