從函數到包的Python程式碼層次
程式碼層次
Python是一門腳本語言,新建一個.py
文件,寫點程式碼,就可以跑起來了,無論放哪都可以。比如where.py
文件:
print("Where am I?")
那麼問題來了,這是寫在哪裡的呢?為了一目了然,我們用「導遊圖」的視角來看看程式碼層次:

紅色箭頭指出了,是寫在模組中的,原來一個.py
文件就是一個模組。模組中可以寫函數和類,模組可以放在包中。
函數
Python中最出名的函數一定是print()
了,畢竟全世界都在用它say Hello World。Python函數其實和數學中的函數很像,比如y = f(x)
。有函數名字、輸入和輸出。Python的函數結構如下:

函數通過def
關鍵字來定義:
def 函數名(參數列表):
函數體
參數列表有就有,無則無,多個參數用逗號分隔。例如:
def hello() :
print("Hello World!")
hello() # 調用函數
def max(a, b):
if a > b:
return a
else:
return b
a = 4
b = 5
print(max(a, b))
調用函數,不需要再加def
,直接函數名(參數列表)
即可。參數既可以是變數,也可以是其他函數,只要能一一對應。return
關鍵字用來返回值。return不是必需的,如果沒有,那麼函數會把內部程式碼全部都執行完再退出,如果有,函數會在return語句立刻退出,同時返回return語句的值,例如:
# 可寫函數說明
def sum( arg1, arg2 ):
# 返回2個參數的和."
total = arg1 + arg2
print ("函數內 : ", total)
return total
print("這裡不會執行!")
# 調用sum函數
total = sum( 10, 20 )
print ("函數外 : ", total)
類
為什麼還要寫類,函數它不香么?這個問題有點大,我只能簡單解釋一下,那就是因為,類是包括了函數的,如果有一天你發現函數不夠用了,那麼可以用類試試,哈哈哈。
類是面向對象編程中的概念,把對象中共性的東西抽離出來而成。
類中的函數叫做方法,除了方法還有屬性(也就是變數),我寫個不嚴謹的公式:類 = 屬性 + 方法,例如:
class People:
#定義屬性
name = ''
age = 0
#定義方法
def speak(self):
print("%s 說: 我 %d 歲。" %(self.name, self.age))
類的使用跟函數一樣,需要調用,例如:
dongfanger = People() # 這叫做實例化對象
dongfanger.speak() # 調用方法
類的一大好處是,可以通過繼承來進一步復用程式碼。
模組
模組中可以包含模組級程式碼、函數和類。模組與模組之間是不能直接調用的,必須使用import
關鍵字來導入。導入時,模組級程式碼一定會被執行,如果我們不想讓某些程式碼執行,那麼可以添加一句if __name__ == '__main__':
,例如
if __name__ == '__main__':
print('這裡的程式碼,僅在該模組自身運行時執行')
else:
print('模組被導入時執行')
函數和類需要調用才會運行,所以不存在這個問題。
包
包是一個目錄,特殊的地方在於需要包含一個__init__.py
文件(內容可以為空),這是為什麼呢?設想一下import hello
這條語句,Python從哪去找hello
這個包,C盤D盤E盤,成千上萬個文件,範圍太大了。所以需要把有Python模組的目錄標出來,只查找這些目錄就可以了。示例:
sound/ 頂層包
__init__.py 初始化 sound 包
formats/ 文件格式轉換子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ 聲音效果子包
__init__.py
echo.py
surround.py
reverse.py
...
filters/ filters 子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
命名空間
命名衝突是個頭疼的問題,Python提供了命名空間這個方法,把程式碼塊劃分為不同的命名空間,同一個命名空間不能重名,不同命名空間可以重名,如圖所示:

命名空間一般有三種:

- 內置:Python內置的名字。
- 全局:模組中定義的名字,包括模組的函數、類、其他導入的模組、模組級的變數和常量。
- 局部:函數中定義的名字,包括函數的參數和局部定義的變數。(類中定義的也是)
包裡面是文件,文件名重複與否由作業系統判斷。
作用域
命名空間決定了變數的作用域,小的作用域只在內部才有作用,比如函數內的變數,模組是不能用的:
def func():
a = 1
print(a) # 報錯NameError: name 'a' is not defined
反之,大的作用域能作用到小的作用域:
a = 1
def func():
print(a)
func() # a = 1
如果不同作用域有相同名字的變數,Python的匹配順序為:局部 -> 全局 -> 內置,例如:
a = 1
def func():
a = 2 # 不會作用到模組的a
func() # 調用函數修改a的值
print(a) # a的值仍為1
函數內部的a並不能影響到模組級別的a,因為Python在找a時,函數內部已經找到了,就不會再找了。
可以使用global
關鍵字,把局部變數定義為全局變數,這樣模組級別的變數也可以在函數內修改了:
a = 1
def func():
global a # global聲明為全局
a = 2
func() # 調用函數修改a的值
print(a) # a的值變為2
另外,Python中只有模組、類和函數,才會產生作用域。其他程式碼塊如if
、while
、for
等是不會產生作用域的,也就是說這些語句內定義的變數,外部也可以訪問,例如:
if True:
a = 1
print(a)
東方說
本文是Python入門系列這道前菜的最後一篇了,正餐Python進階系列計劃在2021年1月開始推送,具體計劃我會寫在元旦的一篇文章中。Python入門系列並不算完整的教程,它的定位是進階篇的鋪墊,做一些知識儲備,降低閱讀門檻。如果想學習完整教程,可以找菜鳥教程,也可以上B站看影片(個人更推薦)。最後,為了知識共享和傳遞,我把入門的7篇文章都導出成pdf上傳了,可以在公眾號後台回復「入門」下載哦。

參考資料:
//www.runoob.com/python3/python3-function.html
//www.runoob.com/python3/python3-class.html