如何从类装饰器的应用方法中访问变量?

如何解决如何从类装饰器的应用方法中访问变量?

注意
我已经根据@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()实例f1f2,则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.fooMyDecoratorClass()的包装器!

现在,如果实现选项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 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)> insert overwrite table dwd_trade_cart_add_inc > select data.id, > data.user_id, > data.course_id, > date_format(
错误1 hive (edu)> insert into huanhuan values(1,'haoge'); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive> show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 <configuration> <property> <name>yarn.nodemanager.res