本文介绍了如何从类装饰器的应用方法中访问变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 注意 我已经根据@AlexHall和@ juanpa.arrivillaga编写的所有内容编写了一个答案。见下文。 我正在编写要应用的 Class Decorator 在方法上。这种做法并不常见,但是幸运的是,StackOverflow社区帮助完成了这项工作: 用于其他类方法的类装饰器 现在,我想进一步向前迈进。调用的方法应该可以访问 Class Decorator 中的某些变量。这是我尝试过的一个独立的小示例: import functools class MyDecoratorClass: def __init __(self,method)-> None: functools.update_wrapper(self,method) self.method =方法 self.decorator_var = None 返回 def __get __(self, obj,objtype)->对象:返回类型(self)(self.method .__ get __(obj,objtype)) def __call __(self,* args,** kwargs)->对象: self.decorator_var = hello world; retval = self.method(* args,** kwargs) return retval class Foobar: def __init __(self): pass @MyDecoratorClass def foo(self):#我想在这里访问'decorator_var': 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 __()方法/ code>。这会返回一个新的 MyDecoratorClass()实例,该实例的 decorator_var 初始化为 None 。 有没有一种方法可以从 foo()中访问 decorator_var 方法?解决方案 此答案基于@AlexHall和@ juanpa.arrivillaga写在这里: 其他类方法的类装饰器。我要感谢他们的帮助。 让 foo()是类 Foobar 中的方法,并用 foo() c $ c> MyDecoratorClass() -instance。所以问题是: 在 foo()中运行的代码是否可以从 MyDecoratorClass() -instance? 要使其正常工作,我们首先需要考虑多少个 MyDecoratorClass()实例是在程序过程中创建的。经过@AlexHall和@ juanpa.arrivillaga的大量研究和帮助,我得出结论,基本上有三种选择。让我们首先快速浏览一下它们,然后逐个进行深入研究。 概述 选项1 一个$ MyDecoratorClass() -instance在程序的最开始产生($ un $) foo()方法,它是用于调用 foo()的唯一实例。每次调用 foo()时,此 MyDecoratorClass() -instance会插入相应的 Foobar ()实例中的技巧。 这种方法允许在 foo()和 MyDecoratorClass()实例。但是,如果您有几个 Foobar()实例 f1 和 f2 在您的程序中,然后 f1.foo()可能会影响 f2.foo()行为-因为它们共享相同的 MyDecoratorClass() -instance! 选项2 再次在程序的开头为 MyDecoratorClass() -instance c> foo()方法。但是,每次访问它时,它都会动态返回一个新的 MyDecoratorClass()实例。该实例是短暂的。它会在完成该方法后立即消失。 这种方法不允许在 foo()中运行的代码之间进行任何通信。 和 MyDecoratorClass()实例。假设您在内部 foo()代码中,并且尝试从 MyDecoratorClass()访问变量code>-实例: @MyDecoratorClass def foo(self):#我想在这里访问'decorator_var': value = self.foo.decorator_var print(f foo装饰器_var = {value}) 甚至在尝试达到 decorator_var 的那一刻,您基本上都会获得一个新的 MyDecoratorClass ()实例从 __ get __()方法返回! 选项3 和以前一样,一个 MyDecoratorClass()实例在程序的开头就产生了( unbound) foo()方法。每次您访问它时(这意味着调用其 __ get __()方法),它将检查谁正在尝试访问。如果它是未知的 Foobar()对象,则 __ get __()方法返回一个新的 MyDecoratorClass() -instance和绑定的 foo() -method。如果它是已知的 Foobar()对象,则 __ get __()方法检索 MyDecoratorClass ()实例,它已经为该 Foobar()对象生成了,然后返回它。 此选项可确保一对一关系:每个 Foobar()对象仅获得一个 MyDecoratorClass ()实例包装其 foo()方法。每个 MyDecoratorClass()实例恰好属于一个 Foobar() -object (*)。 (*) MyDecoratorClass()实例在程序开始时就产生了 foo()方法是唯一的例外。但是此实例仅用于其 __ get __()方法,该方法用作 MyDecoratorClass() -instance-factory :生成,返回并存储每个实例上的一个 MyDecoratorClass()实例c> foo()已被调用。 让我们遍历每个选项。在此之前,我想强调一下,这三个选项之间的唯一实现差异是 __ get __()方法! 1。第一个选择:坚持一个实例 让 MyDecoratorClass 成为方法 foo 在类 Foobar 中定义: import functools,类型 class MyDecoratorClass: def __init __(self,method)->无: functools.update_wrapper(self,method) self.method =方法 def __get __(self,obj,objtype)-> object: return lambda * args,** kwargs:self .__ call __(obj,* args,** kwargs) def __call __(self,* args,** kwargs)-> ;对象: return self.method(* args,** kwargs) class Foobar: def __init __(self): pass @MyDecoratorClass def foo(自身): print(f" foo!") 即使您从未实例化 Foobar(),Python解释器仍会在该实例中创建 MyDecoratorClass 的一个实例。程序的开始。这个实例是为UNBOUND方法 foo()创建的。选项1基本上意味着在该程序的其余部分都使用此 MyDecoratorClass()实例。为此,我们需要确保 __ get __()方法不会重新实例化 MyDecoratorClass() 。相反,它应该使现有的 MyDecoratorClass()出现以持有绑定方法: ──────────────────────────────── ────────────────────__│def __get __(self,obj,objtype = None):││返回lambda * args,** kwargs:self .__ call __(obj,* args,** kwargs)│└─────────────────── ────────────────────┘b 如您所见, self.method 永远不会绑定到 Foobar() -instance。相反,它只是以这种方式出现。让我们做一个测试来证明这一点。实例化 Foobar()并调用 foo()方法: >>> f = Foobar()>> f.foo() 方法调用基本上由两部分组成: PART 1 f.foo 调用 __ get __()方法。仅在一个 MyDecoratorClass()实例上调用此实例,该实例在 self.method 中拥有一个未绑定方法。然后,它返回其 __ call __()方法的lambda引用,但将 Foobar()实例添加到* args元组。 PART 2 括号'() f.foo 之后的'应用于返回的任何 __ get __()。在这种情况下,我们知道 __ get __()从一个且仅 __ call __()方法$ c> MyDecoratorClass()实例(实际上是用lambda修改的),因此自然会调用该方法。 在 __ call__内部()方法,我们像这样调用存储方法(原始foo): self.method(* args,** kwargs) 而 self.method 是 foo()的未绑定版本, Foobar()实例位于第一个* args! 简而言之:每次您在 foo()方法上调用一个 Foobar()实例,您处理一个且仅 MyDecoratorClass() -instance,该实例具有未绑定的 foo()方法引用,并使其绑定到您调用的 Foobar()实例上。 c $ c> foo()上! 一些额外的测试 您可以验证 self.method 在 __ call __()中始终是未绑定的方法,其中: hasattr(self.method,'__self __') self.method .__ self__不是None 总是打印 False ! 您还可以在 __ init __()中放入打印语句。 方法来验证 MyDecoratorClass()仅实例化一次,即使您调用 foo()在多个 Foobar()对象上。 注释 如@ AlexHall指出: 返回lambda * args,** kwargs:self .__ call __(obj,* args ,** kwargs) 本质上与以下内容相同: 返回lambda * args,** kwargs:self(obj,* args,** kwargs) 那是因为应用括号’(()’本质上与调用其 __ call __()方法相同。您还可以将return语句替换为: return functools.partial(self,obj) 甚至: 返回类型。MethodType(self,obj) 2 。第二个选择:每次调用都创建一个新实例 在第二个选项中,我们在每个实例上实例化一个新的 MyDecoratorClass() -instance每次 foo()调用: ┌──────── ──────────────────────────────────────────────── ─┐│def __get __(self,obj,objtype = None):││返回类型(self)(self.method .__ get __(obj,objtype))│└── ──────────────────────────────────────────────── ──────────┘ 此 MyDecoratorClass() __ del __()方法中的打印语句,发现它在foo()结束后立即获取垃圾! 因此,如果您在多个 Foobar()实例上调用 foo(),会发生以下情况: >>> f1 = Foobar()>> f2 = Foobar()>> f1.foo()>> f2.foo() 和往常一样, MyDecoratorClass()未绑定 foo()方法的-instance在任何 Foobar()对象诞生之前就产生。它仍然有效,直到程序结束。让我们称其为不朽 MyDecoratorClass() -instance 。 调用 foo(),您将创建一个新的短暂的 MyDecoratorClass() -instance。请记住, foo()的调用实际上分两步进行: STEP 1 f1.foo 对不朽的 MyDecoratorClass()调用 __ get __()方法-实例(目前没有其他实例!)。与选项1不同,我们现在生成一个新的 MyDecoratorClass()并将其绑定的 foo()方法作为参数传递。返回此新的 MyDecoratorClass()实例。 STEP 2 f1.foo 之后的括号'()'适用于 __ get __()返回。 我们知道这是一个新的 MyDecoratorClass()实例,因此括号'()'调用它 __ call __()方法。在 __ call __()方法内部,我们仍然得到以下信息: self.method(* args,** kwargs) 但是这次没有 Foobar()-隐藏在args元组中的对象,但是现在已绑定了存储的方法-因此无需这样做! f1.foo()完成,并且寿命很短的 MyDecoratorClass() -instance会收集垃圾(您可以使用 __ del __()方法中的打印语句对此进行测试。 是时候了 f2.foo()现在。当短暂的 MyDecoratorClass()实例死亡时,它将对不朽的实例调用 __ get __()方法(还有什么?)。在此过程中,将创建一个NEW实例,并重复该循环。 简而言之:每次 foo()调用均始于调用不朽的 MyDecoratorClass()实例上的 __ get __()方法。该对象始终返回一个新的但短暂的 MyDecoratorClass()实例,并绑定一个 foo()-方法。它会在完成工作后死亡。 3。第三选项:每个`Foobar()`实例一个`MyDecoratorClass()`实例 第三个也是最后一个选项结合了两个方面的优点。它为每个 Foobar()实例创建一个 MyDecoratorClass()实例。 将 __ obj_dict __ 字典保留为类变量,并实现 __ get __()方法,如下所示: ┌──────────────────────── ────────────────────┐│def __get __(self,obj,objtype):││如果obj在MyDecoratorClass中。__obj_dict__:││#返回││#给定对象的现有MyDecoratorClass()实例,并确保其持有绑定││#方法。 ││m = MyDecoratorClass .__ obj_dict __ [obj]││断言m.method .__ self__是obj││return m││#创建一个新的MyDecoratorClass()实例使用绑定的││#方法,并将其存储在字典中。 ││m =类型(self)(self.method .__ get __(obj,objtype))││MyDecoratorClass .__ obj_dict __ [obj] = m││返回m│└────────────────────────────── ──────────────┘ 所以每当 foo ()被调用, __ get __()方法()检查是否 MyDecoratorClass() -instance已经为给定的 Foobar()对象生成(使用绑定方法)。如果是,则返回该 MyDecoratorClass()实例。否则,将生成一个新的并存储在类字典 MyDecoratorClass .__ obj_dict __ ()中。 (*)注意:此 MyDecoratorClass .__ obj_dict __ 是您必须在类定义中创建自己的类级字典。 (*)注意:同样在这里, __ get __()方法总是在不朽的 MyDecoratorClass()实例,该实例在程序的开始时生成-在任何 Foobar()对象诞生之前。但是,重要的是 __ get __()方法返回。 警告 保留 __ obj_dict __ 来存储所有 Foobar()-实例有一个缺点。他们都不会死。根据情况,这可能是巨大的内存泄漏。因此,在应用选项3之前,请考虑一个适当的解决方案。 我也相信这种方法不允许递归。 4.在foo()和MyDecoratorClass()中的代码之间进行数据交换。 -instance 让我们回到最初的问题: 让 foo()是类 Foobar 中的方法,并让 foo()用 MyDecoratorClass() -instance。在 foo()中运行的代码能否从 MyDecoratorClass() -instance实例访问变量? 如果实现 first 或 third选项,则可以访问任何 MyDecoratorClass() -foo中的实例变量代码: @MyDecoratorClass def foo(self):值= self.foo.decorator_var print(f foo装饰器_var = {value}) 具有 self.foo 实际上访问 MyDecoratorClass () -instance。毕竟 MyDecoratorClass()是 self.foo 的包装器! 现在,如果实现选项1 ,则需要记住, decorator_var 在所有 Foobar()中共享-对象。对于选项3 ,每个 Foobar()对象都有自己的 MyDecoratorClass() foo()方法。 5.再走一步:在几种方法上应用`@ MyDecoratorClass` 选项3 效果很好-直到我应用 @MyDecoratorClass 两种方法: class Foobar: def __init __(self): pass @MyDecoratorClass def foo(自己): print(f foo!) @MyDecoratorClass def bar(自己) ): print( bar!) 现在尝试以下操作: >>> f = Foobar()>> f.foo()>> f.bar() foo! foo! 一旦 MyDecoratorClass()实例存在, c $ c> Foobar()对象,您将始终访问此现有对象以调用该方法。在我们的例子中,此 MyDecoratorClass()实例绑定到 foo()方法,因此 bar()永远不会执行! 解决方案是修改我们存储 MyDecoratorClass() __ obj_dict __ 中的c> -instance。不要只为每个 Foobar()对象生成并存储一个 MyDecoratorClass()实例,而是为每个实例存储一个实例( Foobar(),方法)组合!这需要为装饰器添加一个额外的参数,例如: @MyDecoratorClass( foo) def foo(自已): print(f foo !!) @MyDecoratorClass( bar) def bar(自已):打印( bar!) 带有参数的修饰符本质上意味着将基础方法/函数双重包装!因此,我们为此设计一个包装器: def my_wrapper(name = unknown): def _my_wrapper_(方法):返回MyDecoratorClass(方法,名称)返回_my_wrapper_ 使用此包装器: class Foobar: def __init __(self): pass @my_wrapper( foo) def foo(自身): print(f foo!) @my_wrapper( ; bar) def bar(自身): print( bar!) 最后,我们需要重构 MyDecoratorClass : 导入功能工具,输入 class MyDecoratorClass: __obj_dict__ = {} def __init __(self,method,name = unknown)->无: functools.update_wrapper(self,method) self.method =方法 self.method_name =名称返回 def __get __(self, obj,objtype)->对象:如果MyDecoratorClass中的obj .__ obj_dict __。keys():#返回的现有MyDecoratorClass()实例#给定的object-method_name组合,并使#确定拥有一个绑定方法。 ,如果MyDecoratorClass中的self.method_name .__ obj_dict __ [obj] .keys():m = MyDecoratorClass .__ obj_dict __ [obj] [self.method_name] 返回m 否则:#使用绑定的#方法创建一个新的MyDecoratorClass()实例,并将其存储在字典中。 m = type(self)(self.method .__ get __(obj,objtype),self.method_name) MyDecoratorClass .__ obj_dict __ [obj] [self.method_name] = m 返回m #使用绑定的#方法创建一个新的MyDecoratorClass()实例,并将其存储在字典中。 m = type(self)(self.method .__ get __(obj,objtype),self.method_name) MyDecoratorClass .__ obj_dict __ [obj] = {} MyDecoratorClass .__ obj_dict __ [obj] [self [method_name] = m return m def __call __(self,* args,** kwargs)->对象: return self.method(* args,** kwargs) def __del __(self): print(f {id(self)} ) 让我们修改一下:在程序开始时,在任何 Foobar之前() -object诞生了,Python解释器已经生成了两个 MyDecoratorClass() -instances:一个用于未绑定的 foo ()和另一个用于未绑定的 bar()方法。这些是我们不朽的 MyDecoratorClass()实例,其 __ get __()方法用作 MyDecoratorClass( )工厂。 这里没有新内容。这也是在我们进行这些更改之前发生的。但是,现在我们在工厂建立时存储 method_name !这样,工厂方法 __ get __()可以利用该信息来生成和存储不仅仅是一个 MyDecoratorClass() Foobar()对象>个实例,但一个对象(( Foobar(), " foo" )和( Foobar(), bar )组合! 这是完整的自包含程序: import functools ,类型 class MyDecoratorClass: __obj_dict__ = {} def __init __(self,method,name = unknown)->无: functools.update_wrapper(self,method) self.method =方法 self.method_name =名称返回 def __get __(self, obj,objtype)->对象:如果MyDecoratorClass中的obj .__ obj_dict __。keys():#返回的现有MyDecoratorClass()实例#给定的object-method_name组合,并使#确定拥有一个绑定方法。 ,如果MyDecoratorClass中的self.method_name .__ obj_dict __ [obj] .keys():m = MyDecoratorClass .__ obj_dict __ [obj] [self.method_name] 返回m 否则:#使用绑定的#方法创建一个新的MyDecoratorClass()实例,并将其存储在字典中。 m = type(self)(self.method .__ get __(obj,objtype),self.method_name) MyDecoratorClass .__ obj_dict __ [obj] [self.method_name] = m 返回m #使用绑定的#方法创建一个新的MyDecoratorClass()实例,并将其存储在字典中。 m = type(self)(self.method .__ get __(obj,objtype),self.method_name) MyDecoratorClass .__ obj_dict __ [obj] = {} MyDecoratorClass .__ obj_dict __ [obj] [self [method_name] = m return m def __call __(self,* args,** kwargs)->对象: return self.method(* args,** kwargs) def __del __(self): print(f {id(self)} )) def my_wrapper(name =未知): def _my_wrapper_(method): return MyDecoratorClass(method,name) return _my_wrapper_ class Foobar: def __init __(self): pass @my_wrapper( foo) def foo(自身): print(f foo !!) @my_wrapper( bar) def bar(自身): print( bar!) NOTEI've compiled an answer based on everything written by @AlexHall and @juanpa.arrivillaga. See below.I'm writing a Class Decorator to be applied on methods. This practice is quite uncommon, but luckily the StackOverflow community helped to get it done:Class decorator for methods from other classNow I want to take things one step further. The method being invoked should have access to some variables from the Class Decorator. Here is a small self-contained example of what I've tried:import functoolsclass 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 retvalclass 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}")Let's test:>>> f = Foobar()>>> f.foo()foo decorator_var = NoneAs you can see, the variable decorator_var is not accessed correctly. I believe this happens right at the moment I'm trying to access the variable:value = self.foo.decorator_varAccessing self.foo invokes the __get__() method from MyDecoratorClass. This returns a new MyDecoratorClass()-instance which has its decorator_var initialized to None.Is there a way I can access decorator_var from within the foo() method? 解决方案 This answer is based on everything @AlexHall and @juanpa.arrivillaga wrote here:Class decorator for methods from other class. I want to thank them for their help.Let foo() be a method from class Foobar, and let foo() be decorated with a MyDecoratorClass()-instance. So the question is:Can the code running in foo() access variables from the MyDecoratorClass()-instance?For this to work properly, we need to think first about how many MyDecoratorClass() instances get created over the course of the program. After lots of research and help from @AlexHall and @juanpa.arrivillaga, I concluded that there are basically three options. Let's first glance over them rapidly and then investigate them profoundly one-by-one.OverviewOPTION 1One MyDecoratorClass()-instance spawns at the very beginning of your program for the (unbound) foo() method, and it's the only instance used to invoke foo(). Each time you invoke foo(), this MyDecoratorClass()-instance inserts the corresponding Foobar() instance in the method through a trick.This approach allows for communication between the code running in foo() and the MyDecoratorClass()-instance. However, if you've got several Foobar()-instances f1 and f2 in your program, then f1.foo() can have an impact on the way f2.foo() behaves - because they share the same MyDecoratorClass()-instance!OPTION 2Again one MyDecoratorClass()-instance spawns at the very beginning of the program for the (unbound) foo() method. However, each time you access it, it returns a NEW MyDecoratorClass()-instance on the fly. This instance is short-lived. It dies immediately after completing the method.This approach doesn't allow for any communication between the code running in foo() and the MyDecoratorClass()-instance. Imagine you're inside the foo() code and you try to access a variable from the MyDecoratorClass()-instance:@MyDecoratorClassdef foo(self): # I want to access the 'decorator_var' right here: value = self.foo.decorator_var print(f"foo decorator_var = {value}")The moment you even try to reach decorator_var, you essentially get a new MyDecoratorClass()-instance returned from the __get__() method!OPTION 3Just like before, one MyDecoratorClass()-instance spawns at the very beginning of the program for the (unbound) foo() method. Each time you access it (which implies calling its __get__() method), it checks who is trying to access. If it's an unknown Foobar()-object, the __get__() method returns a NEW MyDecoratorClass()-instance with a bound foo()-method. If it's a known Foobar()-object, the __get__() method retrieves the MyDecoratorClass()-instance it has spawn before for that very Foobar()-object, and returns it.This option ensures a one-to-one relationship: each Foobar()-object gets exactly one MyDecoratorClass()-instance to wrap its foo() method. And each MyDecoratorClass()-instance belongs to exactly one Foobar()-object(*). Very neat!(*) The MyDecoratorClass()-instance spawn at the very beginning of the program for the unbound foo() method is the only exception here. But this instance gets only used for its __get__() method, which serves as a MyDecoratorClass()-instance-factory: spawning, returning and storing exactly one MyDecoratorClass()-instance per Foobar() instance upon which foo() has been invoked.Let's go through each of the options. Before doing so, I'd like to stress that the only implementation difference between the three options is in the __get__() method!1. FIRST OPTION: Stick to one instanceLet MyDecoratorClass be a decorator for method foo defined in class Foobar:import functools, typesclass 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, *args, **kwargs) -> object: return self.method(*args, **kwargs)class Foobar: def __init__(self): pass @MyDecoratorClass def foo(self): print(f"foo!")Even if you never instantiate Foobar(), the Python interpreter will still create ONE instance of MyDecoratorClass in the very beginning of your program. This one instance is created for the UNBOUND method foo(). OPTION 1 basically implies to stick to this MyDecoratorClass()-instance for the rest of the program. To achieve this, we need to make sure that the __get__() method doesn't re-instantiate MyDecoratorClass(). Instead, it should make the existing MyDecoratorClass() APPEAR to hold a bound method: ┌────────────────────────────────────────────────────────────────────────┐ │ def __get__(self, obj, objtype=None): │ │ return lambda *args, **kwargs: self.__call__(obj, *args, **kwargs) │ └────────────────────────────────────────────────────────────────────────┘As you can see, self.method NEVER gets bound to a Foobar()-instance. Instead, it just appears that way. Let's do a test to prove this. Instantiate Foobar() and invoke the foo() method:>>> f = Foobar()>>> f.foo()The method invocation essentially exists of two parts:PART 1f.foo invokes the __get__() method. This gets invoked on the ONE AND ONLY MyDecoratorClass() instance, which holds an unbound method in self.method. It then returns a lambda-reference to its __call__() method, but with the Foobar() instance added to the *args tuple.PART 2The parenthesis '()' after f.foo are applied on WHATEVER __get__() returned. In this case, we know that __get__() returned the __call__() method from the ONE AND ONLY MyDecoratorClass() instance (actually a bit modified with lambda), so naturally that method gets invoked.Inside the __call__() method, we invoke the stored method (the original foo) like so:self.method(*args, **kwargs)While self.method is an unbound version of foo(), the Foobar() instance is right there in the first element of *args!In short: Each time you invoke the foo() method on a Foobar()-instance, you deal with the ONE AND ONLY MyDecoratorClass()-instance which holds an unbound foo() method-reference and makes it appear to be bound to the very Foobar()-instance you invoked foo() on!Some extra testsYou can verify that self.method is always unbound in the __call__() method with:hasattr(self.method, '__self__')self.method.__self__ is not Nonewhich always prints False!You can also put a print-statement in the __init__() method to verify that MyDecoratorClass() gets instantiated only once, even if you invoke foo() on multiple Foobar() objects.NotesAs @AlexHall pointed out, this:return lambda *args, **kwargs: self.__call__(obj, *args, **kwargs)is essentially the same as:return lambda *args, **kwargs: self(obj, *args, **kwargs)That's because applying parenthesis '()' on an object is essentially the same as invoking its __call__() method. You can also replace the return statement with:return functools.partial(self, obj)or even:return types.MethodType(self, obj)2. SECOND OPTION: Create a new instance per invocationIn this second option, we instantiate a new MyDecoratorClass()-instance upon each and every foo() invocation: ┌─────────────────────────────────────────────────────────────┐ │ def __get__(self, obj, objtype=None): │ │ return type(self)(self.method.__get__(obj, objtype)) │ └─────────────────────────────────────────────────────────────┘This MyDecoratorClass()-instance is very short-lived. I've checked with a print-statement in the __del__() method that it gets garbage collected right after foo() ends!So this is what happens if you invoke foo() on several Foobar() instances:>>> f1 = Foobar()>>> f2 = Foobar()>>> f1.foo()>>> f2.foo()As always, a MyDecoratorClass()-instance for the unbound foo() method gets spawn before any Foobar()-object gets born. It remains alive until the end of the program. Let's call this one the immortal MyDecoratorClass()-instance.The moment you invoke foo(), you create a new short-lived MyDecoratorClass()-instance. Remember, the foo() invocation essentially happens in two steps:STEP 1f1.foo invokes the __get__() method on the immortal MyDecoratorClass()-instance (there is no other at this point!). Unlike OPTION 1, we now spawn a NEW MyDecoratorClass() and pass it a bound foo() method as argument. This new MyDecoratorClass()-instance gets returned.STEP 2The parenthesis '()' after f1.foo are applied on WHATEVER __get__() returned.We know it's a NEW MyDecoratorClass()-instance, so the parenthesis '()' invoke its __call__() method. Inside the __call__() method, we still got this:self.method(*args, **kwargs)This time however, there is NO Foobar()-object hidden in the args tuple, but the stored method is bound now - so there is no need for that!f1.foo() completes and the short-lived MyDecoratorClass()-instance gets garbage collected (you can test this with a print-statement in the __del__() method).It's time for f2.foo() now. As the short-lived MyDecoratorClass()-instance died, it invokes the __get__() method on the immortal one (what else?). In the process, a NEW instance gets created and the cycle repeats.In short: Each foo() invocation starts with calling the __get__() method on the immortal MyDecoratorClass()-instance. This object always returns a NEW but short-lived MyDecoratorClass()-instance with a bound foo()-method. It dies after completing the job.3. THIRD OPTION: One `MyDecoratorClass()`-instance per `Foobar()`-instanceThe third and last option combines the best of both worlds. It creates one MyDecoratorClass()-instance per Foobar()-instance.Keep an __obj_dict__ dictionary as a class-variable and implement the __get__() method like so: ┌───────────────────────────────────────────────────────────────┐ │ def __get__(self, obj, 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 │ └───────────────────────────────────────────────────────────────┘So whenever foo() gets invoked, the __get__() method() checks if a MyDecoratorClass()-instance was already spawn (with bound method) for the given Foobar()-object. If yes, that MyDecoratorClass()-instance gets returned. Otherwise, a new one gets spawn and stored in the class dictionary MyDecoratorClass.__obj_dict__().(*) Note: This MyDecoratorClass.__obj_dict__ is a class-level dictionary you have to create yourself in the class definition.(*) Note: Also here, the __get__() method always gets invoked on the immortal MyDecoratorClass()-instance that is spawn at the very beginning of the program - before any Foobar()-objects were born. However, what's important is what the __get__() method returns.WARNINGKeeping an __obj_dict__ to store all the Foobar()-instances has a downside. None of them will ever die. Depending on the situation, this can be a huge memory leak. So think about a proper solution before applying OPTION 3.I also believe this approach doesn't allow recursion. To be tested. 4. Data exchange between the code in `foo()` and the `MyDecoratorClass()`-instanceLet's go back to the initial question:Let foo() be a method from class Foobar, and let foo() be decorated with a MyDecoratorClass()-instance. Can the code running in foo() access variables from the MyDecoratorClass()-instance?If you implement the first or the third option, you can access any MyDecoratorClass()-instance variable from within the foo() code:@MyDecoratorClassdef foo(self): value = self.foo.decorator_var print(f"foo decorator_var = {value}")With self.foo actually accessing the MyDecoratorClass()-instance. After all, MyDecoratorClass() is a wrapper for self.foo!Now if you implement option 1, you need to keep in mind that decorator_var is shared amongst all Foobar()-objects. For option 3, each Foobar()-object has its own MyDecoratorClass() for the foo() method. 5. One step further: apply `@MyDecoratorClass` on several methodsOption 3 worked fine - until I applied @MyDecoratorClass on two methods:class Foobar: def __init__(self): pass @MyDecoratorClass def foo(self): print(f"foo!") @MyDecoratorClass def bar(self): print("bar!")Now try this:>>> f = Foobar()>>> f.foo()>>> f.bar()foo!foo!Once a MyDecoratorClass()-instance exists for the Foobar() object, you'll always access this existing one to invoke the method. In our case, this MyDecoratorClass()-instance got bound to the foo() method, so bar() never executes!The solution is to revise the way we store the MyDecoratorClass()-instance in __obj_dict__. Don't just spawn and store one MyDecoratorClass()-instance per Foobar()-object, but one instance per (Foobar(), method) combination! That requires an extra parameter for our decorator, eg:@MyDecoratorClass("foo")def foo(self): print(f"foo!")@MyDecoratorClass("bar")def bar(self): print("bar!")A decorator with a parameter essentially means double-wrapping the underlying method/function! So let's design a wrapper for that:def my_wrapper(name="unknown"): def _my_wrapper_(method): return MyDecoratorClass(method, name) return _my_wrapper_and now use this wrapper:class Foobar: def __init__(self): pass @my_wrapper("foo") def foo(self): print(f"foo!") @my_wrapper("bar") def bar(self): print("bar!")Finally, we need to refactor the MyDecoratorClass:import functools, typesclass 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, obj, 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, objtype), self.method_name) MyDecoratorClass.__obj_dict__[obj] = {} MyDecoratorClass.__obj_dict__[obj][self.method_name] = m return m def __call__(self, *args, **kwargs) -> object: return self.method(*args, **kwargs) def __del__(self): print(f"{id(self)} garbage collected!")Let's revise: at the beginning of the program, before any Foobar()-object is born, the Python interpreter already spawns two MyDecoratorClass()-instances: one for the unbound foo() and another for the unbound bar() method. These are our immortal MyDecoratorClass()-instances whose __get__() methods serve as MyDecoratorClass() factories.Nothing new here. This happened also before we did these changes. However, now we store the method_name at the moment the factories are built! This way, the factory method __get__() can make use of that information to spawn and store not just one MyDecoratorClass()-instances per Foobar() object, but one for the (Foobar(), "foo") and (Foobar(), "bar") combination!This is the complete self-contained program:import functools, typesclass 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, obj, 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, objtype), self.method_name) MyDecoratorClass.__obj_dict__[obj] = {} MyDecoratorClass.__obj_dict__[obj][self.method_name] = m return m def __call__(self, *args, **kwargs) -> object: return self.method(*args, **kwargs) def __del__(self): print(f"{id(self)} garbage collected!")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!") 这篇关于如何从类装饰器的应用方法中访问变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
10-10 22:22