我想实现一个用于包装方法的元类以记录其他信息。但是我还需要拥有abstractmethods
。我尝试扩展ABCMeta
,但似乎没有强制使用@abstractmethod
装饰器:
import types
import abc
def logfunc(fn, *args, **kwargs):
def fncomposite(*args, **kwargs):
rt = fn(*args, **kwargs)
print("Executed %s" % fn.__name__)
return rt
return fncomposite
class LoggerMeta(abc.ABCMeta):
def __new__(cls, clsname, bases, dct):
for name, value in dct.items():
if type(value) is types.FunctionType or type(value) is types.MethodType:
dct[name] = logfunc(value)
return super(LoggerMeta, cls).__new__(cls, clsname, bases, dct)
def __init__(cls, *args, **kwargs):
super(LoggerMeta, cls).__init__(*args, **kwargs)
if cls.__abstractmethods__:
raise TypeError("{} has not implemented abstract methods {}".format(
cls.__name__, ", ".join(cls.__abstractmethods__)))
class Foo(metaclass=LoggerMeta):
@abc.abstractmethod
def foo(self):
pass
class FooImpl(Foo):
def a(self):
pass
v = FooImpl()
v.foo()
运行此命令时,它会打印Executed foo
。但是,我期望它会失败,因为我没有在foo
中实现FooImpl
。
我该如何解决?
答案 0 :(得分:1)
问题是,当您装饰一个函数(或方法)并返回另一个对象时,您实际上用其他东西替换了该函数(方法)。在您的情况下,该方法不再是abstractmethod
。这是一个包装abstractmethod
的函数,ABCMeta
不能将其识别为抽象。
在这种情况下,修复相对容易:functools.wraps
:
import functools # <--- This is new
def logfunc(fn, *args, **kwargs):
@functools.wraps(fn) # <--- This is new
def fncomposite(*args, **kwargs):
rt = fn(*args, **kwargs)
print("Executed %s" % fn.__name__)
return rt
return fncomposite
这就是您需要更改的所有内容。
随着适当的更改,它会正确引发:
TypeError: Foo has not implemented abstract methods foo
但是您不再需要LoggerMeta.__init__
。您可以简单地让ABCMeta
处理存在未实现的抽象方法的情况。如果没有LoggerMeta.__init__
方法,这将引发另一个异常:
TypeError: Can't instantiate abstract class FooImpl with abstract methods foo
functools.wraps
不仅可以正确处理抽象方法。它还保留修饰后功能的签名和文档(以及其他一些不错的东西)。如果使用装饰器简单地包装函数,则几乎总是要使用functools.wraps
!