[英]Wrapping derived class method from base class
想象一下,我有一個基類和一個派生類,如下所示:
class A:
def foo(self):
pass
class B(A):
def foo(self):
pass
我希望包裝由B
實例進行的調用foo
調用。 不允許修改 B 的任何部分(我不擁有 B)。
我現在擁有的:
class A:
def __init__(self):
fnames = ["foo"]
for fname in fnames:
def capture(f):
def wrapper(*args, **kwargs):
print("wrapped")
return f(*args, **kwargs)
return wrapper
meth = capture(getattr(self, fname))
bound = meth.__get__(self)
setattr(self, fname, bound)
class B(A):
def foo(self):
print("b")
o = B()
o.foo()
print(o.foo)
這可以按預期工作,但是我擔心這在內存方面效率低下。
o.foo
是<bound method A.__init__.<locals>.capture.<locals>.wrapper of <__main__.B object at 0x10523ffd0>>
。 似乎我必須為我創建的每個實例支付 2 次關閉的費用。
有一個更好的方法嗎? 也許是基於元類的方法?
除非您計划同時運行數千個實例,否則這樣做的資源使用量不應該讓您擔心 - 與正在運行的 Python 應用程序將使用的其他資源相比,它相當小。
只是為了比較:異步協程和任務是一種可以在一個進程中創建數千個的對象,它只是“ok”,會有類似的開銷。
但是,由於您可以控制B
的基類,因此有幾種方法可以做到這一點,而無需求助於“猴子修補”——這將在 B 創建后就地修改它。 當必須修改他們不控制代碼的類時,這通常是唯一的選擇。
自動包裝該方法,當它從B
實例中檢索時,以一種懶惰的方式,甚至可以省去這一點 - 並且肯定可以比在基類__init__
包裝更優雅:
如果您事先知道必須包裝的方法,並確定它們是在您控制的類的子類中實現的,則可以通過制作專門的__getattribute__
方法來實現:這樣,該方法僅在即將獲取時才被包裝用過的。
from functools import wraps, partial
def _capture(f): # <- there is no need for this to be inside __getattribute__
# unless the wrapper is to call `super()`
@wraps(f)
def wrapper(self, *args, **kwargs):
print("wrapped")
return f(*args, **kwargs)
# ^ "f" is already bound when we retrieve it via super().__getattribute__
# so, take care not to pass "self" twice. (the snippet in the question
# body seems to do that)
return wrapper
class A:
def __getattribute__(self, name):
fnames = {"foo", }
attr = super().__getattribute__(name)
if name in fnames:
# ^ maybe add aditional checks, like if attr is a method,
# and if its origin is indeed in a
# class we want to change the behavior
attr = partial(_capture(attr), self)
# ^ partial with self as the first parameter
# has the same effect as calling __get__ passing
# the instance to bind the method
return attr
class B(A):
def foo(self):
pass
至於在創建 B 時包裝foo
,這可以使用更少的資源 - 雖然它可以在元類中完成,但從 Python 3.6 開始, __init_subclass__
特殊方法可以處理它,而無需自定義元類。
但是,如果代碼可能在class C(B):
這將再次覆蓋foo
:如果方法在基類中使用super()
調用foo
,則可以多次調用包裝器。 避免包裝器中的代碼多次運行將需要一些復雜的狀態處理(但可以毫無意外地完成)。
from functools import wraps
def _capture(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
print("wrapped")
return f(self, *args, **kwargs)
# ^ "f" is retrieved from the class in __init_subclass__, before being
# bound, so "self" is forwarded explicitly
return wrapper
class A:
def __init_subclass__(cls, *args, **kw):
super().__init_subclass__(*args, **kw)
fnames = {"foo",}
for name in fnames:
if name not in cls.__dict__:
continue
setattr(cls, name, _capture(getattr(cls, name)))
# ^no need to juggle with binding the captured method:
# it will work just as any other method in the class, and
# `self` will be filled in by the Python runtime itself.
# \/ also, no need to return anything.
class B(A):
def foo(self):
pass
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.