【问题标题】:Enforcing abstractmethod behavior when decorating all methods in an ABCMeta subclass装饰 ABCMeta 子类中的所有方法时强制执行抽象方法行为
【发布时间】:2018-12-13 13:32:24
【问题描述】:

我想实现一个元类来包装方法来记录附加信息。但我还需要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

我该如何解决这个问题?

【问题讨论】:

  • 嗯...您在元类__new__ 中用非抽象函数装饰(替换)了您的抽象方法。你期待什么?
  • 您只能装饰非抽象成员。那应该可以。
  • 好吧,我也认为是这样。但我也希望记录抽象成员。猜猜我不熟悉 ABCMeta 是如何实现的。我认为只要我们不更改函数名称,当涉及到抽象方法检查时,它指向哪个实际函数并不重要。

标签: python python-3.x metaprogramming metaclass abstract-methods


【解决方案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

【讨论】:

    猜你喜欢
    • 2021-09-17
    • 1970-01-01
    • 2014-11-25
    • 2016-12-14
    • 2021-10-16
    • 2023-04-06
    • 2017-09-29
    • 2015-03-05
    • 2011-09-28
    相关资源
    最近更新 更多