注意
我根据@AlexHall和@juanpa.arrivillaga所写的所有内容编写了一份答案。见下文。
我正在编写一个类装饰器来应用于方法。这种做法非常罕见,但幸运的是,StackOverflow社区帮助完成了这项工作: Class decorator for methods from other class
现在我想把事情再向前推进一步。被调用的方法应该可以访问类装饰器中的一些变量。下面是我尝试过的一个独立的小例子:
import functools
class MyDecoratorClass:
def __init__(self, method) -> None:
functools.update_wrapper(self, method)
self.method = method
self.decorator_var = None
return
def __get__(self, obj, objtype) -> object:
return type(self)(self.method.__get__(obj, objtype))
def __call__(self, *args, **kwargs) -> object:
self.decorator_var = "hello world"
retval = self.method(*args, **kwargs)
return retval
class Foobar:
def __init__(self):
pass
@MyDecoratorClass
def foo(self):
# I want to access the 'decorator_var' right here:
value = self.foo.decorator_var
print(f"foo decorator_var = {value}")
让我们测试一下:
>>> f = Foobar()
>>> f.foo()
foo decorator_var = None
如您所见,变量decorator_var
未正确访问。我相信这是在我试图访问变量时发生的:value = self.foo.decorator_var
访问self.foo
调用MyDecoratorClass
中的__get__()
方法。这将返回一个新的MyDecoratorClass()
-实例,该实例的decorator_var
已初始化为None
有没有办法从foo()
方法中访问decorator_var
这个答案基于@AlexHall和@juanpa.arrivillaga在这里所写的一切: Class decorator for methods from other class。我要感谢他们的帮助。
让
foo()
是类Foobar
中的一个方法,让foo()
用MyDecoratorClass()
实例修饰。因此,问题是:为了使其正常工作,我们需要首先考虑在程序过程中创建了多少
MyDecoratorClass()
实例。经过@AlexHall和@juanpa.arrivillaga的大量研究和帮助,我得出结论,基本上有三种选择。让我们先快速浏览一下,然后逐一深入调查概述
这种方法允许在
foo()
和MyDecoratorClass()
-实例中运行的代码之间进行通信。但是,如果您的程序中有多个Foobar()
实例f1
和f2
,那么f1.foo()
可能会影响f2.foo()
的行为方式,因为它们共享相同的MyDecoratorClass()
实例这种方法不允许在
foo()
中运行的代码与MyDecoratorClass()
-实例之间进行任何通信。假设您在foo()
代码中,试图从MyDecoratorClass()
实例访问变量:甚至在您尝试到达
decorator_var
的那一刻,您实际上得到了从__get__()
方法返回的一个新的MyDecoratorClass()
实例此选项确保了一对一的关系:每个
Foobar()
-对象只获得一个MyDecoratorClass()
-实例来包装其foo()
方法。并且每个MyDecoratorClass()
实例恰好属于一个Foobar()
-对象(*)。非常整洁(*)程序开始时为unbound
foo()
方法生成的MyDecoratorClass()
实例是这里唯一的例外。但是这个实例只用于它的__get__()
方法,该方法充当MyDecoratorClass()
实例工厂:在调用foo()
的每个Foobar()
实例上生成、返回和存储一个MyDecoratorClass()
实例。让我们看一下每个选项。在这样做之前,我想强调的是,这三个选项之间的唯一实现差异在于
__get__()
方法一,。第一个选项:坚持使用一个实例
设
MyDecoratorClass
为类Foobar
中定义的方法foo
的修饰符:即使您从未实例化
Foobar()
,Python解释器仍然会在程序的最开始创建一个MyDecoratorClass
实例。这一个实例是为UNBOUND方法foo()
创建的。选项1基本上意味着在程序的其余部分坚持使用这个MyDecoratorClass()
实例。为了实现这一点,我们需要确保__get__()
方法不会重新实例化MyDecoratorClass()
。相反,它应该使现有的MyDecoratorClass()
看起来包含绑定方法:如您所见,
self.method
从不绑定到Foobar()
-实例。相反,它只是以这种方式出现。让我们做一个测试来证明这一点。实例化Foobar()
并调用foo()
方法:方法调用基本上由两部分组成:
简而言之:每次在
Foobar()
实例上调用foo()
方法时,您都要处理一个也是唯一一个MyDecoratorClass()
实例,该实例包含一个未绑定的foo()
方法引用,并使它看起来绑定到您在foo()
实例上调用的Foobar()
一些额外的测试
您可以使用以下方法验证
self.method
在__call__()
方法中始终未绑定self.method
:hasattr(self.method, '__self__')
self.method.__self__ is not None
总是打印
False
您还可以在
__init__()
方法中放置一个print语句,以验证MyDecoratorClass()
仅实例化一次,即使您在多个Foobar()
对象上调用foo()
注释
正如@AlexHall指出的,这:
基本上与以下内容相同:
这是因为在对象上应用括号
'()'
与调用其__call__()
方法基本相同。你也可以将返回语句替换为:甚至:
二,。第二个选项:每次调用创建一个新实例
在第二个选项中,我们在每次
foo()
调用时实例化一个新的MyDecoratorClass()
实例:这个
MyDecoratorClass()
实例非常短暂。我已经用__del__()
方法中的print语句检查过,它在foo()结束后立即被垃圾收集因此,如果在多个
Foobar()
实例上调用foo()
,就会发生这种情况:与往常一样,unbound
foo()
方法的MyDecoratorClass()
实例在任何Foobar()
对象生成之前都会生成。它将一直保持活动状态,直到程序结束。让我们称之为不朽MyDecoratorClass()
-实例在调用
foo()
时,就创建了一个新的短期MyDecoratorClass()
-实例。请记住,foo()
调用基本上分两步进行:f1.foo()
完成,短寿命的MyDecoratorClass()
-实例被垃圾回收(您可以使用__del__()
方法中的print语句来测试这一点)现在是
f2.foo()
的时间了。当短命的MyDecoratorClass()
实例死亡时,它调用不朽实例上的__get__()
方法(还有什么?)。在此过程中,将创建一个新实例,并重复该循环简言之:每个
foo()
调用都从对不朽MyDecoratorClass()
实例调用__get__()
方法开始。此对象始终返回一个新的但短暂的MyDecoratorClass()
实例,该实例带有一个绑定的foo()
方法。它在完成工作后死亡三,。第三个选项:每个'Foobar()`-实例一个'mydecorclass()`-实例
第三个也是最后一个选项结合了这两个方面的优点。它为每个
Foobar()
实例创建一个MyDecoratorClass()
实例保持
__obj_dict__
字典作为类变量,并实现__get__()
方法,如下所示:因此,每当调用
foo()
时,__get__()
方法()检查给定MyDecoratorClass()
对象的MyDecoratorClass()
实例是否已经生成(使用绑定方法)。如果是,则返回MyDecoratorClass()
-实例。否则,将生成一个新的类并将其存储在类字典MyDecoratorClass.__obj_dict__
()中(*)注意:这个
MyDecoratorClass.__obj_dict__
是一个类级字典,您必须在类定义中自己创建。(*)注意:在这里,
__get__()
方法总是在任何Foobar()
对象生成之前,在程序最开始生成的不朽MyDecoratorClass()
实例上调用。然而,重要的是__get__()
方法返回的内容警告
保留一个
__obj_dict__
来存储所有Foobar()
-实例有一个缺点。他们都不会死。根据具体情况,这可能是一个巨大的内存泄漏。因此,在应用选项3之前,请考虑一个合适的解决方案我也相信这种方法不允许递归。有待检验
四,。`foo()`中的代码与`MyDecoratorClass()`-实例之间的数据交换
让我们回到最初的问题:
如果实现第一个或第三个选项,则可以从
foo()
代码中访问任何MyDecoratorClass()
实例变量:使用
self.foo
实际访问MyDecoratorClass()
-实例。毕竟,MyDecoratorClass()
是self.foo
的包装器现在,如果您实现了选项1,您需要记住
decorator_var
是在所有Foobar()
-对象之间共享的。对于选项3,对于foo()
方法,每个Foobar()
-对象都有自己的MyDecoratorClass()
五,。更进一步:在几个方法上应用“@MyDecoratorClass”
选项3运行良好-直到我将
@MyDecoratorClass
应用于两种方法:现在试试这个:
一旦为
Foobar()
对象存在一个MyDecoratorClass()
-实例,您将始终访问这个现有实例来调用该方法。在我们的例子中,这个MyDecoratorClass()
-实例绑定到foo()
方法,因此bar()
永远不会执行解决方案是修改
MyDecoratorClass()
实例在__obj_dict__
中的存储方式。不要只为每个Foobar()
对象生成和存储一个MyDecoratorClass()
实例,而是为每个(Foobar()
,method
)组合生成和存储一个实例!这需要我们的装饰器有一个额外的参数,例如:带有参数的装饰器本质上意味着双重包装底层方法/函数!因此,让我们为此设计一个包装器:
现在使用这个包装器:
最后,我们需要重构
MyDecoratorClass
:让我们修改一下:在程序开始时,在任何
Foobar()
对象生成之前,Python解释器已经生成了两个MyDecoratorClass()
实例:一个用于unboundfoo()
,另一个用于unboundbar()
方法。这些是我们不朽的MyDecoratorClass()
实例,它们的__get__()
方法充当MyDecoratorClass()
工厂这里没什么新鲜事。这在我们做这些改变之前也发生过。然而,现在我们在工厂建成时存储
method_name
!通过这种方式,工厂方法__get__()
可以利用这些信息,不仅为每个Foobar()
对象生成和存储一个MyDecoratorClass()
实例,而且为(Foobar()
,"foo"
)和(Foobar()
,"bar"
)组合生成和存储一个MyDecoratorClass()
实例这是一个完整的独立程序:
相关问题 更多 >
编程相关推荐