在上下文管理器中__init__ vs __enter__

max*_*max 24 python contextmanager python-3.x

据我了解,__init__()__enter__()上下文管理的方法调用一次每个,一个接一个,不留下任何机会之间执行任何其他代码.将它们分成两种方法的目的是什么,我应该将它们放入每种方法中?

编辑:对不起,没注意文档.

编辑2:实际上,我感到困惑的原因是因为我在想@contextmanager装饰师.使用创建的上下文管理器@contextmananger只能使用一次(第一次使用后生成器将耗尽),因此通常使用构造函数内部with语句编写; 如果这是使用with陈述的唯一方法,我的问题就有意义了.当然,实际上,情境管理者比@contextmanager可以创造的更为笼统; 特别是上下文管理器通常可以重用.我希望这次能做对吗?

Ant*_*ala 41

据我了解,__init__()__enter__()上下文管理的方法调用一次每个,一个接一个,不留下任何机会之间执行任何其他代码.

你的理解是不正确的. __init__在创建对象__enter__时,在使用with语句输入对象时调用,这些是完全不同的2个.通常情况下,构造函数在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')
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

此外,如果__init____enter__没有分开,甚至不可能使用以下内容:

def open_etc_file(name):
    return open(os.path.join('/etc', name))

with open_etc_file('passwd'):
    ...
Run Code Online (Sandbox Code Playgroud)

因为初始化(内部open)明显与with入口分开.


创建的经理contextlib.manager是单一参与者,但他们也可以在with街区之外构建.举个例子:

from contextlib import contextmanager

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)
Run Code Online (Sandbox Code Playgroud)

你可以用它作为:

def heading(level=1):
    return tag('h{}'.format(level))

my_heading = heading()
print('Below be my heading')
with my_heading:
     print('Here be dragons')
Run Code Online (Sandbox Code Playgroud)

输出:

Below be my heading
<h1>
Here be dragons
</h1>
Run Code Online (Sandbox Code Playgroud)

但是,如果您尝试重用my_heading(并因此tag),您将获得

RuntimeError: generator didn't yield
Run Code Online (Sandbox Code Playgroud)

  • 哦,不管怎么说,我记得的所有例子都有构造函数调用发生在`with`语句中(例如,`with Foo()...`).现在一切都有道理.谢谢 (3认同)

Sep*_*ity 6

Antti Haapalas的回答非常好.我只是想详细说明参数的使用(比如myClass(* args)),因为这对我来说有点不清楚(回顾我问自己为什么......)

使用参数在with语句中初始化类与使用类通常的方法没有什么不同.调用将按以下顺序进行:

  1. __init__ (课程分配)
  2. __enter__ (输入上下文)
  3. __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()
Run Code Online (Sandbox Code Playgroud)

输出:

__init__ called: 42
__enter__ called
    do something with 42
__exit__ called
Run Code Online (Sandbox Code Playgroud)

如果您想确保您的调用(几乎)只能在上下文中使用(例如强制调用__exit__),请参阅此处的stackoverflow文章.在评论中,您还将找到即使在那时如何使用参数的问题的答案.