__init__ 与上下文管理器中的 __enter__
__init__ vs __enter__ in context managers
据我了解,上下文管理器的 __init__()
和 __enter__()
方法各调用一次,一个接一个,没有机会在其间执行任何其他代码。将它们分成两个方法的目的是什么,我应该在每个方法中放入什么?
编辑:抱歉,没注意文档。
编辑 2:实际上,我感到困惑的原因是因为我在考虑 @contextmanager
装饰器。使用@contextmananger
创建的上下文管理器只能使用一次(生成器会在第一次使用后耗尽),所以它们通常是在with
语句中使用构造函数调用编写的;如果那是使用 with
语句的唯一方法,那么我的问题就有意义了。当然,实际上,上下文管理器比 @contextmanager
可以创建的更通用;特别是上下文管理器通常可以重用。希望这次我做对了?
As far as I understand, __init__()
and __enter__()
methods of the context manager are called exactly once each, one after another, leaving no chance for any other code to be executed in between.
而且你的理解是不正确的。 __init__
在创建对象时调用,__enter__
在使用 with
语句输入时调用,这是两个截然不同的事情。通常情况下,在 with
初始化中直接调用构造函数,没有中间代码,但不一定是这种情况。
考虑这个例子:
class Foo:
def __init__(self):
print('__init__ called')
def __enter__(self):
print('__enter__ called')
return self
def __exit__(self, *a):
print('__exit__ called')
myobj = Foo()
print('\nabout to enter with 1')
with myobj:
print('in with 1')
print('\nabout to enter with 2')
with myobj:
print('in with 2')
myobj
可以单独初始化,分多个with
块进入:
输出:
__init__ called
about to enter with 1
__enter__ called
in with 1
__exit__ called
about to enter with 2
__enter__ called
in with 2
__exit__ called
此外,如果 __init__
和 __enter__
没有分开,甚至无法使用以下内容:
def open_etc_file(name):
return open(os.path.join('/etc', name))
with open_etc_file('passwd'):
...
因为初始化(在 open
内)显然与 with
条目分开。
contextlib.manager
创建的管理器是单入场者,但它们同样可以在 with
块之外构建。举个例子:
from contextlib import contextmanager
@contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)
您可以将其用作:
def heading(level=1):
return tag('h{}'.format(level))
my_heading = heading()
print('Below be my heading')
with my_heading:
print('Here be dragons')
输出:
Below be my heading
<h1>
Here be dragons
</h1>
但是,如果您尝试重用 my_heading
(因此,tag
),您将得到
RuntimeError: generator didn't yield
Antti Haapalas 的回答非常好。我只是想详细说明一下参数的用法(比如 myClass(* args)
),因为这对我来说有点不清楚(回顾我问自己为什么......)
在 with
语句中使用参数初始化 class 与通常使用 class 没有什么不同。
通话将按以下顺序进行:
__init__
(配置class)
__enter__
(输入上下文)
__exit__
(离开上下文)
简单示例:
class Foo:
def __init__(self, i):
print('__init__ called: {}'.format(i))
self.i = i
def __enter__(self):
print('__enter__ called')
return self
def do_something(self):
print('do something with {}'.format(self.i))
def __exit__(self, *a):
print('__exit__ called')
with Foo(42) as bar:
bar.do_something()
输出:
__init__ called: 42
__enter__ called
do something with 42
__exit__ called
如果您想确保您的调用(几乎)只能在上下文中使用(例如强制调用 __exit__
),请参阅 Whosebug post here.在评论中,您还将找到如何使用参数的问题的答案。
据我了解,上下文管理器的 __init__()
和 __enter__()
方法各调用一次,一个接一个,没有机会在其间执行任何其他代码。将它们分成两个方法的目的是什么,我应该在每个方法中放入什么?
编辑:抱歉,没注意文档。
编辑 2:实际上,我感到困惑的原因是因为我在考虑 @contextmanager
装饰器。使用@contextmananger
创建的上下文管理器只能使用一次(生成器会在第一次使用后耗尽),所以它们通常是在with
语句中使用构造函数调用编写的;如果那是使用 with
语句的唯一方法,那么我的问题就有意义了。当然,实际上,上下文管理器比 @contextmanager
可以创建的更通用;特别是上下文管理器通常可以重用。希望这次我做对了?
As far as I understand,
__init__()
and__enter__()
methods of the context manager are called exactly once each, one after another, leaving no chance for any other code to be executed in between.
而且你的理解是不正确的。 __init__
在创建对象时调用,__enter__
在使用 with
语句输入时调用,这是两个截然不同的事情。通常情况下,在 with
初始化中直接调用构造函数,没有中间代码,但不一定是这种情况。
考虑这个例子:
class Foo:
def __init__(self):
print('__init__ called')
def __enter__(self):
print('__enter__ called')
return self
def __exit__(self, *a):
print('__exit__ called')
myobj = Foo()
print('\nabout to enter with 1')
with myobj:
print('in with 1')
print('\nabout to enter with 2')
with myobj:
print('in with 2')
myobj
可以单独初始化,分多个with
块进入:
输出:
__init__ called
about to enter with 1
__enter__ called
in with 1
__exit__ called
about to enter with 2
__enter__ called
in with 2
__exit__ called
此外,如果 __init__
和 __enter__
没有分开,甚至无法使用以下内容:
def open_etc_file(name):
return open(os.path.join('/etc', name))
with open_etc_file('passwd'):
...
因为初始化(在 open
内)显然与 with
条目分开。
contextlib.manager
创建的管理器是单入场者,但它们同样可以在 with
块之外构建。举个例子:
from contextlib import contextmanager
@contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)
您可以将其用作:
def heading(level=1):
return tag('h{}'.format(level))
my_heading = heading()
print('Below be my heading')
with my_heading:
print('Here be dragons')
输出:
Below be my heading
<h1>
Here be dragons
</h1>
但是,如果您尝试重用 my_heading
(因此,tag
),您将得到
RuntimeError: generator didn't yield
Antti Haapalas 的回答非常好。我只是想详细说明一下参数的用法(比如 myClass(* args)
),因为这对我来说有点不清楚(回顾我问自己为什么......)
在 with
语句中使用参数初始化 class 与通常使用 class 没有什么不同。
通话将按以下顺序进行:
__init__
(配置class)__enter__
(输入上下文)__exit__
(离开上下文)
简单示例:
class Foo:
def __init__(self, i):
print('__init__ called: {}'.format(i))
self.i = i
def __enter__(self):
print('__enter__ called')
return self
def do_something(self):
print('do something with {}'.format(self.i))
def __exit__(self, *a):
print('__exit__ called')
with Foo(42) as bar:
bar.do_something()
输出:
__init__ called: 42
__enter__ called
do something with 42
__exit__ called
如果您想确保您的调用(几乎)只能在上下文中使用(例如强制调用 __exit__
),请参阅 Whosebug post here.在评论中,您还将找到如何使用参数的问题的答案。