在 'with' 语句中调用构造函数
Invoking a constructor in a 'with' statement
我有以下代码:
class Test:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f'entering {self.name}')
def __exit__(self, exctype, excinst, exctb) -> bool:
print(f'exiting {self.name}')
return True
with Test('first') as test:
print(f'in {test.name}')
test = Test('second')
with test:
print(f'in {test.name}')
运行 它产生以下输出:
entering first
exiting first
entering second
in second
exiting second
但我预计它会产生:
entering first
in first
exiting first
entering second
in second
exiting second
为什么我的第一个示例中的代码没有被调用?
__enter__
方法应该 return 上下文对象。 with ... as ...
使用 __enter__
的 return 值来确定给你什么对象。由于你的 __enter__
return 什么都没有,它隐含地 return 是 None
,所以 test
是 None
.
with Test('first') as test:
print(f'in {test.name}')
test = Test('second')
with test:
print(f'in {test.name}')
所以 test
是 none。那么test.name
就是一个错误。出现该错误,因此 Test('first').__exit__
被调用。 __exit__
returns True
,表示错误已被处理(本质上,你的 __exit__
就像一个 except
块),所以代码在第一个 with
块之后继续,因为你告诉 Python 一切都很好。
考虑
def __enter__(self):
print(f'entering {self.name}')
return self
你也可以考虑不从 __exit__
returning True
除非你真的打算无条件地抑制块中的 all 错误(并充分理解抑制其他程序员错误的后果,以及KeyboardInterrupt
、StopIteration
和各种系统信号)
我认为这种行为是因为 __enter__
必须 return 将要操作的东西,在这种情况下将使用名称 test
访问。通过将 __enter__
更改为以下
def __enter__(self):
print(f"entering {self.name}")
return self
我们得到了预期的行为。
问题是您的 __enter__
方法 returns None
。因此,test
被赋值为 None
.
然后您尝试访问 (None).name
,这会引发错误。由于您的 __exit__
方法 returns True
总是,它将抑制任何错误。根据the docs:
Returning a true value from this method will cause the with statement
to suppress the exception and continue execution with the statement
immediately following the with statement.
解释:
__enter__
给出了 None
作为输出,因为没有 return
,因此它会直接触发 __exit__
因为 None
没有属性name
,例如:
>>> None.name
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
None.__name__
AttributeError: 'NoneType' object has no attribute 'name'
>>>
如果你设置它调用__class__.__name__
(None
对象有那个属性,它给出NoneType
),你可以很容易地找到问题:
class Test:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f'entering {self.name}')
def __exit__(self, exctype, excinst, exctb) -> bool:
print(f'exiting {self.name}')
return True
with Test('first') as test:
print(f'in {test.__class__.__name__}')
test = Test('second')
with test:
print(f'in {test.__class__.__name__}')
输出:
entering first
in NoneType
exiting first
entering second
in Test
exiting second
如您所见,它表示 in NoneType
,而不是 returning 任何值是造成这种情况的原因。在很多情况下,__enter__
不需要 return,但在这种情况下 Test
class 需要 return.
解决方案:
解决方案是保留 Test
实例,以便它在上下文管理器 [=16= 之后调用 returned self
的 name
] 结果。到目前为止 __enter__
结果 None
,因此 None.name
属性不存在。因此,如果您 return self
,test.name
属性将存在。
解决方法是 return self
在 __enter__
魔术方法实现中:
...
def __enter__(self):
print(f'entering {self.name}')
return self
...
完整代码:
class Test:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f'entering {self.name}')
return self
def __exit__(self, exctype, excinst, exctb) -> bool:
print(f'exiting {self.name}')
return True
with Test('first') as test:
print(f'in {test.name}')
test = Test('second')
with test:
print(f'in {test.name}')
输出:
entering first
in first
exiting first
entering second
in second
exiting second
我给出的其他答案没有给出的额外信息是 __enter__
方法实现给出 None
的更具体证明。我也展示了一个例子。
原因是第一种情况和第二种情况不一样
在first
中:
- 创建对象,调用
__init__
;
- 然后
with
调用 __enter__
;
- 然后
as
将__enter__
的结果存入test
- 因为
__enter__
没有 return 值,test
是 None
。
在second
中:
- 创建对象,调用
__init__
;
- 然后分配给
test
;
- 然后
with
调用 __enter__
;
- 但是
__enter__
; 的结果没有做任何事情
- 所以
test
一直引用最初创建的对象。
在这两种情况下,都会为 with
正在处理的对象调用 __exit__
,因此您会看到正在打印正确的标签;只是,在 first
中,test
标识符未绑定到同一对象。
注意 __enter__
不必 return self
。它可能 return 完全不同,例如您可以打开一个文件并使 __enter__
return 流,而 __exit__
可能会关闭它。如果给定 __enter__
应该 return self
,那将是多余的,可以只是暗示。
我有以下代码:
class Test:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f'entering {self.name}')
def __exit__(self, exctype, excinst, exctb) -> bool:
print(f'exiting {self.name}')
return True
with Test('first') as test:
print(f'in {test.name}')
test = Test('second')
with test:
print(f'in {test.name}')
运行 它产生以下输出:
entering first
exiting first
entering second
in second
exiting second
但我预计它会产生:
entering first
in first
exiting first
entering second
in second
exiting second
为什么我的第一个示例中的代码没有被调用?
__enter__
方法应该 return 上下文对象。 with ... as ...
使用 __enter__
的 return 值来确定给你什么对象。由于你的 __enter__
return 什么都没有,它隐含地 return 是 None
,所以 test
是 None
.
with Test('first') as test:
print(f'in {test.name}')
test = Test('second')
with test:
print(f'in {test.name}')
所以 test
是 none。那么test.name
就是一个错误。出现该错误,因此 Test('first').__exit__
被调用。 __exit__
returns True
,表示错误已被处理(本质上,你的 __exit__
就像一个 except
块),所以代码在第一个 with
块之后继续,因为你告诉 Python 一切都很好。
考虑
def __enter__(self):
print(f'entering {self.name}')
return self
你也可以考虑不从 __exit__
returning True
除非你真的打算无条件地抑制块中的 all 错误(并充分理解抑制其他程序员错误的后果,以及KeyboardInterrupt
、StopIteration
和各种系统信号)
我认为这种行为是因为 __enter__
必须 return 将要操作的东西,在这种情况下将使用名称 test
访问。通过将 __enter__
更改为以下
def __enter__(self):
print(f"entering {self.name}")
return self
我们得到了预期的行为。
问题是您的 __enter__
方法 returns None
。因此,test
被赋值为 None
.
然后您尝试访问 (None).name
,这会引发错误。由于您的 __exit__
方法 returns True
总是,它将抑制任何错误。根据the docs:
Returning a true value from this method will cause the with statement to suppress the exception and continue execution with the statement immediately following the with statement.
解释:
__enter__
给出了 None
作为输出,因为没有 return
,因此它会直接触发 __exit__
因为 None
没有属性name
,例如:
>>> None.name
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
None.__name__
AttributeError: 'NoneType' object has no attribute 'name'
>>>
如果你设置它调用__class__.__name__
(None
对象有那个属性,它给出NoneType
),你可以很容易地找到问题:
class Test:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f'entering {self.name}')
def __exit__(self, exctype, excinst, exctb) -> bool:
print(f'exiting {self.name}')
return True
with Test('first') as test:
print(f'in {test.__class__.__name__}')
test = Test('second')
with test:
print(f'in {test.__class__.__name__}')
输出:
entering first
in NoneType
exiting first
entering second
in Test
exiting second
如您所见,它表示 in NoneType
,而不是 returning 任何值是造成这种情况的原因。在很多情况下,__enter__
不需要 return,但在这种情况下 Test
class 需要 return.
解决方案:
解决方案是保留 Test
实例,以便它在上下文管理器 [=16= 之后调用 returned self
的 name
] 结果。到目前为止 __enter__
结果 None
,因此 None.name
属性不存在。因此,如果您 return self
,test.name
属性将存在。
解决方法是 return self
在 __enter__
魔术方法实现中:
...
def __enter__(self):
print(f'entering {self.name}')
return self
...
完整代码:
class Test:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f'entering {self.name}')
return self
def __exit__(self, exctype, excinst, exctb) -> bool:
print(f'exiting {self.name}')
return True
with Test('first') as test:
print(f'in {test.name}')
test = Test('second')
with test:
print(f'in {test.name}')
输出:
entering first
in first
exiting first
entering second
in second
exiting second
我给出的其他答案没有给出的额外信息是 __enter__
方法实现给出 None
的更具体证明。我也展示了一个例子。
原因是第一种情况和第二种情况不一样
在first
中:
- 创建对象,调用
__init__
; - 然后
with
调用__enter__
; - 然后
as
将__enter__
的结果存入test
- 因为
__enter__
没有 return 值,test
是None
。
在second
中:
- 创建对象,调用
__init__
; - 然后分配给
test
; - 然后
with
调用__enter__
; - 但是
__enter__
; 的结果没有做任何事情
- 所以
test
一直引用最初创建的对象。
在这两种情况下,都会为 with
正在处理的对象调用 __exit__
,因此您会看到正在打印正确的标签;只是,在 first
中,test
标识符未绑定到同一对象。
注意 __enter__
不必 return self
。它可能 return 完全不同,例如您可以打开一个文件并使 __enter__
return 流,而 __exit__
可能会关闭它。如果给定 __enter__
应该 return self
,那将是多余的,可以只是暗示。