如何解决如何从类装饰器的应用方法中访问变量?
注意
我已经根据@AlexHall和@ juanpa.arrivillaga编写的所有内容编译了一个答案。见下文。
我正在编写要用于方法的 Class Decorator 。这种做法并不常见,但是幸运的是,StackOverflow社区帮助完成了这一工作: Class decorator for methods from other class
现在,我想将事情进一步向前发展。调用的方法应该可以访问 Class Decorator 中的某些变量。这是我尝试过的一个独立的小示例:
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
从__get__()
调用MyDecoratorClass
方法。这将返回一个新的MyDecoratorClass()
实例,其decorator_var
初始化为None
。
是否可以通过decorator_var
方法访问foo()
?
解决方法
此答案基于@AlexHall和@ juanpa.arrivillaga在此处编写的所有内容: Class decorator for methods from other class。我要感谢他们的帮助。
让foo()
是类Foobar
的方法,并用foo()
实例修饰MyDecoratorClass()
。所以问题是:
在
foo()
中运行的代码可以从MyDecoratorClass()
实例访问变量吗?
要使其正常工作,我们首先需要考虑在程序过程中创建了多少个MyDecoratorClass()
实例。经过@AlexHall和@ juanpa.arrivillaga的大量研究和帮助,我得出结论,基本上有三种选择。让我们首先快速浏览一下它们,然后逐一进行深入研究。
概述
选项1
一个MyDecoratorClass()
实例在程序的最开始产生(非绑定)foo()
方法,它是用于调用foo()
的唯一实例。每次调用foo()
时,此MyDecoratorClass()
实例都会通过技巧将相应的Foobar()
实例插入方法中。
此方法允许在foo()
中运行的代码与MyDecoratorClass()
实例之间进行通信。但是,如果您的程序中有多个Foobar()
实例f1
和f2
,则f1.foo()
会对f2.foo()
的行为产生影响-因为它们共享相同的MyDecoratorClass()
实例!
选项2
再次在程序的开头为{unbound)MyDecoratorClass()
方法生成一个foo()
实例。但是,每次访问它时,它都会即时返回一个新的MyDecoratorClass()
实例。该实例是短暂的。该方法完成后会立即死亡。
此方法不允许在foo()
中运行的代码与MyDecoratorClass()
实例之间进行任何通信。假设您位于foo()
代码的内部中,并尝试从MyDecoratorClass()
实例访问变量:
@MyDecoratorClass
def foo(self):
# I want to access the 'decorator_var' right here:
value = self.foo.decorator_var
print(f"foo decorator_var = {value}")
您甚至尝试到达decorator_var
的那一刻,实际上就从MyDecoratorClass()
方法返回了一个新的__get__()
实例!
选项3
就像以前一样,一个MyDecoratorClass()
实例在程序的开头产生了一个(未绑定)foo()
方法。每次您访问它(意味着调用其__get__()
方法)时,它都会检查谁正在尝试访问。如果它是未知的Foobar()
对象,则__get__()
方法将返回具有绑定的MyDecoratorClass()
方法的新foo()
实例。如果它是已知的Foobar()
对象,则__get__()
方法将为该MyDecoratorClass()
对象检索之前产生的Foobar()
实例,然后将其返回。
此选项确保一对一的关系:每个Foobar()
对象仅获得一个MyDecoratorClass()
实例以包装其foo()
方法。每个MyDecoratorClass()
实例恰好属于一个Foobar()
对象(*)。非常整洁!
(*)此处,唯一的例外是在程序的最开始产生MyDecoratorClass()
实例的未绑定foo()
方法。但是此实例仅用于其__get__()
方法,该方法用作MyDecoratorClass()
-instance-factory:每个MyDecoratorClass()
实例产生,返回并存储一个Foobar()
-instance在其上调用foo()
。
让我们遍历每个选项。在此之前,我想强调一下,这三个选项之间的唯一实现差异是在__get__()
方法中!
1。第一选择:坚持一个实例
让MyDecoratorClass
作为类foo
中定义的方法Foobar
的装饰器:
import functools,types
class MyDecoratorClass:
def __init__(self,method) -> None:
functools.update_wrapper(self,method)
self.method = method
def __get__(self,obj,objtype) -> object:
return lambda *args,**kwargs: self.__call__(obj,*args,**kwargs)
def __call__(self,**kwargs) -> object:
return self.method(*args,**kwargs)
class Foobar:
def __init__(self):
pass
@MyDecoratorClass
def foo(self):
print(f"foo!")
即使您从未实例化Foobar()
,Python解释器仍会在程序的开头创建MyDecoratorClass
的一个实例。这是为UNBOUND方法foo()
创建的一个实例。选项1基本上意味着在程序的其余部分都坚持使用此MyDecoratorClass()
实例。为此,我们需要确保__get__()
方法不会重新实例化MyDecoratorClass()
。相反,它应该使现有的MyDecoratorClass()
出现以持有绑定方法:
┌────────────────────────────────────────────────────────────────────────┐
│ def __get__(self,objtype=None): │
│ return lambda *args,**kwargs) │
└────────────────────────────────────────────────────────────────────────┘
如您所见,self.method
从未绑定到Foobar()
实例。相反,它只是以这种方式出现。让我们做一个测试来证明这一点。实例化Foobar()
并调用foo()
方法:
>>> f = Foobar()
>>> f.foo()
方法调用本质上分为两个部分:
PART 1
f.foo
调用__get__()
方法。这在一个{ON MyDecoratorClass()中拥有一个未绑定的方法。然后,它返回其self.method
方法的lambda引用,但在{args元组中添加了__call__()
实例。
PART 2
Foobar()
之后的括号'()'
适用于返回的所有内容f.foo
。在这种情况下,我们知道__get__()
从一个且仅__get__()
实例(实际上是用lambda修改)返回了__call__()
方法,因此自然会调用该方法。在
MyDecoratorClass()
方法内部,我们像这样调用存储方法(原始foo):__call__()
虽然
self.method(*args,**kwargs)
是self.method
的未绑定版本,但是foo()
实例就在* args的第一个元素中!
简而言之:每次在Foobar()
实例上调用foo()
方法时,都将处理一个且唯一的Foobar()
实例,该实例具有未绑定的MyDecoratorClass()
方法引用,并使其似乎绑定到您在foo()
上调用的Foobar()
实例上!
一些额外的测试
您可以使用以下方法验证foo()
在self.method
方法中始终不受约束:
-
__call__()
-
hasattr(self.method,'__self__')
总是打印self.method.__self__ is not None
!
即使您在多个False
对象上调用__init__()
,也可以在MyDecoratorClass()
方法中添加打印语句以验证foo()
仅实例化一次。
注释
正如@AlexHall指出的那样:
Foobar()
与以下基本相同:
return lambda *args,**kwargs)
那是因为在对象上加上括号return lambda *args,**kwargs: self(obj,**kwargs)
与调用其'()'
方法基本相同。您还可以将return语句替换为:
__call__()
甚至:
return functools.partial(self,obj)
2。第二个选择:每次调用都创建一个新实例
在第二个选项中,我们在每次return types.MethodType(self,obj)
调用时都实例化一个新的MyDecoratorClass()
实例:
foo()
此 ┌─────────────────────────────────────────────────────────────┐
│ def __get__(self,objtype=None): │
│ return type(self)(self.method.__get__(obj,objtype)) │
└─────────────────────────────────────────────────────────────┘
实例寿命很短。我检查了MyDecoratorClass()
方法中的打印语句,发现它在foo()结束后立即得到了垃圾回收!
因此,如果您在多个__del__()
实例上调用foo()
,则会发生以下情况:
Foobar()
与往常一样,未绑定>>> f1 = Foobar()
>>> f2 = Foobar()
>>> f1.foo()
>>> f2.foo()
方法的MyDecoratorClass()
实例在任何foo()
对象诞生之前就产生。它仍然有效,直到程序结束。我们将此称为不朽的Foobar()
实例。
调用MyDecoratorClass()
时,就创建了一个新的短暂foo()
实例。请记住,MyDecoratorClass()
调用实质上分为两个步骤:
STEP 1
foo()
对不朽的f1.foo
调用__get__()
方法- 实例(目前没有其他对象!)。与选项1不同,我们现在生成一个新的MyDecoratorClass()
并将其绑定的MyDecoratorClass()
方法作为参数传递给它。这个新的foo()
实例将返回。
STEP 2
MyDecoratorClass()
之后的括号'()'
适用于返回的所有内容f1.foo
。 我们知道这是一个新的__get__()
实例,因此括号MyDecoratorClass()
会调用其'()'
方法。在__call__()
方法内部,我们仍然得到以下信息:__call__()
但是这一次,在args元组中没有隐藏任何
self.method(*args,**kwargs)
对象,但是现在已绑定了存储的方法-因此无需这样做!
Foobar()
完成,并且短暂的f1.foo()
实例被垃圾回收(您可以在MyDecoratorClass()
方法中使用打印语句对其进行测试)。
现在该__del__()
了。当短暂的f2.foo()
实例死亡时,它将对不朽实例调用MyDecoratorClass()
方法(还有什么?)。在此过程中,将创建一个NEW实例,并重复该循环。
简而言之:每个__get__()
调用都始于对不朽foo()
实例的__get__()
方法的调用。该对象始终返回一个新的但短暂的MyDecoratorClass()
实例,该实例具有绑定的MyDecoratorClass()
方法。它会在完成工作后死亡。
3。第三选择:每个`Foobar()`实例一个`MyDecoratorClass()`实例
第三个也是最后一个选项结合了两个方面的优势。它会为每个foo()
实例创建一个MyDecoratorClass()
实例。
将Foobar()
字典保留为类变量,并像这样实现__obj_dict__
方法:
__get__()
因此,每当调用 ┌───────────────────────────────────────────────────────────────┐
│ def __get__(self,objtype): │
│ if obj in MyDecoratorClass.__obj_dict__: │
│ # Return existing MyDecoratorClass() instance for │
│ # the given object,and make sure it holds a bound │
│ # method. │
│ m = MyDecoratorClass.__obj_dict__[obj] │
│ assert m.method.__self__ is obj │
│ return m │
│ # Create a new MyDecoratorClass() instance WITH a bound │
│ # method,and store it in the dictionary. │
│ m = type(self)(self.method.__get__(obj,objtype)) │
│ MyDecoratorClass.__obj_dict__[obj] = m │
│ return m │
└───────────────────────────────────────────────────────────────┘
时,foo()
方法() 都会检查一个__get__()
实例是否为已为给定的MyDecoratorClass()
对象生成(使用绑定方法)。如果是,则返回该Foobar()
实例。否则,将生成一个新的,并将其存储在类字典MyDecoratorClass()
()中。
(*)注意:此MyDecoratorClass.__obj_dict__
是您必须在类定义中创建的类级字典。
(*)注意:同样在这里,MyDecoratorClass.__obj_dict__
方法总是在程序的开始处生成的永生__get__()
实例上被调用-在任何{{1 }}-对象诞生了。但是,重要的是MyDecoratorClass()
方法返回。
警告
保留Foobar()
来存储所有__get__()
实例有一个缺点。他们都不会死。根据情况,这可能是巨大的内存泄漏。因此,在应用选项3之前,请考虑一个适当的解决方案。
我也相信这种方法不允许递归。要测试。
4. foo()中的代码与MyDecoratorClass()实例之间的数据交换
让我们回到最初的问题:
让
__obj_dict__
是类Foobar()
的方法,并用foo()
实例修饰Foobar
。在foo()
中运行的代码能否从MyDecoratorClass()
实例访问变量?
如果实现 first 或 third选项,则可以从foo()
代码中访问任何MyDecoratorClass()
实例变量:
MyDecoratorClass()
在foo()
实际访问@MyDecoratorClass
def foo(self):
value = self.foo.decorator_var
print(f"foo decorator_var = {value}")
实例的情况下。毕竟,self.foo
是MyDecoratorClass()
的包装器!
现在,如果实现选项1 ,则需要记住MyDecoratorClass()
在所有self.foo
对象之间共享。对于选项3 ,每个decorator_var
对象都有自己的Foobar()
用于Foobar()
方法。
5.进一步:在几种方法上应用“ @MyDecoratorClass”
选项3 工作正常-直到我在两种方法上应用MyDecoratorClass()
为止:
foo()
现在尝试:
@MyDecoratorClass
一旦class Foobar:
def __init__(self):
pass
@MyDecoratorClass
def foo(self):
print(f"foo!")
@MyDecoratorClass
def bar(self):
print("bar!")
对象存在一个>>> f = Foobar()
>>> f.foo()
>>> f.bar()
foo!
foo!
实例,您将始终访问该现有实例以调用该方法。在我们的例子中,此MyDecoratorClass()
实例已绑定到Foobar()
方法,因此MyDecoratorClass()
永远不会执行!
解决方案是修改在foo()
中存储bar()
实例的方式。不要只是为每个MyDecoratorClass()
对象生成并存储一个__obj_dict__
实例,而是每个(MyDecoratorClass()
,Foobar()
)组合一个实例!这需要为装饰器添加一个额外的参数,例如:
Foobar()
带有参数的装饰器实质上意味着将基础方法/函数双重包装!因此,我们为此设计一个包装器:
method
现在使用此包装器:
@MyDecoratorClass("foo")
def foo(self):
print(f"foo!")
@MyDecoratorClass("bar")
def bar(self):
print("bar!")
最后,我们需要重构def my_wrapper(name="unknown"):
def _my_wrapper_(method):
return MyDecoratorClass(method,name)
return _my_wrapper_
:
class Foobar:
def __init__(self):
pass
@my_wrapper("foo")
def foo(self):
print(f"foo!")
@my_wrapper("bar")
def bar(self):
print("bar!")
让我们进行修改:在程序开始时,在任何MyDecoratorClass
对象诞生之前,Python解释器已经生成了两个import functools,types
class MyDecoratorClass:
__obj_dict__ = {}
def __init__(self,method,name="unknown") -> None:
functools.update_wrapper(self,method)
self.method = method
self.method_name = name
return
def __get__(self,objtype) -> object:
if obj in MyDecoratorClass.__obj_dict__.keys():
# Return existing MyDecoratorClass() instance for
# the given object-method_name combination,and make
# sure it holds a bound method.
if self.method_name in MyDecoratorClass.__obj_dict__[obj].keys():
m = MyDecoratorClass.__obj_dict__[obj][self.method_name]
return m
else:
# Create a new MyDecoratorClass() instance WITH a bound
# method,and store it in the dictionary.
m = type(self)(self.method.__get__(obj,objtype),self.method_name)
MyDecoratorClass.__obj_dict__[obj][self.method_name] = m
return m
# Create a new MyDecoratorClass() instance WITH a bound
# method,and store it in the dictionary.
m = type(self)(self.method.__get__(obj,self.method_name)
MyDecoratorClass.__obj_dict__[obj] = {}
MyDecoratorClass.__obj_dict__[obj][self.method_name] = m
return m
def __call__(self,**kwargs)
def __del__(self):
print(f"{id(self)} garbage collected!")
实例:一个用于未绑定的Foobar()
,另一个用于未绑定的MyDecoratorClass()
方法。这些是我们不朽的foo()
实例,其bar()
方法用作MyDecoratorClass()
工厂。
这里没有新内容。在我们进行这些更改之前,也发生了这种情况。但是,现在我们在工厂建成时存储__get__()
!这样,工厂方法MyDecoratorClass()
可以利用这些信息来生成和存储每个method_name
对象,而不只是一个__get__()
实例,还可以为({{1} MyDecoratorClass()
和(Foobar()
,Foobar()
)组合!
这是完整的自包含程序:
"foo"
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。