【问题标题】:How to access the variables created within a `with` statement如何访问在 `with` 语句中创建的变量
【发布时间】:2020-03-22 17:53:26
【问题描述】:

我在一个文件中定义了一个python上下文类和一个Test类:

class Test(object):
  pass

class MyContext(object):
  def __init(self):
     self._vars = []
  def __enter__(self):
    pass
  def __exit(self, ....):
    pass

在另一个使用该上下文的文件中:

from somewhere import Test, MyContext
with MyContext() as ctx:
  mytest = Test()

所以我想要实现的是,当我退出上下文时,我想知道创建的mytest 实例并将其添加到ctx._vars = [<instance of Test >] 中。

我不想拥有ctx.add_var(mytest) 方法,我希望将那些Test 实例自动添加到ctx 实例中。

【问题讨论】:

  • 使用上下文并不意味着ctx 知道您的with 语句中的代码。
  • 正确。一个中间解决方案是将 locals() 传递给 ctx ,最后一行类似。 ctx.get_nodes_from_locals(locals()) 但我必须过滤 ctx 内部的内容并摆脱可能存在的外部。目前还不是防弹的。
  • 在您输入上下文时评估闭包。从内部更改上下文表明您在设计任何东西时都使用了错误的设计工具。
  • (我删除了“python-contextvars”标签,因为这些标签与 with-block 管理的“context”无关

标签: python introspection


【解决方案1】:

使用 Python 的自省功能可以做到这一点,但您必须注意这不是创建 with 上下文块的目的。

我同意这是一种有用的语法结构,可以“偏离”以执行您想要的操作:注释在“注册表”中的代码块内创建的对象。

在展示如何使用上下文管理器执行此操作之前,请考虑类主体是否足以满足您的需求。以这种方式使用类体也偏离了它的主要目的,但你有你的“注册表”是免费的:


from somewhere import Test, MyContext
class ctx:
  mytest = Test()

vars = ctx.__dict__.values()

为了使用上下文管理器执行此操作,您必须在 with 块的开头和结尾检查局部变量。虽然这并不难做到,但它不会涵盖所有创建的 Test 实例 - 因为如果代码是这样的:

mytests = []
with Mycontext as ctx:
    mytests.append(Test())

没有创建新变量 - 因此跟踪局部变量的代码不会找到任何东西。可以编写代码以递归方式查看具有容器的变量,例如字典和列表 - 但随后可以将 mytest() 实例添加到作为全局变量或其他模块中的变量引用的容器中。

事实证明,跟踪Test 实例的可靠方法是使用Test 类本身来注释注册表中的新实例。这要容易得多,并且更少依赖于“局部变量自省”技巧。

代码有点像:


class Test(object):
  pass

class MyContext(object):
    def __init(self, *args):
        self.vars = []
        self.track = args
        self.original_new = {}

    def patch(self, cls_to_patch):
        cls_new = getattr(cls_to_patch, "__new__")
        if "__new__" in cls.__dict__:
            self.original_new[cls_to_patch] = cls_new

        def patched_new(cls, *args, **kwargs):
            instance = cls_new(*args, **kwags)
            self.vars.append(instance)
            return instance

        cls_to_patch.__new__ = patched_new        

    def restore(self, cls):
        if cls in self.original_new:
            # class had a very own __new_ prior to patching
            cls.__new__ = self.original_new[cls]
        else:
            # just remove the wrapped new method, restores access to superclass `__new__`
            del cls.__new__

    def __enter__(self):
        for cls in self.track:
            self.patch(cls)
        return self

    def __exit(self, ....):
        for cls in self.track:
            self.restore(cls)
        ...


from somewhere import Test, MyContext
with MyContext(Test) as ctx:
  mytest = Test()

【讨论】:

  • 非常有趣的方法。谢谢
  • 上面唯一的问题是你只是在跟踪作为参数传递给上下文的类,而不是在上下文中创建的实际实例。
猜你喜欢
  • 2022-12-03
  • 2020-08-02
  • 1970-01-01
  • 2020-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多