2016-09-21 48 views
7

据我所知,上下文管理器的方法每次一个接一个地被调用一次,没有机会让任何其他代码在其间执行。将它们分为两种方法的目的是什么?我应该把它们分别放入哪些方面?__init__ vs __enter__在上下文管理器

编辑:对不起,没有注意到文档。

编辑2:其实,我感到困惑的原因是因为我在想着装饰者@contextmanager。使用@contextmananger创建的上下文管理器只能使用一次(第一次使用后生成器将耗尽),因此经常在with语句中使用构造函数调用它们;如果这是使用with声明的唯一方法,那么我的问题就会有意义。当然,在现实中,上下文管理者比通常创建的更普遍;特别是情境管理者通常可以重复使用。我希望这次我明白了吗?

+1

你很困惑*创建一个上下文管理器和输入上下文。这两者是截然不同的,您可以多次使用相同的上下文管理器。 –

回答

20

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

而你的理解是不正确的。当创建对象时调用__init__,当它与with语句一起输入时,它们是__enter__,这些是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') 

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 
+1

哦,不知何故,我记得所有的例子都有''''''语句中的构造函数调用(例如'with Foo()...')。现在一切都很有意义。 Thx – max

+0

哦,等一下,那么'@ contextmanager'呢?由于它依赖于一个发生器,不会在第一次使用时耗尽它,从而不可能重复使用该物体? – max

+0

@max是的,'contextmanager'用于一次性上下文管理器。 –

0

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() 

输出:

__init__ called: 42 
__enter__ called 
    do something with 42 
__exit__ called 

如果您希望确保您的呼叫只能在上下文中使用(例如,强制呼叫__exit__),请参阅计算器后here。在评论中,你还会发现如何使用参数的问题的答案。