我有一個驗證器類,其方法執行多項檢查并可能引發不同的例外:
class Validator:
def validate(something) -> None:
if a:
raise ErrorA()
if b:
raise ErrorB()
if c:
raise ErrorC()
在外部(呼叫者)代碼中有一個地方我想自定義它的行為并防止ErrorB
被引發,而不是阻止ErrorC
. 像恢復語意這樣的東西在這里會很有用。然而,我還沒有找到實作這一目標的好方法。
澄清一下:我可以控制Validator
源代碼,但更愿意盡可能地保留其現有介面。
我考慮過的一些可能的解決方案:
顯而易見的
try: validator.validate(something) except ErrorB: ...
不好,因為它也抑制
ErrorC
了兩者都應該提高的ErrorB
情況ErrorC
。復制粘貼該方法并洗掉檢查:
# In the caller module class CustomValidator(Validator): def validate(something) -> None: if a: raise ErrorA() if c: raise ErrorC()
復制
a
and的邏輯是一個壞主意,如果更改c
會導致錯誤。Validator
將該方法拆分為單獨的檢查:
class Validator: def validate(something) -> None: self.validate_a(something) self.validate_b(something) self.validate_c(something) def validate_a(something) -> None: if a: raise ErrorA() def validate_b(something) -> None: if b: raise ErrorB() def validate_c(something) -> None: if c: raise ErrorC() # In the caller module class CustomValidator(Validator): def validate(something) -> None: super().validate_a(something) super().validate_c(something)
這只是一個稍微好一點的復制粘貼。如果
validate_d()
稍后添加一些,我們在CustomValidator
.手動添加一些抑制邏輯:
class Validator: def validate(something, *, suppress: list[Type[Exception]] = []) -> None: if a: self._raise(ErrorA(), suppress) if b: self._raise(ErrorB(), suppress) if c: self._raise(ErrorC(), suppress) def _raise(self, e: Exception, suppress: list[Type[Exception]]) -> None: with contextlib.suppress(*suppress): raise e
這是我目前傾向于的。有一個新的可選引數,
raise
語法變得有點難看,但這是可以接受的成本。添加禁用某些檢查的標志:
class Validator: def validate(something, *, check_a: bool = True, check_b: bool = True, check_c: bool = True) -> None: if check_a and a: raise ErrorA() if check_b and b: raise ErrorB() if check_c and c: raise ErrorC()
這很好,因為它允許對不同的檢查進行精細控制,即使它們引發了相同的例外。
但是,它感覺很冗長,并且需要在
Validator
更改時進行額外的維護。實際上,我在那里有超過三張支票。按值產生例外:
class Validator: def validate(something) -> Iterator[Exception]: if a: yield ErrorA() if b: yield ErrorB() if c: yield ErrorC()
這很糟糕,因為它對現有呼叫者來說是一項重大更改,并且它使傳播例外(典型用途)的方式更加冗長:
# Instead of # validator.validate(something) e = next(validator.validate(something), None) if e is not None: raise e
即使我們保持一切向后兼容
class Validator: def validate(something) -> None: e = next(self.iter_errors(something), None) if e is not None: raise e def iter_errors(something) -> Iterator[Exception]: if a: yield ErrorA() if b: yield ErrorB() if c: yield ErrorC()
新的抑制呼叫者仍然需要撰寫所有這些代碼:
exceptions = validator.iter_errors(something) e = next(exceptions, None) if isinstance(e, ErrorB): # Skip ErrorB, don't raise it. e = next(exceptions, None) if e is not None: raise e
與前兩個選項相比:
validator.validate(something, suppress=[ErrorB])
validator.validate(something, check_b=False)
uj5u.com熱心網友回復:
除了少數例外,您正在為這項作業尋找錯誤的工具。在 Python 中,raise
例外意味著執行遇到了無法恢復的例外情況。終止中斷的執行是例外的明確目的。
執行模型:4.3。例外
Python 使用錯誤處理的“終止”模型:例外處理程式可以找出發生了什么并在外層繼續執行,但它不能修復錯誤原因并重試失敗的操作(除非重新輸入有問題的部分頂部的代碼)。
要獲得例外處理的恢復語意,您可以查看用于恢復或處理的通用工具。
恢復:協程
Python 的恢復模型是協程:yield
協程生成器或async
協程都允許暫停和顯式恢復執行。
def validate(something) -> Iterator[Exception]:
if a:
yield ErrorA()
if b:
yield ErrorB()
if c:
yield ErrorC()
區分 -send
風格的“正確”協程和迭代器風格的“生成器”協程是很重要的。只要不需要向協程發送任何值,它在功能上就相當于一個迭代器。Python 對使用迭代器有很好的內置支持:
for e in validator.iter_errors(something):
if isinstance(e, ErrorB):
continue # continue even if ErrorB happens
raise e
同樣,可以filter
使用迭代器或使用推導。迭代器很容易組合并優雅地終止,使它們適合迭代例外情況。
效果處理
例外處理只是更通用的效果處理的常見用例。雖然 Python 沒有內置效果處理支持,但僅處理效果的源或接收器的簡單處理程式可以像函式一樣建模:
def default_handler(failure: BaseException):
raise failure
def validate(something, failure_handler = default_handler) -> None:
if a:
failure_handler(ErrorA())
if b:
failure_handler(ErrorB())
if c:
failure_handler(ErrorC())
這允許呼叫者通過提供不同的處理程式來更改效果處理。
def ignore_b_handler(failure: BaseException):
if not isinstance(failure, ErrorB):
raise failure
validate(..., ignore_b_handler)
這對依賴倒置似乎很熟悉,實際上與之相關。
購買效果處理有不同的階段,并且可以通過類重現很多(如果不是全部)功能。除了技術功能外,還可以通過執行緒區域變數或背景關系區域變數實作環境效果處理程式(類似于try
“自動連接”的方式) 。raise
uj5u.com熱心網友回復:
選項4的另一個旋轉:
class Validator:
def validate(something, *, suppress: tuple[Type[Exception], ...] = ()) -> None:
def _raise(e: Exception) -> None:
with contextlib.suppress(*suppress):
raise e
if a:
_raise(ErrorA())
if b:
_raise(ErrorB())
if c:
_raise(ErrorC())
這raise
部分現在看起來更干凈了。但是我不確定我是否會使用它,因為我實際上有多個validate()
類似的方法,并且每個方法都必須定義相同的閉包。
uj5u.com熱心網友回復:
此解決方案需要對原始代碼進行一些重寫以允許您使用抑制語意,但感覺相當干凈。您將每個包裝raise
在一個檢查抑制物件的背景關系管理器中:
class SuppressionManager:
def __init__(self, suppress=None):
self.suppress = suppress or []
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type in self.suppress:
pass
else:
raise exc_val from None
然后在你的函式中:
class Validator:
def validate(something, suppress=None) -> None:
mgr = SuppressionManager(suppress)
if a:
with mgr:
raise ErrorA()
if b:
with mgr:
raise ErrorB()
if c:
with mgr:
raise ErrorC()
現在沒有人需要改變任何東西 :) 如果你不控制里面的代碼validate
,我建議檢查源代碼,并重寫 AST 來包裝每個 raise 陳述句,重新編譯它,然后猴子修補它。
uj5u.com熱心網友回復:
您可以創建一個用于將其應用于方法的 mixin 類__init_subclass__
,validate
或者,更傳統的是,使用裝飾器:
class Validator:
@suppressed_raise
def validate(something, *, suppressed_raise) -> None:
if a:
suppressed_raise(ErrorA())
if b:
suppressed_raise(ErrorB())
if c:
suppressed_raise(ErrorC())
import functools
import inspect
import contextlib
def suppressed_raise(f):
sig = inspect.signature(f)
@functools.wraps(f)
def decorated(*args, *, suppress: list[Type[Exception]] = None, **kwargs):
def raise_(e: Exception) -> None:
with contextlib.suppress(*(suppress or [])):
raise e
bound = sig.bind_partial(*args, **kwargs)
bound.arguments["suppressed_raise"] = raise_
return f(**bound.args, **bound.kwargs)
return decorated
這會將suppress
串列重寫為一個隨時可用的suppressed_raise
函式,因此呼叫者會給出一個要抑制的例外串列,并且該方法可以suppressed_raise
用來引發它們的例外。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/507098.html
上一篇:Javax.validation拋出InternerServerError500而不是BadRequest400