[英]Magic attributes of functions inconsistent
I'm currently working on a project where I have a class with various expensive methods that I'd like to cache. 目前,我正在一个项目中,我有一个类,其中包含各种我想缓存的昂贵方法。 I want to implement the cache myself, both for exercise as well as that it's special in that it specifically aimed at functions where
f(f(x)) == x
is True
(via a dict subclass where d[key] == value and d[value] == key
is True
). 我想自己实现缓存,以用于练习,它的特殊之处在于它专门针对
f(f(x)) == x
为True
f(f(x)) == x
(通过dict子类,其中d[key] == value and d[value] == key
为True
)。 This goes kinda deep into python at times, and I'm a bit lost at the moment. 有时这会深入到python,此刻我有点迷路。
The cache should be attached to the class that the method is defined on and thus I needed to extract the class from the function in the decorator that adds the cache to a function. 缓存应该附加到定义该方法的类上,因此我需要从将缓存添加到函数的装饰器中的函数中提取该类。 The problem is that it seems like python does indeed do something else as
f = dec(f)
when decorating f
with @dec
. 问题是,当用
@dec
装饰f
时,似乎python确实确实做了f = dec(f)
其他事情。
My test code and the beginning of the cache decorator is: 我的测试代码和缓存装饰器的开头是:
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
When defining A
without the cache decorator and then execute the following calls (REPL session) it gives the outputs as expected: 在不使用缓存装饰器
A
情况下定义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'>
But if I instead run the class definition with the decorator I always have staticmethod
objects inside the decorator and it breaks because those don't have a __qualname__
. 但是,如果我改为使用装饰器运行类定义,则装饰器内部始终会有
staticmethod
对象,并且该对象会中断,因为这些对象没有__qualname__
。 Calling dir
on Ax
, where x
are all the test methods, gives a completely different output as when dir
is called within the decorator. 在
Ax
上调用dir
,其中x
是所有测试方法,与在装饰器中调用dir
时产生完全不同的输出。
The question I have is, why is it that @dec
receives a function object that's different from what dec(f)
receives? 我的问题是,为什么
@dec
接收的函数对象与dec(f)
接收的对象不同? Is there any way to retrieve the class a function is defined on within the scope of a decorator or would I always have to manually do Ax = dec(x)
? 有什么方法可以检索在装饰器范围内定义的函数的类,还是我总是必须手动执行
Ax = dec(x)
?
The __self__
attribute you are trying to access there is not present when the decorator code is run. 运行装饰器代码时,您尝试访问的
__self__
属性不存在。
The decorator body is called with the method as a parameter when the class body is run - that is before the class itself is created, not to say instances of that class. 当运行类主体时-即在创建类本身之前,更不用说该类的实例时,以该方法作为参数调用装饰器主体。
The easiest way to get the instance ( self
) in a method decorator is simply to accept it as a parameter in the wrapper function your decorator uses to replace the original method: 在方法装饰器中获取实例(
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
(For the method name to be preserved, you should decorate the wrapper
inner function itself with functools.wraps
) (要保留方法名称,您应该使用
functools.wraps
装饰wrapper
内部函数本身)
In this model, when the code inside wrapper
is run, you have a living instance of your class - and the self
parameter is the instance - and you can decide whether or not to call the original function based in whatever you want and have stored in cache from previous calls. 在此模型中,运行
wrapper
的代码时,您将拥有一个活泼的类实例-并且self
参数是该实例-并且您可以根据所需的内容和存储的内容来决定是否调用原始函数。从先前的呼叫中缓存。
So I just noticed that I did in fact not need to attach the cache to the class, making everything way easier. 因此,我只是注意到我实际上不需要将缓存附加到类上,从而使一切都变得更加容易。 Details are in my comment under @jsbuenos answer.
详细信息在我的@jsbuenos答案下的评论中。 The final solution looks like this:
最终的解决方案如下所示:
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.