Pythonのcontextlibコンテキスト管理モジュールの使い方を詳しく理解する

8931 ワード

私たちが使っているOSモジュールは、ファイルを読み込むときに、実は彼は__を含んでいます.enter__ __exit__ .一つはwithトリガの時、もう一つは退出の時です.

with file('nima,'r') as f:
  print f.readline()

では、私たちは自分でもう一つの標準的なwithのクラスを実現します.個人的にpythonを書くときは、論理を閉じる必要があるコードに対してwithのパターンを構築するのが好きです. 

#encoding:utf-8
class echo:
  def __enter__(self):
    print 'enter'
 
  def __exit__(self,*args):
    print 'exit'
 
with echo() as e:
  print 'nima'

contextlibはwithよりも美しいものであり、コンテキストメカニズムを提供するモジュールでもあり、Generator装飾器によって実現され、採用されていない.enter__および_exit__.contextlibのcontextmanagerは、関数レベルのコンテキスト管理メカニズムを提供するために装飾器として機能する.

from contextlib import contextmanager
 
@contextmanager
def make_context() :
  print 'enter'
  try :
    yield {}
  except RuntimeError, err :
    print 'error' , err
  finally :
    print 'exit'
 
with make_context() as value :
  print value


前回書いたredis分散ロックコードにcontextlibの使い方があることをここに貼ります.実は一見、withとcontextlibを使うのは面倒ですが、少なくともあなたの本体コードをもっと鮮明にしました.

from contextlib import contextmanager
from random import random
 
DEFAULT_EXPIRES = 15
DEFAULT_RETRIES = 5
 
@contextmanager
def dist_lock(key, client):
  key = 'lock_%s' % key
 
  try:
    _acquire_lock(key, client)
    yield
  finally:
    _release_lock(key, client)
 
def _acquire_lock(key, client):
  for i in xrange(0, DEFAULT_RETRIES):
    get_stored = client.get(key)
    if get_stored:
      sleep_time = (((i+1)*random()) + 2**i) / 2.5
      print 'Sleeipng for %s' % (sleep_time)
      time.sleep(sleep_time)
    else:
      stored = client.set(key, 1)
      client.expire(key,DEFAULT_EXPIRES)
      return
  raise Exception('Could not acquire lock for %s' % key)
 
def _release_lock(key, client):
  client.delete(key)


Context Manager API
コンテキストマネージャはwith宣言でアクティブ化され、APIには2つのメソッドが含まれています.enter__()メソッド実行実行ストリームはwithコードブロック内に入る.彼はオブジェクトのコンテキストを返して使用します.実行フローがwithブロックから離れると、_exit__()メソッドコンテキストマネージャは、使用されるリソースを消去します.

class Context(object):
  
  def __init__(self):
    print '__init__()'

  def __enter__(self):
    print '__enter__()'
    return self

  def __exit__(self, exc_type, exc_val, exc_tb):
    print '__exit__()'

with Context():
  print 'Doing work in the context.'


印刷結果

__init__()
__enter__()
Doing work in the context.
__exit__()

コンテキストマネージャの実行時に呼び出されます_enter__離れるときに呼び出す_exit__.
__enter__任意のオブジェクトを返し、with宣言に指定した名前を結合できます.

class WithinContext(object):

  def __init__(self, context):
    print 'WithinContext.__init__(%s)' % context

  def do_something(self):
    print 'WithinContext.do_something()'

  def __del__(self):
    print 'WithinContext.__del__'


class Context(object):

  def __init__(self):
    print '__init__()'
  
  def __enter__(self):
    print '__enter__()'
    return WithinContext(self)
  
  def __exit__(self, exc_type, exc_val, exc_tb):
    print '__exit__()'

with Context() as c:
  c.do_something()


印刷結果

__init__()
__enter__()
WithinContext.__init__(<__main__.context object="" at="">)
WithinContext.do_something()
__exit__()
WithinContext.__del__

コンテキストマネージャが例外を処理できる場合、_exit__()True値を返す必要があります.この例外は伝播する必要がありません.False例外を返すと実行されます.exit__その後引き起こされる.

class Context(object):

  def __init__(self, handle_error):
    print '__init__(%s)' % handle_error
    self.handle_error = handle_error
  
  def __enter__(self):
    print '__enter__()'
    return self
  
  def __exit__(self, exc_type, exc_val, exc_tb):
    print '__exit__(%s, %s, %s)' % (exc_type, exc_val, exc_tb)
    return self.handle_error

with Context(True):
  raise RuntimeError('error message handled')

print

with Context(False):
  raise RuntimeError('error message propagated')


印刷結果

__init__(True)
__enter__()
__exit__(, error message handled, )

__init__(False)
__enter__()
__exit__(, error message propagated, )
Traceback (most recent call last):
 File "test.py", line 23, in 
   raise RuntimeError('error message propagated')
   RuntimeError: error message propagated


ジェネレータからコンテキストマネージャへ
クラスと__を記述することによって、コンテキスト管理の従来の方法を作成します.enter__()と_exit__()方法は、難しくありません.しかし、必要なコストよりもわずかなコンテキストを管理することがあります.このような場合、contextmanager()decoratorジェネレータ関数を使用してコンテキストマネージャに変換できます.

import contextlib

@contextlib.contextmanager
def make_context():
  print ' entering'
  try:
    yield {}
   except RuntimeError, err:
    print ' Error:', err
  finally:
    print ' exiting'
    
print 'Normal:'

with make_context() as value:
  print ' inside with statement:', value
  
print
print 'handled ereor:'

with make_context() as value:
  raise RuntimeError('show example of handling an error')

print
print 'unhandled error:'

with make_context() as value:
  raise ValueError('this exception is not handled')


印刷結果

Normal:
 entering
 inside with statement: {}
  exiting

handled ereor:
entering
 Error: show example of handling an error
 exiting

unhandled error:
entering
exiting
Traceback (most recent call last):
 File "test.py", line 30, in 
   raise ValueError('this exception is not handled')
   ValueError: this exception is not handled


ネストされたコンテキスト
nested()を使用すると、複数のコンテキストを同時に管理できます.

import contextlib

@contextlib.contextmanager
def make_context(name):
  print 'entering:', name
  yield name
  print 'exiting:', name

with contextlib.nested(make_context('A'), make_context('B'), make_context('C')) as (A, B,   C):
  print 'inside with statement:', A, B, C


印刷結果

entering: A
entering: B
entering: C
inside with statement: A B C
exiting: C
exiting: B
exiting: A

Python 2.7以降のバージョンはnested()の使用に賛成しないため、直接ネストできる

import contextlib

@contextlib.contextmanager
def make_context(name):
  print 'entering:', name
  yield name
  print 'exiting:', name

with make_context('A') as A, make_context('B') as B, make_context('C') as C:
  print 'inside with statement:', A, B, C


Openのハンドルを閉じる
ファイルクラスはコンテキストマネージャをサポートしますが、サポートされていないオブジェクトもあります.close()メソッドを使用するクラスもありますが、コンテキストマネージャはサポートされていません.closing()を使用して、コンテキストマネージャを作成します.(クラスにはcloseメソッドが必要です)

import contextlib


class Door(object):
  def __init__(self):
    print ' __init__()'
    
  def close(self):
    print ' close()'

print 'Normal Example:'
with contextlib.closing(Door()) as door:
  print ' inside with statement'
  
print 
print 'Error handling example:'
try:
  with contextlib.closing(Door()) as door:
    print ' raising from inside with statement'
    raise RuntimeError('error message')
except Exception, err:
  print ' Had an error:', err


印刷結果

Normal Example:
  __init__()
  inside with statement
  close()

Error handling example:
  __init__()
  raising from inside with statement
  close()
  Had an error: error message