我们如何 "associate" 一个 Python 上下文管理器来处理出现在其块中的变量?

How can we "associate" a Python context manager to the variables appearing in its block?

据我了解,上下文管理器在 Python 中用于定义对象的初始化和完成代码片段(__enter____exit__)。

但是,在 tutorial for PyMC3 中,他们显示了以下上下文管理器示例:

basic_model = pm.Model()

with basic_model:

    # Priors for unknown model parameters
    alpha = pm.Normal('alpha', mu=0, sd=10)
    beta = pm.Normal('beta', mu=0, sd=10, shape=2)
    sigma = pm.HalfNormal('sigma', sd=1)

    # Expected value of outcome
    mu = alpha + beta[0]*X1 + beta[1]*X2

    # Likelihood (sampling distribution) of observations
    Y_obs = pm.Normal('Y_obs', mu=mu, sd=sigma, observed=Y)

并提到这具有将变量 alphabetasigmamuY_obs 关联到模型 basic_model.

我想了解这种机制是如何运作的。在我发现的 explanations of 上下文管理器中,我没有看到任何暗示上下文块中定义的变量或对象如何以某种方式 "associated" 进入上下文管理器的提示。库 (PyMC3) 似乎可以以某种方式访问​​ "current" 上下文管理器,因此它可以在幕后将每个新创建的语句关联到它。但是库如何访问上下文管理器?

我不知道在这个特定情况下它是如何工作的,但通常你会使用一些 'behind the scenes magic':

class Parent:
    def __init__(self):
        self.active_child = None

    def ContextManager(self):
        return Child(self)

    def Attribute(self):
        return self.active_child.Attribute()

class Child:
    def __init__(self,parent):
        self.parent = parent

    def __enter__(self):
        self.parent.active_child = self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.parent.active_child = None

    def Attribute(self):
        print("Called Attribute of child")

使用此代码:

p = Parent()
with p.ContextManager():
    attr = p.Attribute()

将产生以下输出:

Called Attribute of child

PyMC3 通过在 Context class 中维护 thread local variable 作为 class 变量 来做到这一点。 Models 继承自 Context.

每次您在模型上调用 with 时,当前模型都会被推送到特定于线程的上下文堆栈中。因此,堆栈的顶部总是指用作上下文管理器的最里面(最近)的模型。

Contexts(因此 Models)有一个 .get_context() class 方法 来获取顶部上下文堆栈。

Distribution 在创建它们时调用 Model.get_context() 以将它们自己与最内层模型相关联。

简而言之:

  1. with modelmodel 压入上下文堆栈。这意味着在 with 块内,type(model).contextsModel.contextsContext.contexts 现在包含 model 作为其最后(最顶层)元素。
  2. Distribution.__init__() 调用 Model.get_context()(注意大写 M),其中 returns 上下文堆栈的顶部。在我们的例子中,这是 model。上下文堆栈是线程本地的(每个线程有一个),但它不是特定于实例的。如果只有一个线程,那么也只有一个上下文堆栈,与模型数量无关。
  3. 退出上下文管理器时。 model 从上下文堆栈中弹出。