
@total_ordering
允許我們僅透過 2 個 Class comparison 方法,來自動完成一套完整的 comparison operations (<
、<=
、==
、!=
、>=
、>
) 方法。@total_ordering
允許您專注於 Class 如何比較的核心邏輯,並讓 Python 處理繁瑣的樣板。 (顯著簡化您的程式碼)
Class 必須使用 __eq__
和其他任一種方法 (__lt__
(小於)、__le__
(小於或等於)、__gt__
(大於)、__ge__
(大於或等於)),才能使用 @total_ordering
。
from functools import total_ordering
@total_ordering
class MyClass():
def __init__(self, value):
self.value = value
def __eq__(self, other):
if isinstance(other, MyClass):
return self.value == other.value
return NotImplemented
def __lt__(self, other):
if isinstance(other, MyClass):
return self.value < other.value
return NotImplemented
a = MyClass(10)
b = MyClass(20)
print(a == b) # False
print(a != b) # True
print(a < b) # True
print(a > b) # False
print(a <= b) # True
print(a >= b) # False
comparison 方法:
self
參數是對調用該方法的實例的引用,而 other
參數是對我們要比較的實例的引用。
假設我們有一個 MyClass
Class 和兩個實例 a
和 b
。當您在程式碼中調用 a < b
時,這實際上會被轉換為 a.__lt__(b)
。
實例 a
是 self
,實例 b
是 other
,因此 Class 中必須有 __lt__(self, other)
此方法。
@wraps()
@wraps()
是一個 decorator factory,它將 update_wrapper()
應用於裝飾函數。 這通常用在裝飾器定義中,以確保保留被裝飾的原始函數的 metadata。
當我們創建一個 decorator 時,通常會用另一個函數替換或包裝(wrapper)一個函數。 因此原始函數的 metadata 會丟失 (name, docstring, module, parameter list...)。 如果我們需要訪問這些訊息,這就會出現問題 (例如:debugging 或檢查)。
這就是 @wraps()
派上用場的地方。 該 decorator 用於我們的 decorator 的定義中,以保存裝飾函數的 metadata。
定義 decorators 時使用 @wraps()
是一個很好的做法,以確保 debugging 按預期的工作。
@wraps()
確保保留修飾函數 metadata。 這對於大型程式碼 library 中的日誌記錄和 debugging 非常有用。
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Before calling the function")
result = func(*args, **kwargs)
print("After calling the function")
return result
return wrapper
@my_decorator
def greet(name):
"""Greets a person"""
print(f"Hello, {name}")
greet("Alice")
print(greet.__name__) # Output: greet
print(greet.__doc__) # Output: Greets a person
範例 – 帶參數的 Decorators
有時,我們想要定義帶有參數的 decorators。 這需要另一層 wrapping。
在此示例中,repeat()
是一個 decorator factory,它接受參數num_times
。 它返回 decorator decorator_repeat()
,而 decorator 又返回包裝函數。 @wraps(func)
保留了原始函數的 metadata。
import functools
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
@repeat(num_times=4)
def greet(name):
"""Greets a person"""
print(f"Hello, {name}")
greet("Alice")
'''
Hello, Alice
Hello, Alice
Hello, Alice
Hello, Alice
'''
範例 – Debugging
import functools
import time
# Define debugging decorator
def debug(func):
"""Prints the function signature and return value"""
@functools.wraps(func)
def wrapper_debug(*args, **kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
print(f"Calling {func.__name__}({signature})")
start = time.time()
value = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} returned {value!r} in {end-start:.5f} s")
return value
return wrapper_debug
@debug
def make_greeting(name, age=None):
"""Form a greeting message"""
if age is None:
return f"Howdy {name}!"
else:
return f"Whoa {name}! {age} already, you are growing up!"
make_greeting("Alice", age=12)
執行結果:
Calling make_greeting('Alice', age=12)
make_greeting returned 'Whoa Alice! 12 already, you are growing up!' in 0.00000 s
update_wrapper()
update_wrapper()
函數和 @functools.wraps()
裝飾器都具有相似的目的:使包裝函數在屬性方面,模仿被包裝函數(例如:__name__
、__doc__
...)。
區別在於它們的使用方式:
functools.update_wrapper(wrapper, wrapped)
: 這是一個普通的函數,你可以明確地調用它,將包裝函數和被包裝的函數作為 arguments。它就地修改包裝器函數,並返回該函數。這個函數經常被用在 decorator 的定義中,以使 decorator 模仿被裝飾的函數。@functools.wraps(wrapped)
: 這是一個 decorator,你在 decorator 內部定義包裝函數時使用。它簡化了functools.update_wrapper()
的用法,允許你用 decorator 的語法將其包裝起來。
update_wrapper(
wrapper,
wrapped,
assigned=('__module__', '__name__',
'__qualname__', '__doc__',
'__annotations__'),
updated=('__dict__',))
assigned
和 updated
定義了原始函數的哪些屬性被轉移到包裝函數中。
assigned
屬性被直接轉移。updated
屬性在包裝函數的 dictionary 中被更新。
update_wrapper()
向引用原始函數的包裝函數加入 __wrapped__
屬性。 這意味著如果您有一個包裝函數,您可以透過 __wrapped__
屬性訪問原始函數。此屬性對於 introspection 或繞過 decorators (例如:@lru_cache
) 特別有用。
範例:
def decorator(func):
def wrapper(*args, **kwargs):
# some decoration here
pass
functools.update_wrapper(wrapper, func)
return wrapper
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# some decoration here
pass
return wrapper
包含的方法
在 functools.update_wrapper()
函數中,屬性 __module__
、__name__
、__qualname__
、__doc__
、__annotations__
直接從原始函數複製到包裝函數。 原始函數的 __dict__
屬性用於更新包裝函數的 __dict__
屬性。 這有助於使包裝函數看起來更像原始函數。
在 Python 中,每個物件都有許多 built-in 屬性,讓我們了解一下這些屬性:
__module__
:該屬性包含定義函數的 module 名稱。 如果函數定義在 main module 中,則__module__
屬性為__main__
。__name__
:該屬性包含函數的原始名稱。__qualname__
:該屬性是 "qualified name" 的縮寫,包括函數名稱、任何封閉 Class 。 它有助於明確地識別函數或 Class。__doc__
:該屬性包含函數的 docstring,它是為函數提供文檔的 string。__annotations__
:該屬性是一個字典,包含函數參數或返回值的所有註釋。__dict__
:該屬性是一個字典,包含函數的名稱空間,即其屬性名稱和值。
def sample_function(a: 'annotation for a', b: 'annotation for b') -> 'annotation for return':
"""This is a sample function for demonstrating function attributes."""
pass
print("Module:", sample_function.__module__)
print("Name:", sample_function.__name__)
print("Qualified Name:", sample_function.__qualname__)
print("Doc:", sample_function.__doc__)
print("Annotations:", sample_function.__annotations__)
print("Dict:", sample_function.__dict__)
執行結果:
Module: __main__
Name: sample_function
Qualified Name: sample_function
Doc: This is a sample function for demonstrating function attributes.
Annotations: {'a': 'annotation for a', 'b': 'annotation for b', 'return': 'annotation for return'}
Dict: {}
範例 – 略過 (不調用) Decorator
假設您出於某種原因想要繞過 (不調用) decorator , 您可以透過調用 __wrapped__
屬性來做到這一點(例如:@lru_cache
)。該技術的實際用途,取決於您正在使用的特定 decorators 和函數。
Bypassing the decorator:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Before calling {func.__name__}")
result = func(*args, **kwargs)
print(f"After calling {func.__name__}")
return result
return wrapper
@my_decorator
def greet(name):
"""Greets a person"""
print(f"Hello, {name}!")
# Calling the decorated function (wrapper function)
greet("Alice")
'''
Output:
Before calling greet
Hello, Alice!
After calling greet
'''
# Calling the original function (wrapped function)
greet.__wrapped__("Alice")
# Output: Hello, Alice!
import functools
@functools.lru_cache
def expensive_computation(x):
print(f"Computing for {x}")
return x * x
print(expensive_computation(4)) # Output: Computing for 4, 16
print(expensive_computation(4)) # Output: 16
print(expensive_computation.__wrapped__(4)) # Output: Computing for 4, 16
範例 – @wraps() vs update_wrapper()
import functools
def double_result_decorator(func):
@functools.wraps(func) # This is a shortcut for calling update_wrapper()
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
@double_result_decorator
def add(a, b):
"""Adds two numbers."""
return a + b
print(add(1, 2)) # Outputs: 6
print(add.__name__) # Outputs: 'add'
print(add.__doc__) # Outputs: 'Adds two numbers.'
import functools
def double_result_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
functools.update_wrapper(wrapper, func)
return wrapper
@double_result_decorator
def add(a, b):
"""Adds two numbers."""
return a + b
print(add(1, 2)) # Outputs: 6
print(add.__name__) # Outputs: 'add'
print(add.__doc__) # Outputs: 'Adds two numbers.'
partial()
允許您建立一個帶有一些預設參數的新函數,我們只需要帶入剩下所需的參數即可。這在具有許多參數的函數或建立函數的變體,以供特定用途時非常有用。(例如:修復函數、隱藏函數參數建立 API)
partial(func, /, *args, **keywords)
func
:建立其 partial 函數的原始函數。args
:partial 函數的 positional arguments。keywords
:partial 函數的 keyword arguments。
partial()
在更複雜的情況下使用時,特別有用。當您需要與函數本身一起傳遞某些 "狀態" 訊息或 context 時。
尤其是當您處理需要提供回調函數,但又想保留一些函數的 libraries or frameworks 。
partial()
大致等效以下程式碼:
def partial(func, /, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = {**keywords, **fkeywords}
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
範例 – 基礎
import functools
def increment_by(val, increment):
return val + increment
increment_by_5 = functools.partial(increment_by, increment=5)
numbers = [1, 2, 3, 4, 5]
result = list(map(increment_by_5, numbers))
print(result) # Output: [6, 7, 8, 9, 10]
import functools
from PIL import Image
def transform_image(image, angle, size):
rotated_image = image.rotate(angle)
resized_image = rotated_image.resize(size)
return resized_image
specific_transform = functools.partial(transform_image,
angle=90, size=(500, 500))
img = Image.open("test.jpg")
new_img = specific_transform(img)
範例 – 帶參數的 Decorators
Decorators 透過將函數作為參數,並返回一個新函數來工作,該新函數通常會擴展或更改輸入函數的行為。 因此,Decorators 通常被設計為只接受一個 argument:被裝飾的函數。
當您想要建立一個接受自己的參數的 decorator 時 (除了它所裝飾的函數之外),常見的做法是創建一個 Decorator factory (返回裝飾器的函數) 或是使用 partial()
做為 decorator factory。
範例:使用 Decorator factory
import functools
# Create decorator factory
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
@repeat(num_times=4)
def greet(name):
"""Greets a person"""
print(f"Hello, {name}")
greet("Alice")
'''
Hello, Alice
Hello, Alice
Hello, Alice
Hello, Alice
'''
範例:使用 partial()
import functools
# Use partial() as decorator factory
def _decorator_repeat(func, num_times):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
def repeat(num_times):
return functools.partial(_decorator_repeat, num_times=num_times)
@repeat(num_times=4)
def greet(name):
"""Greets a person"""
print(f"Hello, {name}")
greet("Alice")
'''
Hello, Alice
Hello, Alice
Hello, Alice
Hello, Alice
'''
partialmethod()
用於 "凍結" 方法的某些參數,生成固定指定參數的新可調用物件。 它可以被視為 partial()
的方法版本,只不過它被設計為用作定義,而不能直接可調用。
functools.partialmethod(func, /, *args, **keywords)
func
:凍結某些參數的方法,即調用的方法。*args
:傳遞給func
的 positional arguments。**keywords
:傳遞給func
的 keyword arguments。
partialmethod()
允許您透過隱藏一些實現細節來創建更清晰、更使用者友好的 API,或是使程式碼更具可讀性。
範例:
from functools import partialmethod
class Cell():
def __init__(self):
self._alive = False
@property
def alive(self):
return self._alive
def set_state(self, state):
self._alive = bool(state)
set_alive = partialmethod(set_state, True)
set_dead = partialmethod(set_state, False)
c = Cell()
print(c.alive) # Output: False
print(c.set_alive()) # Output: None
print(c.alive) # Output: True
from functools import partialmethod
class MyModel():
def calculate(self, x, y, operation):
if operation == 'add':
return x + y
elif operation == 'mul':
return x * y
else:
raise ValueError("Invalid operation")
add = partialmethod(calculate, operation='add')
mul = partialmethod(calculate, operation='mul')
model = MyModel()
print(model.add(3, 5)) # 8
print(model.mul(3, 5)) # 15
import cv2
import functools
class ImageProcessor():
def __init__(self, image):
self.image = image
def filter_image(self, filter_type, *args, **kwargs):
if filter_type == 'blur':
return cv2.blur(self.image, *args, **kwargs)
elif filter_type == 'GaussianBlur':
return cv2.GaussianBlur(self.image, *args, **kwargs)
else:
raise ValueError("Unknown filter type")
blur = functools.partialmethod(filter_image, 'blur')
GaussianBlur = functools.partialmethod(filter_image, 'GaussianBlur')
image = cv2.imread("70720315_p2.jpg")
processor = ImageProcessor(image)
# Use partialmethod()
blurred_image = processor.blur((5,5))
# Not use partialmethod()
blurred_image = processor.filter_image('blur', (5,5))
Last updated
Was this helpful?