在装饰 ABCMeta 子类中的所有方法时强制执行抽象方法行为
Enforcing abstractmethod behavior when decorating all methods in an ABCMeta subclass
我想实现一个元类来包装方法以记录附加信息。但我还需要 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
。但是我预计它会失败,因为我没有在 FooImpl
.
中实现 foo
我该如何解决这个问题?
问题在于,当您装饰一个函数(或方法)和 return 一个不同的对象时,您实际上用其他东西替换了函数(方法)。在您的情况下,该方法不再是 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
!
我想实现一个元类来包装方法以记录附加信息。但我还需要 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
。但是我预计它会失败,因为我没有在 FooImpl
.
foo
我该如何解决这个问题?
问题在于,当您装饰一个函数(或方法)和 return 一个不同的对象时,您实际上用其他东西替换了函数(方法)。在您的情况下,该方法不再是 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
!