從函式到包的Python程式碼層次

dongfanger發表於2020-12-15

程式碼層次

Python是一門指令碼語言,新建一個.py檔案,寫點程式碼,就可以跑起來了,無論放哪都可以。比如where.py檔案:

print("Where am I?")

那麼問題來了,這是寫在哪裡的呢?為了一目瞭然,我們用“導遊圖”的視角來看看程式碼層次:

從函式到包的Python程式碼層次

紅色箭頭指出了,是寫在模組中的,原來一個.py檔案就是一個模組。模組中可以寫函式和類,模組可以放在包中。

函式

Python中最出名的函式一定是print()了,畢竟全世界都在用它say Hello World。Python函式其實和數學中的函式很像,比如y = f(x)。有函式名字、輸入和輸出。Python的函式結構如下:

image-20201214141858428

函式通過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提供了名稱空間這個方法,把程式碼塊劃分為不同的名稱空間,同一個名稱空間不能重名,不同名稱空間可以重名,如圖所示:

image-20201214170723742

名稱空間一般有三種:

image-20201214162422897
  • 內建: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中只有模組、類和函式,才會產生作用域。其他程式碼塊如ifwhilefor等是不會產生作用域的,也就是說這些語句內定義的變數,外部也可以訪問,例如:

if True:
    a = 1
print(a)

東方說

本文是Python入門系列這道前菜的最後一篇了,正餐Python進階系列計劃在2021年1月開始推送,具體計劃我會寫在元旦的一篇文章中。Python入門系列並不算完整的教程,它的定位是進階篇的鋪墊,做一些知識儲備,降低閱讀門檻。如果想學習完整教程,可以找菜鳥教程,也可以上B站看視訊(個人更推薦)。最後,為了知識共享和傳遞,我把入門的7篇文章都匯出成pdf上傳了,可以在公眾號後臺回覆“入門”下載哦。

image-20201214230028900

參考資料:

https://www.runoob.com/python3/python3-function.html

https://www.runoob.com/python3/python3-class.html

https://www.runoob.com/python3/python3-module.html

https://www.runoob.com/python3/python3-namespace-scope.html

相關文章