藉助Python 函式進行模組化程式碼

安全劍客發表於2019-09-21

你是否對函式、類、方法、庫和模組等花哨的程式設計術語感到困惑?你是否在與變數作用域鬥爭?無論你是自學成才的還是經過正式培訓的程式設計師,程式碼的模組化都會令人困惑。但是類和庫鼓勵模組化程式碼,因為模組化程式碼意味著只需構建一個多用途程式碼塊集合,就可以在許多專案中使用它們來減少編碼工作量。換句話說,如果你按照本文對 Python 函式的研究,你將找到更聰明的工作方法,這意味著更少的工作。

函式

函式是邁向模組化過程中重要的一步,因為它們是形式化的重複方法。如果在你的程式中,有一個任務需要反覆執行,那麼你可以將程式碼放入一個函式中,根據需要隨時呼叫該函式。這樣,你只需編寫一次程式碼,就可以隨意使用它。

以下一個簡單函式的示例:

#!/usr/bin/env python3
import time
def Timer():
print("Time is " + str(time.time() ))

建立一個名為 mymodularity 的目錄,並將以上函式程式碼儲存為該目錄下的 timestamp.py。

除了這個函式,在 mymodularity 目錄中建立一個名為 __init__.py 的檔案,你可以在檔案管理器或 bash   中執行此操作:

$ touch mymodularity/__init__.py

現在,你已經建立了屬於你自己的 Python 庫(Python 中稱為“模組”),名為 mymodularity。它不是一個特別有用的模組,因為它所做的只是匯入 time 模組並列印一個時間戳,但這只是一個開始。

要使用你的函式,像對待任何其他 Python 模組一樣對待它。以下是一個小應用,它使用你的 mymodularity 軟體包來測試 Python sleep() 函式的準確性。將此檔案儲存為 sleeptest.py,注意要在 mymodularity 資料夾 之外,因為如果你將它儲存在 mymodularity 裡面,那麼它將成為你的包中的一個模組,你肯定不希望這樣。

#!/usr/bin/env python3
import time
from mymodularity import timestamp
print("Testing Python sleep()...")
# modularity
timestamp.Timer()
time.sleep(3)
timestamp.Timer()

在這個簡單的 中,你從 mymodularity 包中呼叫 timestamp 模組兩次。從包中匯入模組時,通常的語法是從包中匯入你所需的模組,然後使用 模組名稱 + 一個點 + 要呼叫的函式名(例如 timestamp.Timer())。

你呼叫了兩次 Timer() 函式,所以如果你的 timestamp 模組比這個簡單的例子複雜些,那麼你將節省大量重複程式碼。

儲存檔案並執行:

$ python3 ./sleeptest.py
Testing Python sleep()...
Time is 1560711266.1526039
Time is 1560711269.1557732

根據測試,Python 中的 sleep 函式非常準確:在三秒鐘等待之後,時間戳成功且正確地增加了 3,在微秒單位上差距很小。

Python 庫的結構看起來可能令人困惑,但其實它並不是什麼魔法。Python 被程式設計 為一個包含 Python 程式碼的目錄,並附帶一個 __init__.py 檔案,那麼這個目錄就會被當作一個包,並且 Python 會首先在當前目錄中查詢可用模組。這就是為什麼語句 from mymodularity import timestamp 有效的原因:Python 在當前目錄查詢名為 mymodularity 的目錄,然後查詢 timestamp.py 檔案。

你在這個例子中所做的功能和以下這個非模組化的版本是一樣的:

#!/usr/bin/env python3
import time
from mymodularity import timestamp
print("Testing Python sleep()...")
# no modularity
print("Time is " + str(time.time() ) )
time.sleep(3)
print("Time is " + str(time.time() ) )

對於這樣一個簡單的例子,其實沒有必要以這種方式編寫測試,但是對於編寫自己的模組來說,最佳實踐是你的程式碼是通用的,可以將它重用於其他專案。

透過在呼叫函式時傳遞資訊,可以使程式碼更通用。例如,假設你想要使用模組來測試的不是 系統 的 sleep 函式,而是 使用者自己實現 的 sleep 函式,更改 timestamp 程式碼,使它接受一個名為 msg 的傳入變數,它將是一個字串,控制每次呼叫 timestamp 時如何顯示:

#!/usr/bin/env python3
import time
# 更新程式碼
def Timer(msg):
    print(str(msg) + str(time.time() ) )

現在函式比以前更抽象了。它仍會列印時間戳,但是它為使用者列印的內容  msg 還是未定義的。這意味著你需要在呼叫函式時定義它。

Timer 函式接受的 msg 引數是隨便命名的,你可以使用引數 m、message 或 text,或是任何對你來說有意義的名稱。重要的是,當呼叫 timestamp.Timer函式時,它接收一個文字作為其輸入,將接收到的任何內容放入 msg 變數中,並使用該變數完成任務。

以下是一個測試測試使用者正確感知時間流逝能力的新程式:

#!/usr/bin/env python3
from mymodularity import timestamp
print("Press the RETURN key. Count to 3, and press RETURN again.")
input()
timestamp.Timer("Started timer at ")
print("Count to 3...")
input()
timestamp.Timer("You slept until ")

將你的新程式儲存為 response.py,執行它:

$ python3 ./response.py
Press the RETURN key. Count to 3, and press RETURN again.
Started timer at 1560714482.3772075
Count to 3...
You slept until 1560714484.1628013
函式和所需引數

新版本的 timestamp 模組現在 需要 一個 msg 引數。這很重要,因為你的第一個應用程式將無法執行,因為它沒有將字串傳遞給 timestamp.Timer 函式:

$ python3 ./sleeptest.py
Testing Python sleep()...
Traceback (most recent call last):
  File "./sleeptest.py", line 8, in <module>
    timestamp.Timer()
TypeError: Timer() missing 1 required positional argument: 'msg'
你能修復你的 sleeptest.py 應用程式,以便它能夠與更新後的模組一起正確執行嗎?
變數和函式

透過設計,函式限制了變數的範圍。換句話說,如果在函式內建立一個變數,那麼這個變數   在這個函式內起作用。如果你嘗試在函式外部使用函式內部出現的變數,就會發生錯誤。

下面是對 response.py 應用程式的修改,嘗試從 timestamp.Timer() 函式外部列印 msg 變數:

#!/usr/bin/env python3
from mymodularity import timestamp
print("Press the RETURN key. Count to 3, and press RETURN again.")
input()
timestamp.Timer("Started timer at ")
print("Count to 3...")
input()
timestamp.Timer("You slept for ")
print(msg)
試著執行它,檢視錯誤:
$ python3 ./response.py
Press the RETURN key. Count to 3, and press RETURN again.
Started timer at 1560719527.7862902
Count to 3...
You slept for 1560719528.135406
Traceback (most recent call last):
  File "./response.py", line 15, in <module>
    print(msg)
NameError: name 'msg' is not defined

應用程式返回一個 NameError 訊息,因為沒有定義 msg。這看起來令人困惑,因為你編寫的程式碼定義了 msg,但你對程式碼的瞭解比 Python 更深入。呼叫函式的程式碼,不管函式是出現在同一個檔案中,還是打包為模組,都不知道函式內部發生了什麼。一個函式獨立地執行它的計算,並返回你想要它返回的內容。這其中所涉及的任何變數都只是 本地的:它們只存在於函式中,並且只存在於函式完成其目的所需時間內。

Return 語句

如果你的應用程式需要函式中特定包含的資訊,那麼使用return語句讓函式在執行後返回有意義的資料。

時間就是金錢,所以修改timestamp函式,以使其用於一個虛構的收費系統:

#!/usr/bin/env python3
import time
def Timer(msg):
    print(str(msg) + str(time.time() ) )
    charge = .02
    return charge

現在,timestamp 模組每次呼叫都收費 2 美分,但最重要的是,它返回每次呼叫時所收取的金額。

以下一個如何使用 return 語句的演示:

#!/usr/bin/env python3
from mymodularity import timestamp
print("Press RETURN for the time (costs 2 cents).")
print("Press Q RETURN to quit.")
total = 0
while True:
    kbd = input()
    if kbd.lower() == "q":
        print("You owe $" + str(total) )
        exit()
    else:
        charge = timestamp.Timer("Time is ")
        total = total+charge

在這個示例程式碼中,變數 charge 為 timestamp.Timer() 函式的返回,它接收函式返回的任何內容。在本例中,函式返回一個數字,因此使用一個名為 total 的新變數來跟蹤已經進行了多少更改。當應用程式收到要退出的訊號時,它會列印總花費:

$ python3 ./charge.py
Press RETURN for the time (costs 2 cents).
Press Q RETURN to quit.
Time is 1560722430.345412
Time is 1560722430.933996
Time is 1560722434.6027434
Time is 1560722438.612629
Time is 1560722439.3649364
q
You owe $0.1
行內函數

函式不必在單獨的檔案中建立。如果你只是針對一個任務編寫一個簡短的 ,那麼在同一個檔案中編寫函式可能更有意義。唯一的區別是你不必匯入自己的模組,但函式的工作方式是一樣的。以下是時間測試應用程式的最新迭代:

#!/usr/bin/env python3
import time
total = 0
def Timer(msg):
    print(str(msg) + str(time.time() ) )
    charge = .02
    return charge
print("Press RETURN for the time (costs 2 cents).")
print("Press Q RETURN to quit.")
while True:
    kbd = input()
    if kbd.lower() == "q":
        print("You owe $" + str(total) )
        exit()
    else:
        charge = Timer("Time is ")
        total = total+charge

沒有外部依賴(Python 發行版中包含 time 模組),產生與模組化版本相同的結果。它的優點是一切都位於一個檔案中,缺點是你不能在其他指令碼中使用 Timer() 函式,除非你手動複製和貼上它。

全域性變數

在函式外部建立的變數沒有限制作用域,因此它被視為全域性變數。

全域性變數的一個例子是在charge.py中用於跟蹤當前花費total 變數。total 是在函式之外建立的,因此它繫結到應用程式而不是特定函式。

應用程式中的函式可以訪問全域性變數,但要將變數傳入匯入的模組,你必須像傳送 msg 變數一樣將變數傳入模組。

全域性變數很方便,因為它們似乎隨時隨地都可用,但也很難跟蹤它們,很難知道哪些變數不再需要了但是仍然在系統記憶體中停留(儘管 Python 有非常好的垃圾收集機制)。

但是,全域性變數很重要,因為不是所有的變數都可以是函式或類的本地變數。現在你知道了如何向函式傳入變數並獲得返回,事情就變得容易了。

總結

你已經學到了很多關於函式的知識,所以開始將它們放入你的指令碼中 —— 如果它不是作為單獨的模組,那麼作為程式碼塊,你不必在一個指令碼中編寫多次。在本系列的下一篇文章中,我將介紹 Python 類。

原文地址:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559985/viewspace-2657839/,如需轉載,請註明出處,否則將追究法律責任。

相關文章