Как получить доступ к переменным, созданным в операторе `with` - PullRequest
0 голосов
/ 22 марта 2020

Я определил класс контекста 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.

1 Ответ

0 голосов
/ 21 апреля 2020

Это возможно сделать, используя возможности самоанализа 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() экземпляры могут быть добавлены в контейнер, на который ссылаются как на глобальную переменную, или переменную в другом модуле.

It Оказывается, что надежным способом отслеживания экземпляров 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()

...