函数的魔法属性不一致

Magic attributes of functions inconsistent

我目前正在从事一个项目,其中我有一个 class 以及我想要缓存的各种昂贵的方法。我想自己实现缓存,既是为了练习,也是因为它的特殊之处在于它专门针对 f(f(x)) == xTrue 的函数(通过 dict subclass 其中 d[key] == value and d[value] == keyTrue)。 这有时有点深入python,我现在有点迷茫。

缓存应该附加到定义方法的 class 上,因此我需要从将缓存添加到函数的装饰器中的函数中提取 class。问题是当用 @dec.

装饰 f 时,似乎 python 确实做了其他事情 f = dec(f)

我的测试代码和缓存装饰器的开头是:

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'>

但是如果我用装饰器代替 运行 class 定义,我总是在装饰器中有 staticmethod 个对象,它会中断,因为 那些没有 __qualname__。在 A.x 上调用 dir,其中 x 是所有测试方法,给出与在装饰器中调用 dir 时完全不同的输出。

我的问题是,为什么 @dec 收到的函数对象与 dec(f) 收到的不同?有什么方法可以检索装饰器范围内定义的 class 函数,还是我总是必须手动执行 A.x = dec(x)?

装饰器代码为 运行.

时,您尝试访问的 __self__ 属性不存在

当 class 主体为 运行 时,装饰器主体以方法作为参数被调用 - 即在 class 本身被创建之前,更不用说它的实例class.

在方法装饰器中获取实例 (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

(为了保留方法名,你应该用functools.wraps装饰wrapper内部函数本身)

在这个模型中,当 wrapper 中的代码是 运行 时,您有一个 class 的实例 - 而 self 参数是实例 -并且您可以决定是否根据您想要的任何内容调用原始函数,并将之前的调用存储在缓存中。

所以我只是注意到我实际上不需要将缓存附加到 class,让一切变得更容易。详细信息在我在@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