unity入門—五分鐘製作一個理論上的遊戲
阿新 • • 發佈:2020-12-09
一 模組介紹
在Python中,一個py檔案就是一個模組,檔名為xxx.py模組名則是xxx,匯入模組可以引用模組中已經寫好的功能。如果把開發程式比喻成製造一臺電腦,編寫模組就像是在製造電腦的零部件,準備好零部件後,剩下的工作就是按照邏輯把它們組裝到一起。 將程式模組化會使得程式的組織結構清晰,維護起來更加方便。比起直接開發一個完整的程式,單獨開發一個小的模組也會更加簡單,並且程式中的模組與電腦中的零部件稍微不同的是:程式中的模組可以被重複使用。所以總結下來,使用模組既保證了程式碼的重用性,又增強了程式的結構性和可維護性。另外除了自定義模組外,我們還可以匯入使用內建或第三方模組提供的現成功能,這種“拿來主義”極大地提高了程式設計師的開發效率。
二 模組的使用
1:import語句
有如下示範檔案 #檔名:foo.py x=1 def get(): print(x) def change(): global x x=0 class Foo: def func(self): print('from the func') 要想在另外一個py檔案中引用foo.py中的功能,需要使用import foo,首次匯入模組會做三件事: 1、執行原始檔程式碼 2、產生一個新的名稱空間用於存放原始檔執行過程中產生的名字 3、在當前執行檔案所在的名稱空間中得到一個名字foo,該名字指向新建立的模組名稱空間,若要引用模組名稱空間中的名字,需要加上該字首,如下 import foo #匯入模組foo a=foo.x #引用模組foo中變數x的值賦值給當前名稱空間中的名字a foo.get() #呼叫模組foo的get函式 foo.change() #呼叫模組foo中的change函式 obj=foo.Foo() #使用模組foo的類Foo來例項化,進一步可以執行obj.func() 加上foo.作為字首就相當於指名道姓地說明要引用foo名稱空間中的名字,所以肯定不會與當前執行檔案所在名稱空間中的名字相沖突,並且若當前執行檔案的名稱空間中存在x,執行foo.get()或foo.change()操作的都是原始檔中的全域性變數x。
需要強調一點是,第一次匯入模組已經將其載入到記憶體空間了,之後的重複匯入會直接引用記憶體中已存在的模組,
不會重複執行檔案,通過import sys,列印sys.modules的值可以看到記憶體中已經載入的模組名。
提示:
1、在Python中模組也屬於第一類物件,可以進行賦值、以資料形式傳遞以及作為容器型別的元素等操作。
2、模組名應該遵循小寫形式,標準庫從python2過渡到python3做出了很多這類調整,比如ConfigP
用import語句匯入多個模組,可以寫多行import語句 import module1 import module2 ... import moduleN 還可以在一行匯入,用逗號分隔開不同的模組 import module1,module2,...,moduleN 但其實第一種形式更為規範,可讀性更強,推薦使用,而且我們匯入的模組中可能包含有python內建的模組、第三方的模組、自定義的模組,為了便於明顯地區分它們,我們通常在檔案的開頭匯入模組,並且分類匯入,一類模組的匯入與另外一類的匯入用空行隔開,不同類別的匯入順序如下: 1. python內建模組 2. 第三方模組 3. 程式設計師自定義模組 當然,我們也可以在函式內匯入模組,對比在檔案開頭匯入模組屬於全域性作用域,在函式內匯入的模組則屬於區域性的作用域。
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() #不可用
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) #最終呼叫的方式是一致的
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 ,匯入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
# 檔案: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++編譯的擴充套件模組
在匯入一個模組時,如果該模組已載入到記憶體中,則直接引用,否則會優先查詢內建模組,然後按照從左到右的順序依次檢索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在何處,我們都可以匯入它了
6: 區分py檔案的兩種用途
一個Python檔案有兩種用途,一種被當主程式/指令碼執行,另一種被當模組匯入,為了區別同一個檔案的不同用途,每個py檔案都內建了__name__變數,該變數在py檔案被當做指令碼執行時賦值為“__main__”,在py檔案被當做模組匯入時賦值為模組名
作為模組foo.py的開發者,可以在檔案末尾基於__name__在不同應用場景下值的不同來控制檔案執行不同的邏輯
#foo.py
...
if __name__ == '__main__':
foo.py被當做指令碼執行時執行的程式碼
else:
foo.py被當做模組匯入時執行的程式碼
通常我們會在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() #在被當做指令碼執行時,執行此處的程式碼