在 Python 開發中,追求簡潔高效的程式碼一直是我們努力的方向。除了基礎語法,一些進階技巧的掌握能顯著提升程式碼品質。本文將分享一些實戰經驗,帶大家深入 Python 的最佳化與設計模式。首先,我們要避免直接修改可變物件,例如列表,因為這在多模組分享資料時可能導致難以預料的錯誤。建議的做法是建立副本,例如使用 [:] 切片或 copy() 方法,確保修改不會影響原始資料。接著,談到物件導向設計,@staticmethod@classmethod 是兩個利器。@staticmethod 定義的靜態方法如同類別中的工具函式,不依賴例項狀態;而 @classmethod 則常用於工廠模式,建立特定屬性的例項,提升程式碼彈性。functools.partial 也是一個值得關注的工具,它允許固定函式的部分引數,產生新的偏函式,簡化重複呼叫。例如,在影像處理中,可以預設常用的亮度調整引數,提高程式碼可讀性。最後,我們將探討裝飾器。裝飾器可以不修改原始碼的情況下,動態增強函式或類別的功能。例如,可以利用裝飾器實作日誌記錄、許可權驗證等功能。更進一步,可以設計帶引數的裝飾器,根據不同情況調整行為,例如根據日誌級別記錄不同資訊。類別裝飾器則作用於類別,可以修改屬性、方法,甚至改變類別結構,例如增加版本號、作者資訊等。閉包也是 Python 的一個重要特性,它允許建立具有持久狀態的函式,在裝飾器中應用閉包可以實作計數器等功能。

Python進階技巧:玄貓的程式碼最佳化實戰經驗

在Python的世界裡,我們常常追求更簡潔、更高效的程式碼。除了熟悉基本的語法和資料結構外,掌握一些進階技巧能讓你的程式碼更上一層樓。玄貓將分享一些在實際專案中積累的經驗,帶領大家深入瞭解Python的進階用法。

避免原地修改:玄貓的經驗法則

在Python中,修改可變物件(如列表)時,要特別小心。直接修改原始物件可能會導致意料之外的副作用,尤其是在多個地方分享同一個物件時。

為什麼要避免原地修改?

設想一個情境:你正在開發一個金融交易系統,多個模組需要存取和更新交易列表。如果其中一個模組直接修改了這個列表,其他模組可能會讀取到不一致的資料,導致嚴重的錯誤。

替代方案:建立新物件

為了避免這種情況,玄貓建議盡量建立新的物件來代替原地修改。例如,可以使用列表的copy()方法或切片操作來建立一個副本:

my_list = [1, 2, 2, 3, 3, 3]
new_list = my_list[:]  # 建立一個新的列表副本
new_list = list(set(new_list))
print(new_list)  # 輸出: [1, 2, 3]
print(my_list)  # 輸出: [1, 2, 2, 3, 3, 3]

內容解密

  • my_list[:]: 這是一種建立列表副本的簡潔方式。它會複製my_list的所有元素到一個新的列表物件。
  • list(set(new_list)): 這段程式碼用於去除列表中的重複元素。首先,set(new_list)將列表轉換為集合,集合會自動去除重複的元素。然後,list()再將集合轉換回列表。

特例:原地修改的考量

當然,在某些情況下,原地修改可能是更有效率的選擇,例如處理大型資料集時。但玄貓建議,在這種情況下,務必仔細評估風險,並確保程式碼的行為符合預期。

善用 @staticmethod@classmethod:物件導向設計的利器

@staticmethod@classmethod是Python中兩個非常有用的裝飾器,它們讓我們可以定義與類別相關的方法,而不需要例項化類別。

@staticmethod:獨立於例項的工具函式

靜態方法就像是類別中的一個獨立工具函式,它不存取或修改類別的任何狀態。

class Math:
    @staticmethod
    def factorial(n):
        if n == 0:
            return 1
        else:
            return n * Math.factorial(n-1)

print(Math.factorial(5)) # 輸出: 120

內容解密

  • @staticmethod: 這個裝飾器告訴Python,factorial方法是一個靜態方法,可以直接透過類別名稱Math來呼叫,而不需要建立Math的例項。
  • Math.factorial(n-1): 靜態方法內部呼叫自身或其他靜態方法時,需要使用類別名稱Math

@classmethod:工廠模式的實作者

類別方法則可以存取和修改類別的狀態。玄貓發現,它特別適合用於實作工廠模式,建立具有特定屬性的類別例項。

import datetime

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

    @classmethod
    def from_birth_year(cls, name, birth_year):
        age = datetime.date.today().year - birth_year
        return cls(name, age)

person = Person.from_birth_year('Alice', 1990)
print(person.age) # 輸出: 33

內容解密

  • @classmethod: 這個裝飾器告訴Python,from_birth_year方法是一個類別方法,第一個引數cls代表類別本身。
  • cls(name, age): 類別方法內部使用cls來建立類別的例項,這樣可以避免硬編碼類別名稱,使程式碼更具彈性。

partial 函式:固定引數的妙用

partial函式是functools模組中的一個強大工具,它可以讓我們固定一個函式的部分引數,從而建立一個新的函式。

簡化函式呼叫

設想一個情境:你正在開發一個影像處理程式,需要多次呼叫同一個函式來調整圖片的亮度,但每次只需要調整不同的引數。使用partial函式可以讓你預先設定一些常用的引數,簡化函式呼叫。

from functools import partial

def multiply(x, y, z):
    return x * y * z

double = partial(multiply, 2)
triple = partial(multiply, z=3)

print(double(5, 2)) # 輸出: 20
print(triple(5, 2)) # 輸出: 30

內容解密

  • partial(multiply, 2): 這個呼叫會建立一個新的函式double,它固定了multiply函式的x引數為2。
  • partial(multiply, z=3): 這個呼叫會建立一個新的函式triple,它固定了multiply函式的z引數為3。

提高程式碼可讀性

partial函式還可以提高程式碼的可讀性。透過將一些常用的引數預先設定好,我們可以更清楚地表達程式碼的意圖。

總結來說,掌握Python的進階技巧需要時間和實踐。玄貓鼓勵大家多嘗試、多思考,將這些技巧應用到實際專案中,才能真正體會到它們的價值。

使用 functools.partial 建立偏函式

在 Python 中,functools 模組提供了一個強大的工具 partial,可以用於建立偏函式。偏函式允許你預先設定(或「固定」)原始函式的部分引數,從而產生一個新的、引數較少的函式。這在需要重複使用具有相同引數的函式時非常有用。

舉例來說,假設我們有一個 multiply() 函式,它接受三個引數並傳回它們的乘積:

from functools import partial

def multiply(x, y, z):
    return x * y * z

# 建立偏函式,固定 x=2 和 z=3
double_triple = partial(multiply, 2, z=3)

# 呼叫偏函式,只需提供 y 引數
result = double_triple(y=5)
print(result)  # 輸出: 30

在這個範例中,我們定義了一個 multiply() 函式,它接受三個引數並傳回它們的乘積。接著,我們使用 partial() 建立了一個名為 double_triple 的偏函式,將 x 引數固定為 2,z 引數固定為 3。現在,當我們呼叫 double_triple() 時,只需要提供 y 引數,它會被附加到已固定的引數後面。

搭配 Lambda 函式使用偏函式

我們也可以搭配 Lambda 函式來建立偏函式。這在需要對簡單函式進行部分應用,而又不想定義單獨的函式時非常方便。

以下範例展示如何使用 Lambda 函式建立偏函式:

from functools import partial

# 使用 Lambda 函式建立偏函式,固定 x=2
double = partial(lambda x, y: x * y, 2)

# 呼叫偏函式,只需提供 y 引數
print(double(5))  # 輸出: 10

在這個範例中,我們定義了一個接受兩個引數並傳回它們乘積的 Lambda 函式。然後,我們呼叫 partial() 函式,將 Lambda 函式和引數 2 傳遞給它,從而建立一個名為 double 的偏函式。產生的偏函式將 x 引數固定為 2,並且可以透過 y 引數來呼叫,以計算一個數字的兩倍。

函式裝飾器與閉包

撰寫簡單的裝飾器

在 Python 中,裝飾器是一個接受另一個函式作為輸入,並傳回該函式修改後版本的函式。裝飾器可用於修改函式的行為,而無需更改其原始程式碼。它們是 Python 中一個強大的工具,可以幫助簡化程式碼並使其更模組化。

定義簡單的裝飾器

要定義一個簡單的裝飾器,我們可以使用 @ 符號,後跟我們想要修改的函式之前的裝飾器函式名稱。裝飾器函式將原始函式作為輸入,以某種方式修改它,並傳回修改後的函式。

以下範例展示了一個簡單的裝飾器,它在呼叫函式之前新增一個問候語:

def greeting_decorator(func):
    def wrapper():
        print("哈囉!")
        func()
    return wrapper

@greeting_decorator
def say_hello():
    print("歡迎來到我的程式!")

say_hello()

在這個範例中,我們定義了一個 greeting_decorator() 函式,它接受原始函式 func 作為輸入,定義一個新的函式 wrapper(),該函式在呼叫 func() 之前新增一個問候語,並傳回 wrapper()。然後,我們使用 @greeting_decorator 裝飾 say_hello() 函式,這會修改它,在呼叫它之前新增一個問候語。

當我們呼叫 say_hello() 時,輸出將會是:

哈囉!
歡迎來到我的程式!

將引數傳遞給裝飾器

我們也可以修改我們的裝飾器以接受引數。當我們想要根據某些外部引數修改函式的行為時,這會很有用。

以下範例展示了一個接受引數的裝飾器:

def repeat(num):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                func(*args, **kwargs)
            return wrapper
        return decorator
    return decorator

@repeat(3)
def say_hello(name):
    print(f"哈囉, {name}!")

say_hello("小明")

在這個範例中,我們定義了一個 repeat() 函式,它接受一個引數 num,定義一個裝飾器函式,該函式接受原始函式 func 作為輸入,定義一個新的函式 wrapper(),該函式重複呼叫 func() num 次,並傳回 wrapper()。然後,我們使用 @repeat(3) 裝飾 say_hello() 函式,這會修改它,重複呼叫該函式三次。

當我們呼叫 say_hello("小明") 時,輸出將會是:

哈囉, 小明!
哈囉, 小明!
哈囉, 小明!

使用多個裝飾器

我們也可以使用多個裝飾器來修改一個函式。在這種情況下,裝飾器會按照從上到下的順序應用。

以下範例展示瞭如何使用多個裝飾器:

def bold_decorator(func):
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic_decorator(func):
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

@bold_decorator
@italic_decorator
def say_hello():
    return "哈囉!"

print(say_hello())

在這個範例中,我們定義了兩個裝飾器,bold_decorator()italic_decorator(),它們透過新增 HTML 標籤來修改原始函式的輸出。然後,我們使用 @bold_decorator@italic_decorator 裝飾 say_hello() 函式,這會修改它,依序新增兩個裝飾器。

當我們呼叫 say_hello() 時,輸出將會是:

<b><i>哈囉!</i></b>

撰寫接受引數的裝飾器

在 Python 中,裝飾器是接受一個函式作為輸入並傳回一個修改後的函式作為輸出的函式。裝飾器可用於修改函式的行為,而無需更改其原始程式碼。在某些情況下,我們可能想要撰寫一個接受引數的裝飾器。在這種情況下,我們需要定義一個接受引數並傳回一個裝飾器函式的函式,該裝飾器函式接受原始函式作為輸入。

定義一個接受引數的裝飾器

要定義一個接受引數的裝飾器,我們定義一個接受引數並傳回一個裝飾器函式的函式,該裝飾器函式接受原始函式作為輸入。然後,裝飾器函式定義一個新的函式,該函式以某種方式修改原始函式並傳回修改後的函式。

以下範例展示了一個接受引數的裝飾器:

def repeat(num):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                func(*args, **kwargs)
            return wrapper
        return decorator
    return decorator

@repeat(3)
def say_hello(name):
    print(f"哈囉, {name}!")

say_hello("小明")

在這個範例中,我們定義了一個 repeat() 函式,它接受一個引數 num,定義一個裝飾器函式,該函式接受原始函式 func 作為輸入,定義一個新的函式 wrapper(),該函式重複呼叫 func() num 次,並傳回 wrapper()。然後,我們使用 @repeat(3) 裝飾 say_hello() 函式,這會修改它,重複呼叫該函式三次。

當我們呼叫 say_hello("小明") 時,輸出將會是:

哈囉, 小明!
哈囉, 小明!
哈囉, 小明!

玄貓認為,偏函式和裝飾器是 Python 中非常有用的特性,可以幫助我們編寫更簡潔、更模組化的程式碼。透過靈活運用這些工具,可以提高程式碼的可讀性和可維護性。

裝飾器進階:傳遞引數與類別裝飾器的應用

裝飾器是 Python 中一種強大的元程式設計工具,它允許你在不修改原始碼的情況下,動態地修改或增強函式和類別的功能。玄貓認為,掌握裝飾器的進階用法,對於編寫更具彈性和可維護性的程式碼至關重要。

為何需要引數化的裝飾器?

有時候,我們希望裝飾器的行為可以根據不同的情況進行調整。這時,就需要使用帶有引數的裝飾器。玄貓在開發初期也常遇到類別似問題,例如,需要根據不同的日誌級別來記錄函式的執行資訊。

以下是一個帶有引數的裝飾器範例:

def greeting_decorator(greeting):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(greeting)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@greeting_decorator("玄貓歡迎你!")
def say_hello(name):
    print(f"哈囉, {name}!")

say_hello("小明")

內容解密:

  1. greeting_decorator(greeting):這是一個裝飾器工廠函式,它接受一個 greeting 引數,並傳回一個裝飾器函式。
  2. decorator(func):這是一個裝飾器函式,它接受一個函式 func 作為引數,並傳回一個新的函式 wrapper
  3. wrapper(*args, **kwargs):這是被裝飾函式的包裝器,它在呼叫原始函式 func 之前,先印出 greeting 訊息。
  4. @greeting_decorator("玄貓歡迎你!"):這是一個語法糖,它將 say_hello 函式傳遞給 greeting_decorator("玄貓歡迎你!") 裝飾器,從而增強了 say_hello 函式的功能。

當我們呼叫 say_hello("小明") 時,輸出將會是:

玄貓歡迎你!
哈囉, 小明!

類別裝飾器:更強大的修改能力

類別裝飾器與函式裝飾器類別似,但它們作用於類別而不是函式。玄貓發現,類別裝飾器可以用於修改類別的屬性、方法,甚至可以改變類別的結構。

以下是一個簡單的類別裝飾器範例,它向類別增加一個 version 屬性:

def add_version(cls):
    cls.version = "1.0"
    return cls

@add_version
class MyClass:
    pass

print(MyClass.version) # 輸出: 1.0

內容解密:

  1. add_version(cls):這是一個類別裝飾器,它接受一個類別 cls 作為引數,並向該類別增加一個 version 屬性。
  2. @add_version:這是一個語法糖,它將 MyClass 類別傳遞給 add_version 裝飾器,從而增強了 MyClass 類別的功能。

我們還可以將多個類別裝飾器鏈式呼叫,以實作更複雜的修改:

def add_version(cls):
    cls.version = "1.0"
    return cls

def add_author(cls):
    cls.author = "玄貓"
    return cls

@add_version
@add_author
class MyClass:
    pass

print(MyClass.version) # 輸出: 1.0
print(MyClass.author)  # 輸出: 玄貓

使用閉包實作狀態持久化

閉包是 Python 中一個強大的特性,它允許你建立具有持久狀態的函式。玄貓認為,閉包在裝飾器中非常有用,可以用於在多次呼叫之間保留資訊。

以下是一個使用閉包建立計數器函式的範例:

def counter():
    count = 0
    def inner_function():
        nonlocal count
        count += 1
        return count
    return inner_function

my_counter = counter()
print(my_counter()) # 輸出: 1
print(my_counter()) # 輸出: 2
print(my_counter()) # 輸出: 3

內容解密:

  1. counter():這是一個外部函式,它定義了一個內部函式 inner_function
  2. count = 0:這是在外部函式中定義的一個變數,它將被內部函式所參照。
  3. inner_function():這是一個內部函式,它可以存取外部函式中的變數 count
  4. nonlocal count:這是一個關鍵字,它告訴 Python,count 變數不是在內部函式中定義的,而是在外部函式中定義的。
  5. return inner_function:這傳回了內部函式,從而建立了一個閉包。

每次呼叫 my_counter() 時,它都會增加 count 變數的值,並傳回新的值。由於閉包記住了 count 變數的值,因此每次呼叫都會得到不同的結果。

玄貓認為,裝飾器是 Python 中一個非常強大的工具,它可以讓你以一種優雅和可維護的方式修改和增強函式和類別的功能。透過掌握裝飾器的進階用法,你可以編寫出更具彈性和可擴充套件性的程式碼。