Python 的 Context Manager 和函式設計是提升程式碼效率和可維護性的關鍵。Context Manager 允許更優雅地管理資源,確保資源在使用後正確釋放,同時也能應用於暫時修改全域設定、事務處理和資源鎖定等場景。客製化的 Context Manager 可以提升程式碼可讀性,而使用 decorator 則能簡化其建立過程。此外,__exit__ 方法提供的異常資訊處理機制,讓程式碼更具健壯性。對於需要同時管理多個 Context Manager 的情況,contextlib.ExitStack 提供了便捷的解決方案。

另一方面,Python 函式設計的優劣直接影響程式碼品質。*args**kwargs 提供了處理不定數量引數的彈性,而良好的 Docstring 和 Annotation 則能提升程式碼的可讀性和可維護性。Doctest 將測試案例嵌入 Docstring 中,實作了測試與程式碼的緊密結合,確保程式碼的可靠性。最後,純函式的設計理念強調無副作用和相同的輸入產生相同的輸出,提升了程式碼的可預測性和可測試性。然而,在實際應用中,完全避免副作用並非總是可行,因此需要根據具體情況選擇合適的函式設計策略,並妥善處理可變引數的修改,以確保程式碼的健壯性和可維護性。

Python Context Manager 進階應用:玄貓的效率提升秘訣

Context Manager 是 Python 中一個強大的特性,它能幫助我們更優雅地管理資源,確保資源在使用完畢後得到正確的釋放。今天,玄貓將探討 Context Manager 的進階應用,分享一些提升效率的秘訣。

Context Manager 的基本概念:不只是檔案處理

多數人第一次接觸 Context Manager 是在檔案處理上,例如:

with open('my_file.txt', 'r') as f:
    data = f.read()

這個例子中,with 陳述式確保檔案在使用完畢後會自動關閉,即使在讀取過程中發生異常也是如此。但 Context Manager 的用途遠不止於此。

開發客製化 Context Manager:提升程式碼可讀性

我們可以透過定義 __enter____exit__ 方法來建立自己的 Context Manager。這讓我們可以更精確地控制資源的取得和釋放。

範例:計時 Context Manager

以下是一個計時 Context Manager 的範例,它可以測量程式碼區塊的執行時間:

import time

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end = time.time()
        self.elapsed = self.end - self.start
        print(f"程式碼區塊執行時間:{self.elapsed:.4f} 秒")

with Timer():
    # 模擬一段耗時的操作
    time.sleep(1)

內容解密

  • __enter__ 方法:在進入 with 區塊時被呼叫,這裡我們記錄開始時間,並回傳 self,以便在 with 區塊中使用。
  • __exit__ 方法:在離開 with 區塊時被呼叫,無論是否發生異常。這裡我們計算執行時間,並印出結果。exc_typeexc_valexc_tb 引數分別代表異常的型別、值和追蹤資訊。

範例:使用 decorator 簡化 Context Manager 建立

如果我們想要更簡潔地建立 Context Manager,可以使用 contextlib.contextmanager decorator。

import contextlib
import time

@contextlib.contextmanager
def timer():
    start = time.time()
    yield
    end = time.time()
    elapsed = end - start
    print(f"程式碼區塊執行時間:{elapsed:.4f} 秒")

with timer():
    # 模擬一段耗時的操作
    time.sleep(1)

內容解密

  • @contextlib.contextmanager:這個 decorator 將一個 generator 函式轉換為 Context Manager。
  • yield 關鍵字:yield 之前的程式碼會在進入 with 區塊時執行,yield 之後的程式碼會在離開 with 區塊時執行。

Context Manager 的進階應用:不只管理資源

Context Manager 除了管理資源外,還可以應用於其他場景,例如:

  • 暫時修改全域設定
  • 事務處理
  • 鎖定資源

範例:暫時修改全域設定

有時候,我們需要在特定程式碼區塊中使用不同的全域設定。Context Manager 可以幫助我們暫時修改設定,並在離開區塊時還原原狀。

import contextlib

GLOBAL_SETTING = "預設值"

@contextlib.contextmanager
def modify_setting(new_value):
    global GLOBAL_SETTING
    old_value = GLOBAL_SETTING
    GLOBAL_SETTING = new_value
    yield
    GLOBAL_SETTING = old_value

print(f"設定值:{GLOBAL_SETTING}") # 輸出:設定值:預設值

with modify_setting("新設定"):
    print(f"設定值:{GLOBAL_SETTING}") # 輸出:設定值:新設定

print(f"設定值:{GLOBAL_SETTING}") # 輸出:設定值:預設值

內容解密

  • modify_setting Context Manager 暫時將 GLOBAL_SETTING 修改為 "新設定",並在離開 with 區塊時還原為 "預設值"

Context Manager 的錯誤處理:確保程式碼的健壯性

Context Manager 的 __exit__ 方法可以接收異常資訊,這讓我們可以更精確地處理錯誤。

範例:記錄異常資訊

class MyContext:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            print(f"發生異常:{exc_type}, {exc_val}")
            return False # 重新引發異常
        return True # 抑制異常

with MyContext():
    result = 1 / 0 # 觸發 ZeroDivisionError

內容解密

  • 如果 __exit__ 方法回傳 False,則異常會被重新引發。
  • 如果 __exit__ 方法回傳 True,則異常會被抑制。

使用 contextlib.ExitStack 管理多個 Context Manager

當我們需要同時管理多個 Context Manager 時,可以使用 contextlib.ExitStack

import contextlib

class DatabaseConnection:
    def __init__(self, database_url):
        self.database_url = database_url

    def connect(self):
        print(f"連線到資料函式庫:{self.database_url}")

    def disconnect(self):
        print(f"斷開資料函式庫連線:{self.database_url}")

    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()

class HttpConnection:
    def __init__(self, http_url):
        self.http_url = http_url

    def connect(self):
        print(f"連線到 HTTP 伺服器:{self.http_url}")

    def disconnect(self):
        print(f"斷開 HTTP 伺服器連線:{self.http_url}")

    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()

database_url = "postgresql://user:password@host:port/database"
http_url = "https://www.example.com"

with contextlib.ExitStack() as stack:
    db_conn = stack.enter_context(DatabaseConnection(database_url))
    http_conn = stack.enter_context(HttpConnection(http_url))

    # 在這裡可以使用資料函式庫連線和 HTTP 連線
    print("執行一些操作...")

內容解密

  • contextlib.ExitStack 允許我們以堆積疊的方式管理多個 Context Manager。
  • stack.enter_context() 方法將 Context Manager 加入堆積疊中。
  • with 區塊結束時,堆積疊中的 Context Manager 會按照加入的相反順序依序離開。

Python 函式:程式碼的根本

函式是程式設計中不可或缺的一部分,幾乎每種程式語言都會使用。函式是一組執行特定任務或任務集的指令。它們有助於組織程式碼,使其更易於閱讀、理解和維護。在 Python 中,函式使用 “def” 關鍵字定義,並且是語言不可或缺的一部分。

Python 函式功能強大與靈活,使開發人員可以輕鬆執行複雜的操作。它們可用於封裝程式碼,從而使其可重複使用並減少需要編寫的程式碼量。反過來,這減少了在程式碼中引入錯誤的機會。

Python 中的函式可以是簡單的,也可以是複雜的,具體取決於它們執行的任務。簡單的函式執行單個任務,而複雜的函式執行一組任務。無論其複雜性如何,Python 函式都易於定義、使用和理解。

Python 函式的關鍵特性之一是它們可以從程式碼的不同部分多次呼叫。這使得重複使用程式碼和避免重複變得容易。Python 中的函式也可以接受引數,這使得可以根據程式的需求對它們進行自定義。

Python 函式還可以傳回值,這使得可以在複雜的操作中使用它們。return 陳述式用於從函式傳回值。傳回值可以用於程式的其他部分,從而可以輕鬆執行複雜的操作。

Python 還有內建函式,無需定義即可使用。這些函式是 Python 標準函式庫的一部分,可用於執行常見任務。內建函式的一些範例包括 print()、len() 和 input()。

在 Python 中,函式也可以在其他函式內部定義。這些稱為巢狀函式,用於函式執行僅在主函式上下文中使用的特定任務時。巢狀函式使組織程式碼和使其更具可讀性變得容易。

Python 中函式的另一個重要特性是遞迴。遞迴是一種函式呼叫自身(直接或間接)的技術。當函式需要重複執行特定任務時,會使用此技術。

總之,函式是 Python 程式設計中不可或缺的一部分。它們用於執行特定任務或任務集,並有助於組織程式碼,使其更易於閱讀、理解和維護。Python 函式功能強大與靈活,使開發人員可以輕鬆執行複雜的操作。它們可以從程式碼的不同部分多次呼叫,接受引數,傳回值,並且可以在其他函式內部定義。透過掌握 Python 中的函式,開發人員可以編寫更有效率、靈活和可重複使用的程式碼。

函式基礎知識

Python 中的函式引數和傳回值是該語言的重要組成部分,允許建立可重複使用的程式碼,這些程式碼可以使用不同的輸入輕鬆呼叫並產生不同的輸出。在本文中,玄貓將討論 Python 中的函式引數和傳回值,包括它們的型別以及如何使用它們。

Python 中的函式引數:

在 Python 中,有四種型別的函式引數:

  • 位置引數
  • 關鍵字引數
  • 預設引數
  • 可變長度引數

位置引數

位置引數是最基本的函式引數型別。這些是按照在函式定義中定義的順序傳遞給函式的引數。

def add_numbers(x, y):
    return x + y

result = add_numbers(3, 5)
print(result) # 輸出: 8

在上面的範例中,x 和 y 是傳遞給 add_numbers 函式的兩個位置引數。引數的順序很重要,因此如果我們切換引數的順序,我們將得到不同的結果。

關鍵字引數

關鍵字引數是一種使用其引數名稱將引數傳遞給函式的方式。這允許我們以任何順序傳遞引數,只要我們指定引數名稱即可。

def subtract_numbers(x, y):
    return x - y

result = subtract_numbers(x=10, y=3)
print(result) # 輸出: 7

在上面的範例中,我們使用關鍵字引數將 x 和 y 傳遞給 subtract_numbers 函式。我們也可以在同一個函式呼叫中混合使用位置引數和關鍵字引數:

result = subtract_numbers(10, y=3)
print(result) # 輸出: 7

預設引數

預設引數是一種為函式引數指定預設值的方式。如果在呼叫函式時未傳遞引數,則改為使用預設值。

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("John") # 輸出: Hello, John!
greet("Mary", "Hi") # 輸出: Hi, Mary!

在上面的範例中,我們使用預設引數來指定預設問候語(如果未提供)。如果我們傳遞一個問候語引數,它將覆寫預設值。

可變長度引數

可變長度引數允許函式接受任意數量的引數。Python 中有兩種型別的可變長度引數:

  • *args: 這允許函式接受任意數量的位置引數。
  • **kwargs: 這允許函式接受任意數量的關鍵字引數。
def multiply_numbers(*args):
    result = 1
    for arg in args:
        result *= arg
    return result

print(multiply_numbers(1, 2, 3, 4)) # 輸出: 24

Python進階技巧:解鎖 *args**kwargs 的無限可能

在Python的世界裡,*args**kwargs 這兩個魔法般的引數,賦予了函式極大的彈性和擴充套件性。它們就像是開啟潘朵拉盒子的鑰匙,讓玄貓能夠輕鬆應對各種不同數量的引數,讓程式碼更簡潔、更具可讀性。

*args:位置引數的無限延伸

*args 允許函式接收任意數量的位置引數。這些引數會被封裝成一個 tuple 傳遞給函式。想像一下,玄貓要設計一個可以計算任意數量數字乘積的函式,*args 就派上用場了。

def multiply_numbers(*args):
    """
    將所有傳入的數字相乘並傳回結果。

    玄貓提示: 適用於需要處理未知數量引數的場景。
    """
    result = 1
    for number in args:
        result *= number
    return result

# 範例:計算 2, 3, 4 的乘積
result = multiply_numbers(2, 3, 4)
print(result)  # Output: 24

內容解密

  1. def multiply_numbers(*args):: 定義一個名為 multiply_numbers 的函式,使用 *args 接收任意數量的位置引數。
  2. result = 1: 初始化乘積結果為 1。
  3. for number in args:: 遍歷 args 這個 tuple 中的每一個數字。
  4. result *= number: 將每個數字乘到 result 中。
  5. return result: 傳回最終的乘積結果。

**kwargs:關鍵字引數的靈活運用

**kwargs 則允許函式接收任意數量的關鍵字引數。這些引數會被封裝成一個 dictionary 傳遞給函式。如果玄貓需要設計一個可以列印預不同訊息的函式,並且訊息的內容可以根據需要靈活調整,**kwargs 就能夠大顯身手。

def print_values(**kwargs):
    """
    印出所有傳入的鍵值對。

    玄貓提示: 適用於需要處理帶有標籤的引數的場景。
    """
    for key, value in kwargs.items():
        print(f"{key} = {value}")

# 範例:印出姓名、年齡和城市
print_values(name="John", age=30, city="New York")
# Output:
# name = John
# age = 30
# city = New York

內容解密

  1. def print_values(**kwargs):: 定義一個名為 print_values 的函式,使用 **kwargs 接收任意數量的關鍵字引數。
  2. for key, value in kwargs.items():: 遍歷 kwargs 這個 dictionary 中的每一個鍵值對。
  3. print(f"{key} = {value}"): 印出鍵和對應的值。

*args**kwargs 的結合使用,讓函式設計更加強大。

Python 函式檔案撰寫:開發清晰易懂的程式碼

良好的程式碼檔案是協作開發的根本。玄貓認為,清晰的檔案不僅能幫助他人理解程式碼,也能讓自己在未來回顧時快速掌握程式邏輯。Python 提供了 docstringannotation 這兩種強大的工具,協助開發者撰寫優質的檔案。

Docstring:程式碼的說明書

Docstring 是 Python 中用於描述模組、函式、類別的字串。它通常位於定義之後的第一行,用三引號 ("""Docstring goes here""") 包裹。Docstring 可以透過內建的 help() 函式或在 IPython 環境中使用 ? 符號來檢視。

Docstring 分為兩種:

  • 單行 Docstring:用於簡單的函式,僅包含簡短的描述。
def add_numbers(x, y):
    """將兩個數字相加並傳回結果。"""
    return x + y
  • 多行 Docstring:用於複雜的函式,包含更詳細的描述,例如引數、傳回值、副作用等。
def greet(name):
    """
    向指定的人打招呼。

    這個函式接收一個名字作為輸入,並在控制檯印出問候訊息。
    它不傳回任何值。

    Args:
        name (str): 要問候的名字。

    Returns:
        None
    """
    print(f"Hello, {name}!")

多行 Docstring 的常見格式包括:

  • Summary:簡短的函式功能描述。
  • Args:引數列表,包含引數名稱、型別和描述。
  • Returns:傳回值描述,包含型別和值的說明。
  • Raises:可能拋出的異常列表。

Annotation:為程式碼增加型別標籤

Annotation 是 Python 3 引入的特性,允許開發者為函式的引數和傳回值新增型別提示。Annotation 本身不會影響程式碼的執行,但可以被 IDE、linter 等工具用於靜態型別檢查,提高程式碼的可靠性。

def add_numbers(x: int, y: int) -> int:
    """將兩個整數相加並傳回結果。"""
    return x + y

在這個例子中,x: inty: int 表示引數 xy 應該是整數,-> int 表示傳回值應該是整數。

Annotation 也可以用於指定預設引數值和可變長度引數。

def greet(name: str = "World", *args: str, **kwargs: str) -> None:
    """
    向指定的人打招呼,並印出額外的引數。

    這個函式接收一個名字作為輸入,並在控制檯印出問候訊息。
    它還可以接收額外的 positional 和 keyword 引數,並將它們印出。

    Args:
        name (str): 要問候的名字。預設值為 "World"。
        args (str): 額外的 positional 引數。
        kwargs (str): 額外的 keyword 引數。

    Returns:
        None
    """
    print(f"Hello, {name}!")
    if args:
        print("額外的 positional 引數:")
        for arg in args:
            print(f"- {arg}")
    if kwargs:
        print("額外的 keyword 引數:")
        for key, value in kwargs.items():
            print(f"- {key}: {value}")

玄貓建議,在編寫 Python 程式碼時,應養成撰寫 Docstring 和 Annotation 的習慣,提高程式碼的可讀性和可維護性。

Python 單元測試:使用 Doctest 確保程式碼品質

在軟體開發中,單元測試是確保程式碼品質的重要環節。玄貓發現,Python 提供了一個非常方便的單元測試工具——doctest,它可以直接將測試案例嵌入到程式碼的 Docstring 中,讓測試與程式碼緊密結合。

Doctest 的基本語法

Doctest 的測試案例由兩部分組成:

  • Prompt:以 >>> 開頭的程式碼,表示要執行的程式碼。
  • Expected Output:程式碼執行後的預期輸出結果。
def add_numbers(x, y):
    """
    將兩個數字相加並傳回結果。

    >>> add_numbers(2, 3)
    5
    >>> add_numbers(-1, 1)
    0
    """
    return x + y

在這個例子中,我們在 add_numbers 函式的 Docstring 中加入了兩個測試案例。第一個測試案例檢查 add_numbers(2, 3) 是否傳回 5,第二個測試案例檢查 add_numbers(-1, 1) 是否傳回 0

執行 Doctest

要執行 Doctest,可以使用 Python 內建的 doctest 模組。

import doctest

def add_numbers(x, y):
    """
    將兩個數字相加並傳回結果。

    >>> add_numbers(2, 3)
    5
    >>> add_numbers(-1, 1)
    0
    """
    return x + y

if __name__ == '__main__':
    doctest.testmod()

在這個例子中,我們首先匯入 doctest 模組,然後在程式碼的最後呼叫 doctest.testmod() 函式。當執行這個程式碼時,testmod() 函式會自動搜尋 Docstring 中的測試案例並執行它們。如果所有測試案例都透過,則不會有任何輸出。如果測試案例失敗,則會在控制檯印出錯誤訊息。

Doctest 的最佳實踐

玄貓建議,在使用 Doctest 時,應遵循以下最佳實踐:

  • 為每個函式和方法編寫 Doctest:確保程式碼的每個部分都經過測試。
  • 每個測試案例只包含一組輸入/輸出:保持測試案例的簡潔性。
  • 使用描述性的 Prompt:清楚地描述測試案例的輸入和預期輸出。
  • 避免在 Prompt 中使用未在函式中定義的變數:確保測試案例的獨立性。

Doctest 是一個簡單易用的單元測試工具,可以幫助開發者在編寫程式碼的同時進行測試,提高程式碼的品質。

總之,掌握 *args**kwargs、撰寫清晰的 Docstring 和 Annotation、以及使用 Doctest 進行單元測試,是 Python 開發者不可或缺的技能。它們能讓玄貓寫出更簡潔、更易懂、更可靠的程式碼。


使用斷言陳述式進行更複雜的測試案例。
使用三引號表示多行提示和輸出。
在 Python 中編寫 doctest 是一種測試函式和記錄其行為的有效方法。透過遵循最佳實務並為所有函式包含 doctest,我們可以確保我們的程式碼更可靠與更易於維護。
編寫函式註解
Python 中的函式註解用於指定函式引數和傳回值的預期資料型別。這些註解不會由直譯器強制執行,但它們可以被其他工具(如 linters、型別檢查器和 IDE)使用,以提供更好的程式碼完成、型別檢查和檔案。
若要在 Python 中編寫函式註解,您可以使用冒號語法來指示預期的資料型別。例如,若要指定函式 add 預期兩個整數作為引數並傳回一個整數,您可以編寫:
```python
def add(x: int, y: int) -> int:
    return x + y

在此範例中,引數名稱之前的 int-> 箭頭之後的 int 指示 xy 應該是整數,並且傳回值也應該是整數。 您可以使用任何資料型別作為函式註解,包括內建型別(如 intfloatstrboolNone),以及使用者定義的型別,甚至是來自 typing 模組的泛型型別。 以下是一些更多範例:

def greet(name: str) -> str:
    return f"Hello, {name}!"

def divide(x: float, y: float) -> float:
    return x / y

def repeat_string(s: str, n: int) -> str:
    return s * n

def process_data(data: List[Dict[str, Any]]) -> Dict[str, Any]:
    # 處理資料並傳回結果
    pass  # 這裡需要加入實際的資料處理邏輯

在最後一個範例中,我們使用來自 typing 模組的 ListDict 型別來指定 data 引數應該是具有字串鍵和任何型別值的字典的列表,並且傳回值應該是具有字串鍵和任何型別值的字典。

值得注意的是,函式註解在 Python 中是可選的,它們不會以任何方式影響函式的行為。但是,它們對於提供檔案和提高程式碼品質非常有用,尤其是在處理具有多個開發人員的較大專案時。

除了函式註解,您還可以使用型別提示來指定變數和屬性的預期型別。例如:

name: str = "Alice"
age: int = 30

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

p = Person("Bob", 25)

在此範例中,我們使用型別提示來指定 name 變數應該是一個字串,而 age 變數應該是一個整數,並且我們在 Person 類別建構子中使用相同的註解來指定 nameage 屬性的預期型別。

總體而言,使用函式註解和型別提示可以幫助使您的程式碼更具可讀性、可維護性和可靠性。雖然它們在 Python 中不是必需的,但它們是提高程式碼品質和減少錯誤的強大工具。 使用預設引數 在 Python 中,您可以為函式引數定義預設引數。預設引數是在未為該引數提供引數時自動指派給引數的值。這允許您編寫更靈活的函式,並且可以用最少的程式碼變更來處理不同的情境。 若要在 Python 中使用預設引數,您只需在定義函式時為引數提供預設值。例如,考慮以下新增兩個數字的函式:

def add(x, y):
    return x + y

當您使用兩個引數呼叫它時,此函式可以正常運作:

>>> add(2, 3)
5

但是,如果您想使用相同的函式將只有一個數字新增到一個固定值怎麼辦?您可以修改函式以取得 y 的預設值:

def add(x, y=0):
    return x + y

現在,如果您只使用一個引數呼叫 add,它會將該引數新增到 0(y 的預設值):

>>> add(2)
2

如果您使用兩個引數呼叫 add,它會像之前一樣將它們加在一起:

>>> add(2, 3)
5

您也可以使用預設引數,透過允許使用者自訂某些行為而無需修改函式程式碼來使函式更靈活。例如,考慮以下列印訊息的函式:

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

此函式採用一個 name 引數和一個預設為 "Hello"greeting 引數。如果您只使用一個 name 引數呼叫該函式,它會列印 "Hello, {name}!"

>>> greet("Alice")
Hello, Alice!

但是您也可以提供自訂的 greeting

>>> greet("Bob", "Good morning")
Good morning, Bob!

在 Python 中使用預設引數可以幫助您編寫更靈活和可重複使用的函式,並且使您的程式碼更易於閱讀和維護。但是,當使用可變物件(例如列表或字典)作為預設引數時,您應該小心,因為它們的值可以在對同一函式的多個呼叫中被修改,從而導致意外的行為。 使用關鍵字引數 在 Python 中,您可以使用關鍵字引數以您喜歡的任何順序將引數傳遞給函式。關鍵字引數是一種透過在呼叫函式時使用引數名稱作為關鍵字來指定哪個引數對應於哪個引數的方式。 若要在 Python 中使用關鍵字引數,您只需在呼叫函式時提供引數名稱及其對應的值。例如,考慮以下採用兩個引數的函式:

def greet(name, greeting):
    print(f"{greeting}, {name}!")

若要使用位置引數呼叫此函式,您需要以正確的順序提供引數:

>>> greet("Alice", "Hello")
Hello, Alice!

但是您也可以使用關鍵字引數呼叫此函式,明確指定引數名稱:

>>> greet(name="Bob", greeting="Good morning")
Good morning, Bob!

使用關鍵字引數時,您可以以您喜歡的任何順序提供引數:

>>> greet(greeting="Hi", name="Charlie")
Hi, Charlie!

關鍵字引數的妙用:提升程式碼可讀性與靈活性

在 Python 中,關鍵字引數是一種強大的工具,尤其在處理具有眾多引數或需要為某些引數提供預設值時。它們能讓程式碼更易讀、更易維護。

舉例來說,假設我們有一個 divide 函式,它接受兩個數字進行除法運算,並可指定結果的精確度:

def divide(x, y, precision=2):
    result = x / y
    return round(result, precision)

如果只傳入兩個引數,precision 會使用預設值 2:

print(divide(10, 3)) # 輸出 3.33

但我們也可以使用關鍵字引數,自訂 precision 的值:

print(divide(10, 3, precision=4)) # 輸出 3.3333

關鍵字引數能讓程式碼更清晰,尤其當函式有多個引數時。透過指定引數名稱,可以清楚知道每個引數的用途,並在不修改函式程式碼的情況下,為某些引數提供預設值。

*args**kwargs:開發更靈活的函式

Python 的 *args**kwargs 允許定義能接受任意數量的位置引數和關鍵字引數的函式。這在事先不知道需要傳遞多少引數,或希望提供能應付各種使用情境的彈性 API 時特別有用。

*args 用於將可變數量的位置引數傳遞給函式。在函式定義中使用 *args 時,Python 會將所有剩餘的位置引數收集到一個元組中。例如:

def my_function(*args):
    for arg in args:
        print(arg)

my_function(1, 2, 3) # 輸出 1 2 3
my_function('a', 'b', 'c', 'd') # 輸出 a b c d
my_function() # 沒有輸出

**kwargs 則用於將可變數量的關鍵字引數傳遞給函式。使用 **kwargs 時,Python 會將所有剩餘的關鍵字引數收集到一個字典中。範例如下:

def my_function(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

my_function(name="Alice", age=25) # 輸出 name: Alice age: 25
my_function(city="Boston", state="MA", country="USA") # 輸出 city: Boston state: MA country: USA
my_function() # 沒有輸出

*args**kwargs 可以同時使用,建立能同時接受位置引數和關鍵字引數的函式:

def my_function(*args, **kwargs):
    for arg in args:
        print(arg)
    for key, value in kwargs.items():
        print(f"{key}: {value}")

my_function(1, 2, 3, name="Alice", age=25) # 輸出 1 2 3 name: Alice age: 25
my_function('a', 'b', city="Boston", state="MA", country="USA") # 輸出 a b city: Boston state: MA country: USA
my_function(name="Bob") # 輸出 name: Bob
my_function() # 沒有輸出

總而言之,在 Python 中使用 *args**kwargs 能幫助建立更靈活、能處理更廣泛輸入的函式。當事先不知道需要傳遞多少引數,或希望提供能應付各種使用情境的彈性 API 時,這些特性特別有用。

函式設計:純函式的優勢

Python 函式可以分為兩類別:純函式和非純函式。純函式是指沒有副作用,與對於相同輸入總是產生相同輸出的函式。純函式具有可預測性、易於測試,與不依賴任何外部狀態。以下將探討如何在 Python 中編寫純函式,並提供範例程式碼來說明這個概念。

避免修改全域狀態

純函式不應修改任何全域狀態或函式範圍之外的變數。這包括修改全域變數或透過參照傳遞給函式的物件。

# 非純函式,修改全域變數
count = 0
def impure_add_one():
    global count
    count += 1

# 純函式,不修改任何全域狀態
def pure_add_one(num):
    return num + 1

避免修改輸入引數

純函式不應修改其輸入引數。這表示如果函式需要修改輸入物件,則應建立一個新物件或複製輸入物件。

# 非純函式,修改輸入引數
def impure_append_list(item, lst):
    lst.append(item)

# 純函式,建立一個新列表,不修改輸入
def pure_append_list(item, lst):
    return lst + [item]

避免依賴外部狀態

純函式不應依賴任何可能改變其行為的外部狀態。這包括從全域變數讀取或從檔案或資料函式庫等外部來源存取資料。

import datetime

# 非純函式,依賴外部狀態
def impure_get_current_time():
    return datetime.datetime.now()

# 純函式,接受時間引數,不依賴外部狀態
def pure_format_time(time):
    return time.strftime("%Y-%m-%d %H:%M:%S")

傳回一個值

純函式應始終傳回一個值。此值應僅由輸入引數決定,而不應由任何外部狀態決定。

# 非純函式,列印一個值而不是傳回它
def impure_print_hello(name):
    print("Hello, " + name)

# 純函式,傳回一個問候字串
def pure_get_greeting(name):
    return "Hello, " + name

以下是一個計算矩形面積的純函式範例:

def calculate_area(length, width):
    return length * width

此函式接受兩個輸入引數(長度和寬度)並傳回它們的乘積(矩形的面積)。它不修改任何輸入引數、全域狀態,也不依賴任何外部狀態。

總結來說,純函式是函式式程式設計的重要概念。透過遵循上述原則,可以編寫出更易於測試、除錯和維護的程式碼。在設計函式時,玄貓建議盡可能地使用純函式,以提高程式碼的品質和可維護性。

避免副作用:Python 純函式與可變引數的處理策略

在 Python 中,函式的設計方式對於程式碼的可讀性、可維護性以及可測試性有著深遠的影響。其中,純函式(Pure Function)和帶有副作用的函式是兩種截然不同的設計哲學。玄貓將會探討這兩種函式的特性,並提供在實際開發中如何有效運用它們的建議。

什麼是純函式?

純函式是指在給定相同的輸入時,永遠傳回相同的輸出,並且沒有任何副作用的函式。這意味著純函式不應修改任何全域性狀態、輸入引數,也不應依賴任何外部狀態。

以下是一個簡單的純函式範例:

def add(x, y):
  """
  將兩個數字相加並傳回結果。
  這是一個純函式,沒有任何副作用。
  """
  return x + y

result = add(5, 3)  # result 的值永遠是 8

純函式的優點在於其可預測性,這使得測試和除錯變得更加容易。由於沒有副作用,純函式的行為可以被獨立驗證,而不用擔心它會對程式的其他部分產生影響。

純函式的限制

儘管純函式有很多優點,但在某些情況下,完全避免副作用是不現實的。例如,與外部系統(如資料函式庫或檔案系統)互動的函式,必然會產生副作用。

如何編寫帶有副作用的函式

在 Python 中,帶有副作用的函式是指那些會修改自身作用域之外的狀態的函式。這些副作用可能包括修改全域性變數、更改物件的狀態或與外部系統互動。

以下是一些編寫帶有副作用的函式的常見方法:

1. 謹慎使用全域性變數

全域性變數是在任何函式之外宣告的變數,可以從程式的任何部分存取。修改全域性變數的函式可能很有用,但它們也可能引入難以預料的行為,並使程式碼難以維護。

# 全域性變數
count = 0

def increment_count():
  """
  修改全域性變數 count 的值。
  """
  global count
  count += 1

increment_count()
print(count)  # 輸出: 1

在這個例子中,increment_count 函式修改了全域性變數 count。雖然這在某些情況下是必要的,但過度使用全域性變數會使程式碼難以追蹤和除錯。

2. 使用方法修改物件狀態

在 Python 的物件導向程式設計中,可以使用方法來修改物件的狀態。方法是與特定物件或類別關聯的函式,可以修改其內部狀態。

class BankAccount:
  def __init__(self, balance):
    """
    初始化銀行帳戶物件。
    """
    self.balance = balance

  def deposit(self, amount):
    """
    存款到銀行帳戶。
    """
    self.balance += amount

  def withdraw(self, amount):
    """
    從銀行帳戶提款。
    """
    self.balance -= amount

account = BankAccount(100)
account.deposit(50)
print(account.balance)  # 輸出: 150

account.withdraw(25)
print(account.balance)  # 輸出: 125

在這個例子中,BankAccount 類別具有 depositwithdraw 方法,它們修改了 balance 屬性。這種方式允許封裝物件的狀態,並提供一個清晰的介面來與之互動。

3. 使用函式庫與外部系統互動

有時需要與外部系統(如資料函式庫、檔案或 Web 服務)互動以實作特定功能。在 Python 中,可以使用函式庫和模組來抽象這些互動的細節,並為函式提供一個清晰的介面。

import requests

def fetch_data(url):
  """
  使用 requests 函式庫從給定的 URL 取得資料。
  """
  response = requests.get(url)
  return response.content

在這個例子中,fetch_data 函式使用 requests 函式庫向給定的 URL 發出 HTTP 請求並傳回回應內容。透過使用函式庫,可以隱藏進行網路請求的複雜性,並為程式碼提供一個簡單的函式來互動。

修改可變引數

在 Python 中,可變引數是指可以在原地修改的引數,例如列表、字典和集合。當將可變引數傳遞給函式時,函式可以修改它,並且這些修改在函式的作用域之外仍然存在。然而,修改可變引數可能會引入難以預料的行為,並使程式碼難以維護。

以下是一些修改可變引數的常見方法:

1. 在原地修改引數

一種修改函式中可變引數的方法是在原地修改它們。這意味著直接修改原始物件,而不是建立一個新物件。

def add_item_to_list(item, lst):
  """
  將一個專案增加到列表中。
  """
  lst.append(item)

my_list = [1, 2, 3]
add_item_to_list(4, my_list)
print(my_list)  # 輸出: [1, 2, 3, 4]

在這個例子中,add_item_to_list 函式接受一個專案和一個列表,並將該專案增加到列表中。當使用 4my_list 呼叫此函式時,列表會在原地修改,並印出新的值 [1, 2, 3, 4]

2. 傳回一個新物件

另一種修改函式中可變引數的方法是建立一個新物件並傳回它。當想要保留原始物件並建立一個修改後的副本時,此方法非常有用。

def reverse_list(lst):
  """
  傳回一個列表的反向副本。
  """
  return lst[::-1]

my_list = [1, 2, 3]
reversed_list = reverse_list(my_list)
print(my_list)  # 輸出: [1, 2, 3]
print(reversed_list)  # 輸出: [3, 2, 1]

在這個例子中,reverse_list 函式接受一個列表,並傳回該列表的反向副本。當使用 my_list 呼叫此函式時,原始列表不會被修改,但會建立並傳回一個新的反向列表。

3. 結合兩種方法

在某些情況下,結合這兩種方法可能很有用:在原地修改原始物件,並傳回一個新的副本。

def remove_duplicates(lst):
  """
  從列表中刪除重複項,並傳回一個新的唯一列表。
  """
  unique_lst = list(set(lst))
  lst.clear()
  lst.extend(unique_lst)
  return unique_lst

my_list = [1, 2, 2, 3, 3, 3]
unique_list = remove_duplicates(my_list)
print(my_list)  # 輸出: [1, 2, 3]
print(unique_list) # 輸出: [1, 2, 3]

在這個例子中,remove_duplicates 函式接受一個列表,建立一個新的唯一列表,清除原始列表,並使用唯一列表擴充套件它。