首页 > 解决方案 > 一种从 Python 中的类实例访问上下文管理器变量的方法?

问题描述

我会从其他模块中定义的一些类访问上下文中定义的一些变量,就像我在下面的超级简化一样:

在 main.py

import class_def1
import class_def2

class Starter(object):
    #context manager
    def __init__(self):
        self.att = 'Myfirst'

    def __enter__(self):
        return self.att

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

with Starter() as f:
    for name in ('a', 'b', 'c'):
        for number in range(2):
            c1 = class_def1.MyClass1()
            print(c1.attr1)
            c2 = class_def2.MyClass2()
            print(c2.attr2)

在 class_def1.py 中:

class MyClass1(object):
    global f
    global number
    def __init__(self):
        self.attr1 = ','.join([f,str(number)])

在 class_def2.py 中:

class MyClass2(object):
    def __init__(self):
        global f
        global name
        global number
        self.attr2 = ','.join([f,name,str(number)])

这段代码不起作用,我也想避免使用“全局”和输入参数来初始化,因为我有很多变量要“传递”......也许我要求太多了。有什么建议吗?

新的考虑

考虑到使用数据类作为 DTO 的可能性(如@Niel Godfrey Ponciano 所建议的那样),我可以通过这种方式创建一个数据类(或者甚至列出其他变量并将默认值设置为零):

@dataclass
class AllInfo:
      f: str

with Starter() as f:
    info = AllInfo(f)
    for name in (a,b,c):
        info.name = name
        for number in range(2):
            info.number = number
            c1 = class_def1.MyClass1(info)
            print(c1.attr1)
            c2 = class_def2.MyClass2(info)
            print(c2.attr2)

但是,由于它只是存储变量的问题(不需要方法),这相当于:

class AllInfo:
      def __init__(self,f):
        self.f = f

(在我看来,自动添加到数据类的repr方法不相关)。

但是,在这两种情况下,我都需要传递整个信息集。可能不是这样的问题,因为它是一个指针......

标签: python

解决方案


关于__enter__上下文管理器的方法Starter,如文档中所述:

此方法返回的值绑定到使用此上下文管理器的 with 语句的 as 子句中的标识符。

返回的值为self.att

def __enter__(self):
    return self.att  # Returns the string "Myfirst"

这意味着 的值f将是字符串“Myfirst”。如果您想要的f是整个Starter实例而不仅仅是属性Starter.att,那么做return self而不是return self.att.

with Starter() as f:  # f will be a string "Myfirst"

现在,下一步是将上下文管理器变量(f来自Starter.att)与一些迭代值一起name传递number给您的其他类MyClass1MyClass2. 执行此操作的一种标准方法是执行依赖项注入,其中我们将依赖项fname和注入value到目标客户端,即MyClass1MyClass2。简单来说,设计就是MyClass1并且MyClass2取决于fnamevalue

解决方案 1:使用构造函数注入

你提到你不想传递给init。但这是执行此操作的标准方法之一,也是最简单的方法。如果它更适合您,请继续下面的解决方案 2,

class Starter(object):
    #context manager
    def __init__(self):
        self.att = 'Myfirst'

    def __enter__(self):
        return self.att

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass


class MyClass2(object):
    def __init__(self, f, name, number):
        text_list = [f, name, str(number)]
        self.attr2 = ','.join(text_list)


with Starter() as f:
    for name in ('a', 'b', 'c'):
        for number in range(2):
            # Inject all dependencies to the class
            c2 = MyClass2(f, name, number)
            print(c2.attr2)

输出:

Myfirst,a,0
Myfirst,a,1
Myfirst,b,0
Myfirst,b,1
Myfirst,c,0
Myfirst,c,1

解决方案 2:使用 setter 注入

如果您有很多变量,但只会使用其中的一部分,那么这可能是适用的。最好的用例之一是构建器设计模式

class Starter(object):
    #context manager
    def __init__(self):
        self.att = 'Myfirst'

    def __enter__(self):
        return self.att

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass


class MyClass2(object):
    def __init__(self):
        self._f = None
        self._name = None
        self._number = None

    # Here, we are using property getters and setters. Of course you can also do it by just directly
    # accessing the variable from the outside, just remove the _ in the name to imply that they are
    # public and not private as it is now).
    @property
    def f(self):
        return self._f

    @f.setter
    def f(self, value):
        self._f = value

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def number(self):
        return self._number

    @number.setter
    def number(self, value):
        self._number = value

    @property
    def attr2(self):
        # Optionally, you can add checks to filter out null variables in the list
        text_list = [self.f, self.name, str(self.number)]
        return ','.join(text_list)


with Starter() as f:
    for name in ('a', 'b', 'c'):
        for number in range(2):
            c2 = MyClass2()
            # Manually inject each dependency to the class. You can add conditionals to select what
            # should be injected and what shouldn't.
            c2.f = f
            c2.name = name
            c2.number = number
            print(c2.attr2)

输出:

  • 与解决方案 1 相同

如果您有很多变量,正如@chepner 所建议的那样,您可以将它们全部(或按相关数据组)放入其自己的数据结构中。您可以为此目的使用数据类(就像struct在 C/C++ 中一样),它将充当您的数据传输对象 (DTO)


推荐阅读