带和不带"as"子句的上下文管理器的区别



首先,我需要道歉,因为我无法为我的问题提供明确的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()

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

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

据我了解,如果我根本不使用该变量,我是否提供as子句应该没有任何区别。 尽管如此,这里似乎还是如此。

我已经调查了三个方面:

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

谁能解释一下为什么会这样?

这是我可以在气流日志中找到的错误堆栈跟踪:

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'

你的do_something_without_passing_dag((不应该知道DAG(**some_parameters(应该在其参数"dag"中传递。

例如,这有效:

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

最新更新