Обеспечение поведения абстрактного метода при декорировании всех методов в подклассе ABCMeta - PullRequest
0 голосов
/ 05 июля 2018

Я хочу реализовать метакласс для обертывания методов для регистрации дополнительной информации. Но мне также нужно иметь 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.

Как я могу это исправить?

1 Ответ

0 голосов
/ 05 июля 2018

Проблема в том, что когда вы украшаете функцию (или метод) и возвращаете другой объект, вы фактически заменяете функцию (метод) чем-то другим. В вашем случае метод уже не 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!

...