Python 如何用原始类中的包装器调用替换每个函数调用?
我有两个类,其中一个是另一个的包装器。原始类中的函数使用名为Python 如何用原始类中的包装器调用替换每个函数调用?,python,python-3.x,oop,Python,Python 3.x,Oop,我有两个类,其中一个是另一个的包装器。原始类中的函数使用名为forward的方法,但我希望它在包装后使用包装类的forward方法,而不是原始方法。例如: class A: def __init__(self): self.a = 1 def forward(self, x): return self.a + x def main(self, x): return self.forward(x) + 100 clas
forward
的方法,但我希望它在包装后使用包装类的forward
方法,而不是原始方法。例如:
class A:
def __init__(self):
self.a = 1
def forward(self, x):
return self.a + x
def main(self, x):
return self.forward(x) + 100
class Wrapper:
def __init__(self, A):
self.module = A
def forward(self, x):
# Long convoluted code.
# ..
# ..
return self.module.forward(x)
classA = A()
wrapperA = Wrapper(classA)
# Goal: Make classA.main(..) use the forward function from Wrapper instead.
因为包装器类有需要运行的长而复杂的代码,所以我希望所有来自main
的forward
调用都是从包装器类而不是从原始类调用forward
在Python中有没有实现这一点的方法
我没有使用继承的原因:
classA
在运行时可以是不同的对象类型
我想到的另一种方法是在
Wrapper
中重新定义main
。但是,问题是在没有硬编码的情况下为中定义的每个方法自动执行此操作。您可以从A
继承Wrapper
,并使用super
访问父类
class A:
def __init__(self, child):
self.a = 1
self.child = child
def forward(self, x):
return self.a + x
def main(self, x):
return self.child.forward(x) + 100
class Wrapper(A):
def __init__(self):
super(Wrapper, self).__init__(self, self)
def forward(x):
return "whatever"
wrapperA = Wrapper()
但是如果您希望使用类A
,只需从包装器继承A
。否则,我就不知道出了什么问题。请不要随意使用函数。创建一个您希望使用的类,另一个类充当父类,不要混合角色
#...
class A(Wrapper):
def __init__(self):
super(A, self).__init__(self)
#...
在Python中,“一切都是对象”。包括类、函数和方法
在对象上
因此,我们可以接受任何类,循环该类中的所有函数并修改
他们需要的话
根据实际代码,问题中的问题可能会得到更好的解决
根据包装器的依赖关系,使用装饰器或元类
(它需要访问哪些值)。我将不讨论元类,因为对元类的大多数需求也可以使用类装饰器实现,因为类装饰器不太容易出错
正如您在一篇评论中提到的,您可能有几个不同的类需要包装,类装饰器解决方案可能是一个很好的候选者。这样就不会丢失包装类的继承树
下面是一个示例,不使用这两种方法,但完全按照要求执行;)
使用\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
使用类装饰器
有了类装饰器,就不需要重写\uuuuu new\uuuu
,如果没有100%正确地实现,可能会导致难以调试的问题
但是,它有一个关键的区别:它“就地”修改了现有类,因此在某种程度上丢失了原始类。尽管在不太可能的情况下,您可以保留对它的引用
然而,就地修改它也意味着您不需要用新的“包装器”类替换应用程序中的所有用法,从而使它更容易在现有代码库中实现,并消除了忘记在新实例上应用包装器的风险
from functools import update_wrapper
def _wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
def wrapper(cls):
for funcname in dir(cls):
# We skip anything starting with two underscores. They are most
# likely magic methods that we don't want to wrap with the
# additional code. The conditions what exactly we want to wrap, can
# be adapted as needed.
if funcname.startswith("__"):
continue
# We now need to get a reference to that attribute and check if
# it's callable. If not it is a member variable or something else
# and we can/should skip it.
func = getattr(cls, funcname)
if not callable(func):
continue
# Now we "wrap" the function with our additional code. This is done
# in a separate function to keep __new__ somewhat clean
wrapped = _wrap(func)
# After wrapping the function we can attach that new function ont
# our `Wrapper` instance
setattr(cls, funcname, wrapped)
return cls
@wrapper
class A:
def __init__(self):
self.a = 1
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
def main(self, x):
return self.forward(x) + 100
@wrapper
class Demo2:
def foo(self):
print("yoinks")
classA = A()
otherInstance = Demo2()
print(classA.forward(10))
print(otherInstance.foo())
print("docstring is maintained: %r" % classA.forward.__doc__)
使用函数装饰器
另一种选择是使用单独的函数包装器,它与最初的问题有很大的不同,但可能仍然很有见地
代码仍然使用相同的包装器函数,但这里的函数/方法是单独注释的
这可能会提供更大的灵活性,可以让某些方法保持“未包装”,但很容易导致包装代码的执行频率高于预期,如main()
方法所示
from functools import update_wrapper
def wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
class A:
def __init__(self):
self.a = 1
@wrap
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
@wrap # careful: will be wrapped twice!
def main(self, x):
return self.forward(x) + 100
def foo(self):
print("yoinks")
classA = A()
print(">>> forward")
print(classA.forward(10))
print("<<< forward")
print(">>> main")
print(classA.main(100))
print("<<< main")
print(">>> foo")
print(classA.foo())
print("<<< foo")
从functools导入更新\u包装
def wrap(函数):
"""
用附加代码包装*func*。
"""
#我们定义了一个包装函数。这将执行所有附加代码
#在“实”函数之前和之后。
def包装(*args,**kwargs):
打印(“调用前:”,func,args,kwargs)
输出=func(*args,**kwargs)
打印(“调用后:”,func,args,kwargs,output)
返回输出
#使用“更新包装器”保存docstring和其他函数元数据
#完好无损
更新包装(包装,func)
#现在我们可以返回包装函数
退货包装
A类:
定义初始化(自):
self.a=1
@包裹
def前进(自身,x):
"""
docstring(用于演示“更新”包装器`
"""
打印(“原件转发”)
返回self.a+x
@包裹#小心:会被包裹两次!
def干管(自身,x):
返回自我向前(x)+100
def foo(self):
打印(“yoinks”)
classA=A()
打印(“>>>转发”)
打印(A类正向(10))
打印(“>main”)
打印(A类主(100))
打印(“>foo”)
打印(classA.foo())
印刷品("为什么它必须是一个包装器,而不是一个继承的类?这会自动解决这个问题。这很有意义。我也在考虑这个问题,但似乎继承的类需要在运行时动态推断要继承的类,因为实际上classA可以是不同的类。有什么方法可以做到这一点吗?你能影响一个的实例化方式吗,或者你只是从某个地方得到一个实例吗?是的,我可以从某个地方得到一个实例,因为你可以覆盖\uu getattr\uuuuuuuuu()
在包装器中类,并转发对类a
实例的调用和属性访问,该实例在创建时作为参数传递给它。对于\uuuu setattr\uuuuu
,同样的a
实例可以由多个包装器
(或包装器的种类)包装,所以您可能不需要创建很多。这不会跳过包装类中冗长而复杂的代码吗(见上文)?@oldselflearner1959。该代码与以前在包装器中的代码一样,放在子类中。现在,您可以在包装器上调用main
,并将复杂的代码运行。您可以将包装器的引用传递给其父类,并且当您
from functools import update_wrapper
def wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
class A:
def __init__(self):
self.a = 1
@wrap
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
@wrap # careful: will be wrapped twice!
def main(self, x):
return self.forward(x) + 100
def foo(self):
print("yoinks")
classA = A()
print(">>> forward")
print(classA.forward(10))
print("<<< forward")
print(">>> main")
print(classA.main(100))
print("<<< main")
print(">>> foo")
print(classA.foo())
print("<<< foo")