自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

帶參數(shù)的全類型 Python 裝飾器

開發(fā) 后端
在這篇文章中,將展示一個(gè)可選接收參數(shù)的全類型 Python 裝飾器的藍(lán)圖。

這篇短文中顯示的代碼取自我的小型開源項(xiàng)目按合同設(shè)計(jì),它提供了一個(gè)類型化的裝飾器。裝飾器是一個(gè)非常有用的概念,你肯定會(huì)在網(wǎng)上找到很多關(guān)于它們的介紹。簡單說,它們?cè)试S在每次調(diào)用裝飾函數(shù)時(shí)(之前和之后)執(zhí)行代碼。通過這種方式,你可以修改函數(shù)參數(shù)或返回值、測量執(zhí)行時(shí)間、添加日志記錄、執(zhí)行執(zhí)行時(shí)類型檢查等等。請(qǐng)注意,裝飾器也可以為類編寫,提供另一種元編程方法(例如在 attrs 包中完成)

在最簡單的形式中,裝飾器的定義類似于以下代碼:

def my_first_decorator(func):
def wrapped(*args, **kwargs):
# do something before
result = func(*args, **kwargs)
# do something after
return result
return wrapped
@my_first_decorator
def func(a):
return a

如上代碼,因?yàn)楫?dāng)定義了被包裝的嵌套函數(shù)時(shí),它的周圍變量可以在函數(shù)內(nèi)訪問并保存在內(nèi)存中,只要該函數(shù)在某處使用(這在函數(shù)式編程語言中稱為閉包)。

很簡單, 但是這有一些缺點(diǎn)。最大的問題是修飾函數(shù)會(huì)丟失它的之前的函數(shù)名字(你可以用inspect.signature看到這個(gè)),它的文檔字符串,甚至它的名字, 這些是源代碼文檔工具(例如 sphinx)的問題,但可以使用標(biāo)準(zhǔn)庫中的 functools.wraps 裝飾器輕松解決:

from functools import wraps
from typing import Any, Callable, TypeVar, ParamSpec
P = ParamSpec("P") # 需要python >= 3.10
R = TypeVar("R")
def my_second_decorator(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapped(*args: Any, **kwargs: Any) -> R:
# do something before
result = func(*args, **kwargs)
# do something after
return result
return wrapped
@my_second_decorator
def func2(a: int) -> int:
"""Does nothing"""
return a
print(func2.__name__)
# 'func2'
print(func2.__doc__)
# 'Does nothing'

在這個(gè)例子中,我已經(jīng)添加了類型注釋,注釋和類型提示是對(duì) Python 所做的最重要的補(bǔ)充。更好的可讀性、IDE 中的代碼完成以及更大代碼庫的可維護(hù)性只是其中的幾個(gè)例子。上面的代碼應(yīng)該已經(jīng)涵蓋了大多數(shù)用例,但無法參數(shù)化裝飾器。考慮編寫一個(gè)裝飾器來記錄函數(shù)的執(zhí)行時(shí)間,但前提是它超過了一定的秒數(shù)。這個(gè)數(shù)量應(yīng)該可以為每個(gè)裝飾函數(shù)單獨(dú)配置。如果沒有指定,則應(yīng)使用默認(rèn)值,并且應(yīng)使用不帶括號(hào)的裝飾器,以便更易于使用:

@time(threshold=2)
def func1(a):
...
# No paranthesis when using default threshold
@time
def func2(b):
...

如果你可以在第二種情況下使用括號(hào),或者根本不提供參數(shù)的默認(rèn)值,那么這個(gè)秘訣就足夠了:

from functools import wraps
from typing import Any, Callable, TypeVar, ParamSpec
P = ParamSpec("P") # 需要python >= 3.10
R = TypeVar("R")
def my_third_decorator(threshold: int = 1) -> Callable[[Callable[P, R]], Callable[P, R]]:
def decorator(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> R:
# do something before you can use `threshold`
result = func(*args, **kwargs)
# do something after
return result
return wrapper
return decorator
@my_third_decorator(threshold=2)
def func3a(a: int) -> None:
...
# works
@my_third_decorator()
def func3b(a: int) -> None:
...
# Does not work!
@my_third_decorator
def func3c(a: int) -> None:
...

為了涵蓋第三種情況,有一些包,即 wraps 和 decorator,它們實(shí)際上可以做的不僅僅是添加可選參數(shù)。雖然質(zhì)量非常高,但它們引入了相當(dāng)多的額外復(fù)雜性。使用 wrapt-decorated 函數(shù),在遠(yuǎn)程集群上運(yùn)行函數(shù)時(shí),我進(jìn)一步遇到了序列化問題。據(jù)我所知,兩者都沒有完全鍵入,因此靜態(tài)類型檢查器/ linter(例如 mypy)在嚴(yán)格模式下失敗。

當(dāng)我在自己的包上工作并決定編寫自己的解決方案時(shí),必須解決這些問題。它變成了一種可以輕松重用但很難轉(zhuǎn)換為庫的模式。

它使用標(biāo)準(zhǔn)庫的重載裝飾器。這樣,可以指定相同的裝飾器與我們的無參數(shù)一起使用。除此之外,它是上面兩個(gè)片段的組合。這種方法的一個(gè)缺點(diǎn)是所有參數(shù)都需要作為關(guān)鍵字參數(shù)給出(這畢竟增加了可讀性)

from typing import Callable, TypeVar, ParamSpec
from functools import partial, wraps
P = ParamSpec("P") # requires python >= 3.10
R = TypeVar("R
@overload
def typed_decorator(func: Callable[P, R]) -> Callable[P, R]:
...
@overload
def typed_decorator(*, first: str = "x", second: bool = True) -> Callable[[Callable[P, R]], Callable[P, R]]:
...
def typed_decorator(
func: Optional[Callable[P, R]] = None, *, first: str = "x", second: bool = True
) -> Union[Callable[[Callable[P, R]], Callable[P, R]], Callable[P, R]]:
"""
Describe what the decorator is supposed to do!
Parameters
----------
first : str, optional
First argument, by default "x".
This is a keyword-only argument!
second : bool, optional
Second argument, by default True.
This is a keyword-only argument!
"""
def wrapper(func: Callable[P, R], *args: Any, **kw: Any) -> R:
"""The actual logic"""
# Do something with first and second and produce a `result` of type `R`
return result
# Without arguments `func` is passed directly to the decorator
if func is not None:
if not callable(func):
raise TypeError("Not a callable. Did you use a non-keyword argument?")
return wraps(func)(partial(wrapper, func))
# With arguments, we need to return a function that accepts the function
def decorator(func: Callable[P, R]) -> Callable[P, R]:
return wraps(func)(partial(wrapper, func))
return decorator

稍后,我們可以分別使用我們的不帶參數(shù)的裝飾器

@typed_decorator
def spam(a: int) -> int:
return a
@typed_decorator(first = "y
def eggs(a: int) -> int:
return a

這種模式肯定有一些開銷,但收益大于成本。

原文:??https://lemonfold.io/posts/2022/dbc/typed_decorator/??

責(zé)任編輯:龐桂玉 來源: python運(yùn)維技術(shù)
相關(guān)推薦

2023-02-07 07:47:52

Python裝飾器函數(shù)

2016-11-01 09:24:38

Python裝飾器

2024-05-24 11:36:28

Python裝飾器

2009-07-09 00:25:00

Scala參數(shù)化

2010-02-01 17:50:32

Python裝飾器

2021-04-11 08:21:20

Python@property裝飾器

2025-01-22 15:58:46

2022-09-19 23:04:08

Python裝飾器語言

2024-09-12 15:32:35

裝飾器Python

2023-12-11 15:51:00

Python裝飾器代碼

2021-07-27 15:58:12

Python日志代碼

2021-06-01 07:19:58

Python函數(shù)裝飾器

2023-12-13 13:28:16

裝飾器模式Python設(shè)計(jì)模式

2022-09-21 09:04:07

Python裝飾器

2010-09-07 08:44:22

無線路由器

2022-09-26 09:02:54

TS 裝飾器TypeScript

2021-10-30 18:59:15

Python

2010-10-08 16:55:44

MySql存儲(chǔ)過程

2021-03-27 10:54:34

Python函數(shù)代碼

2024-11-09 08:26:52

Python裝飾器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)