【问题标题】:Difference on context manager with and without "as" clause有和没有“as”子句的上下文管理器的区别
【发布时间】:2019-12-13 13:36:32
【问题描述】:

首先,我需要道歉,因为我还不能为我的问题提供明确的MCVE。我的问题是关于我在代码库深处遇到的一个奇怪现象,我想了解这是如何发生的,所以在某种程度上我问我如何创建这个现象的 MCVE第一名。

tl;博士

在根本不使用分配变量的with 语句中是否使用as 子句有什么关系?

加长版

我们正在使用 Airflow(Apache 项目),其中存在一个名为 DAG 的类。此类应该用作with 子句的上下文管理器,如下所示:

with DAG(**some_parameters) as dag:
    do_something_with(dag)

这按预期工作。

但是,在某些情况下,我们不会在 with 子句中使用 dag 变量,因此 IDE 会发出警告,然后将其重命名为 _dag(以声明不使用),我尝试了完全删除as dag 子句:

with DAG(**some_parameters):
    do_something_without_passing_dag()

根据我对 Python 的理解,这应该等同于运行时带有 as dag 子句的版本:

with DAG(**some_parameters) as dag:
    do_something_without_passing_dag()

但是,令人惊讶的是,在 Airflow 项目的背景下,两者之间似乎存在差异。使用as dag 子句,代码按预期工作;如果没有as dag 子句,则会显示错误(请参阅本文末尾)。令人沮丧的是,这个错误出现在 Airflow 进程的日志中,并且根本不包含对我的代码的引用。

我需要指出的是,在 Airflow 上下文中,这些 with 语句位于一个小模块的顶层,因此 as 语句会创建一个模块全局变量(如果存在)。我不知道这是否相关。如果是这样,我不明白为什么。

据我了解,如果我根本不使用该变量,无论我是否提供as 子句,它应该永远有任何区别。不过这里似乎是这样。

我已经调查了三个方面:

  1. 我监控了DAG类的__enter__()方法的输入和输出。在这两种情况下,输入(参数)和输出(返回值)都是相同的(返回值当然是上下文管理器对象)。因此,基于as 子句的存在,这里似乎没有任何区别。
  2. 使用as 子句时,我在with 子句中删除了变量(del dag) 作为第一条语句。然后这个版本的行为就像没有as 子句的版本一样,即。 e.它引发了错误。
  3. 我查看了source of the DAG,发现在它的__enter__() method中,它把当前上下文对象存储在一个DagContext class中,而do_something_without_passing_dag()可以(and will)从DAG对象中访问DagContext。但由于这一切都独立于使用as 语句创建的变量,我不明白这有什么关系。

谁能解释为什么会这样?

我可以在 Airflow 日志中找到错误堆栈跟踪:

webserver_1  | Traceback (most recent call last):
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
webserver_1  |     response = self.full_dispatch_request()
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
webserver_1  |     rv = self.handle_user_exception(e)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
webserver_1  |     reraise(exc_type, exc_value, tb)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
webserver_1  |     raise value
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
webserver_1  |     rv = self.dispatch_request()
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
webserver_1  |     return self.view_functions[rule.endpoint](**req.view_args)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask_admin/base.py", line 69, in inner
webserver_1  |     return self._run_view(f, *args, **kwargs)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask_admin/base.py", line 368, in _run_view
webserver_1  |     return fn(self, *args, **kwargs)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/flask_login/utils.py", line 258, in decorated_view
webserver_1  |     return func(*args, **kwargs)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/airflow/www/utils.py", line 281, in wrapper
webserver_1  |     return f(*args, **kwargs)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/airflow/utils/db.py", line 74, in wrapper
webserver_1  |     return func(*args, **kwargs)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/airflow/www/views.py", line 1958, in paused
webserver_1  |     models.DagModel.get_dagmodel(dag_id).set_is_paused(is_paused=is_paused)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/airflow/utils/db.py", line 74, in wrapper
webserver_1  |     return func(*args, **kwargs)
webserver_1  |   File "/usr/local/lib/python3.7/site-packages/airflow/models/dag.py", line 1562, in set_is_paused
webserver_1  |     subdags = self.get_dag().subdags
webserver_1  | AttributeError: 'NoneType' object has no attribute 'subdags'

【问题讨论】:

  • 必须是字面意义上的as dag 还是也可以是as XYZ
  • 变量名似乎无关紧要。 as a 也有效,as XYZ 也有效。
  • @Ev.Kounis 这是一个很好的答案。
  • 有趣的是,您共享的源不包含与您记录的错误相同的行。现在已更改为:dag = self.get_dag(store_serialized_dags); if dag is None: raise DagNotFound("Dag id {} not found".format(self.dag_id)); subdags = dag.subdags。所以现在有一个NoneType 检查。如果我不得不猜测,源本身可能依赖于正在创建的一些引用,但是因为你的函数没有对dag 做任何事情,它以某种方式干扰了这个过程。

标签: python with-statement contextmanager


【解决方案1】:

您的 do_something_without_passing_dag() 不应该知道 DAG(**some_parameters) 应该在其参数“dag”中传递。

例如,这是可行的:

dag=DAG(**some_parameters)
with dag:
   do_something_without_passing_dag()

【讨论】:

  • 是的,它不应该如此,但不知何故,无论是否存在现有变量,它的行为都会有所不同。例如,如果我在withdo_something... 行之间插入del dag,它就不再起作用了。这个效果还没有完全理解。
  • 在“with dag:”之后定义了变量“dag”,因为您的 do_somethin() 可能是一个气流操作符,它必然继承自 BaseOperator,它本身使用参数“dag”进行初始化,并且应用 @apply_defaults 装饰器。这个 apply_defaults 将查找预定义的变量并将它们作为参数应用。因此,如果您的 do_something_without() 的 dag 参数未定义,则 apply_default 将查找具有相同名称的现有变量来填充它。这有意义吗?
  • 我认为这可能是原因:这个装饰器有一些魔力。我很惊讶它应该在外部堆栈帧中搜索dag 变量。我认为它只搜索我在问题中提到的这个内部数据结构中的上下文。
  • 是的,这并不完全明显。我认为魔法发生在这里:github.com/apache/airflow/blob/… 和这里:github.com/apache/airflow/blob/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-20
  • 2018-11-22
  • 1970-01-01
  • 2013-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多