Магические атрибуты функций несовместимы - PullRequest
0 голосов
/ 03 февраля 2019

В настоящее время я работаю над проектом, в котором у меня есть класс с различными дорогими методами, которые я хотел бы кэшировать.Я хочу реализовать кеш самостоятельно, как для упражнений, так и для того, чтобы он был особенным в том смысле, что он специально нацелен на функции, где f(f(x)) == x равно True (через подкласс dict, где d[key] == value and d[value] == key равно True).Временами это немного углубляется в python, и в данный момент я немного растерялся.

Кэш должен быть присоединен к классу, для которого определен метод, и, таким образом, мне нужно было извлечь класс изфункция в декораторе, которая добавляет кэш для функции.Проблема в том, что кажется, что python действительно делает что-то еще, как f = dec(f), когда декорирует f с помощью @dec.

Мой тестовый код и начало декоратора кеша:

def bidirectional_cache(function):
    """Function decorator for caching
    For functions where f(f(x)) == x is True
    Requires hashable args and doesn't support kwargs
    """
    parent_instance = getattr(function, "__self__", None)
    #print(type(function))
    #print(dir(function))
    if parent_instance is None:
        parent_class = globals()[function.__qualname__.rstrip(f".{function.__name__}")]
    elif type(parent_instance) is type:
        parent_class = parent_instance
    else:
        parent_class = parent_instance.__class__
    print(parent_class)
    ...

class A():
    N = 0
    def __init__(self, n):
        self.n = n

    def __hash__(self):
        return hash(self.n)

    def __add__(self, other):
        return self.__class__(int(self) + int(other))

    def __int__(self):
        return self.n

    @bidirectional_cache
    def test(self):
        return f"n = {self.n}"

    @bidirectional_cache
    @staticmethod
    def test_static(a, b):
        return a + b

    @bidirectional_cache
    @classmethod
    def test_class(cls, b):
        return N + b

При определении A без декоратора кеша и последующем выполнении следующих вызовов (сеанс REPL) он выдает ожидаемые результаты:

>>> bidirectional_cache(A.test)
<class '__main__.A'>
>>> bidirectional_cache(A.test_static)
<class '__main__.A'>
>>> bidirectional_cache(A.test_class)
<class '__main__.A'>
>>> a = A(5)
>>> bidirectional_cache(a.test)
<class '__main__.A'>
>>> bidirectional_cache(a.test_static)
<class '__main__.A'>
>>> bidirectional_cache(a.test_class)
<class '__main__.A'>

Но если вместо этого я запускаю определение класса с декораторомУ меня всегда есть staticmethod объекты внутри декоратора, и он ломается, потому что у них нет __qualname__.Вызов dir для A.x, где x - все методы тестирования, дает совершенно другой вывод, как при вызове dir в декораторе.

Вопрос, который у меня возникает, почемучто @dec получает объект функции, отличный от того, что получает dec(f)?Есть ли способ получить класс, для которого определена функция в рамках декоратора, или мне всегда придется делать A.x = dec(x)?

Ответы [ 2 ]

0 голосов
/ 03 февраля 2019

Итак, я только что заметил, что на самом деле мне не нужно прикреплять кеш к классу, что упрощает все.Подробности в моем комментарии под ответом @jsbuenos.Окончательное решение выглядит так:

class BidirectionalDict(dict):
    def __setitem__(self, key, value):
        super().__setitem__(hash(key), value)
        super().__setitem__(value, key)

    def __delitem__(self, key):
        super().__delitem__(self[key])
        super().__delitem__(key)


def bidirectional_cache(function):
    """Function decorator for caching
    For functions where f(f(x)) == x is True
    Requires hashable args and doesn't support kwargs
    """
    cache = BidirectionalDict()
    @wraps(function)
    def wrapped(*args):
        if hash(args) not in cache:
            cache[hash(args)] = function(*args)
        return cache[hash(args)]
    return wrapped
0 голосов
/ 03 февраля 2019

Атрибут __self__, к которому вы пытаетесь обратиться, отсутствует при запуске кода декоратора.

Тело декоратора вызывается с методом в качестве параметра при запуске тела класса, то естьперед созданием самого класса, не говоря уже об экземплярах этого класса.

Самый простой способ получить экземпляр (self) в декораторе метода - просто принять его как параметр в функции-обертке вашегоДля замены исходного метода используется декоратор:

def bidirectional_cache(function):
    """Function decorator for caching
    For functions where f(f(x)) == x is True
    Requires hashable args and doesn't support kwargs
    """
    def wrapper(self, *args, **kw):
        parent_instance = self
        parent_class = parent_instance.__class__
        print(parent_class)
        ...
        result = function(self, *args, **kw)
        ...
        return result
    return wrapper

(чтобы сохранить имя метода, необходимо украсить внутреннюю функцию wrapper самой functools.wraps)

В этой моделикогда код внутри wrapper запущен, у вас есть живой экземпляр вашего класса - а параметр self - это экземпляр - и вы можете решить, следует ли вызывать исходную функцию, основываясь на том, что вы хотите и сохранилив кеше от предыдущих звонков.

...