/Lib/contextlib.py

http://unladen-swallow.googlecode.com/ · Python · 153 lines · 66 code · 12 blank · 75 comment · 18 complexity · e76b6172110b382e9800e32a64b8f3b3 MD5 · raw file

  1. """Utilities for with-statement contexts. See PEP 343."""
  2. import sys
  3. from functools import wraps
  4. __all__ = ["contextmanager", "nested", "closing"]
  5. class GeneratorContextManager(object):
  6. """Helper for @contextmanager decorator."""
  7. def __init__(self, gen):
  8. self.gen = gen
  9. def __enter__(self):
  10. try:
  11. return self.gen.next()
  12. except StopIteration:
  13. raise RuntimeError("generator didn't yield")
  14. def __exit__(self, type, value, traceback):
  15. if type is None:
  16. try:
  17. self.gen.next()
  18. except StopIteration:
  19. return
  20. else:
  21. raise RuntimeError("generator didn't stop")
  22. else:
  23. if value is None:
  24. # Need to force instantiation so we can reliably
  25. # tell if we get the same exception back
  26. value = type()
  27. try:
  28. self.gen.throw(type, value, traceback)
  29. raise RuntimeError("generator didn't stop after throw()")
  30. except StopIteration, exc:
  31. # Suppress the exception *unless* it's the same exception that
  32. # was passed to throw(). This prevents a StopIteration
  33. # raised inside the "with" statement from being suppressed
  34. return exc is not value
  35. except:
  36. # only re-raise if it's *not* the exception that was
  37. # passed to throw(), because __exit__() must not raise
  38. # an exception unless __exit__() itself failed. But throw()
  39. # has to raise the exception to signal propagation, so this
  40. # fixes the impedance mismatch between the throw() protocol
  41. # and the __exit__() protocol.
  42. #
  43. if sys.exc_info()[1] is not value:
  44. raise
  45. def contextmanager(func):
  46. """@contextmanager decorator.
  47. Typical usage:
  48. @contextmanager
  49. def some_generator(<arguments>):
  50. <setup>
  51. try:
  52. yield <value>
  53. finally:
  54. <cleanup>
  55. This makes this:
  56. with some_generator(<arguments>) as <variable>:
  57. <body>
  58. equivalent to this:
  59. <setup>
  60. try:
  61. <variable> = <value>
  62. <body>
  63. finally:
  64. <cleanup>
  65. """
  66. @wraps(func)
  67. def helper(*args, **kwds):
  68. return GeneratorContextManager(func(*args, **kwds))
  69. return helper
  70. @contextmanager
  71. def nested(*managers):
  72. """Support multiple context managers in a single with-statement.
  73. Code like this:
  74. with nested(A, B, C) as (X, Y, Z):
  75. <body>
  76. is equivalent to this:
  77. with A as X:
  78. with B as Y:
  79. with C as Z:
  80. <body>
  81. """
  82. exits = []
  83. vars = []
  84. exc = (None, None, None)
  85. try:
  86. for mgr in managers:
  87. exit = mgr.__exit__
  88. enter = mgr.__enter__
  89. vars.append(enter())
  90. exits.append(exit)
  91. yield vars
  92. except:
  93. exc = sys.exc_info()
  94. finally:
  95. while exits:
  96. exit = exits.pop()
  97. try:
  98. if exit(*exc):
  99. exc = (None, None, None)
  100. except:
  101. exc = sys.exc_info()
  102. if exc != (None, None, None):
  103. # Don't rely on sys.exc_info() still containing
  104. # the right information. Another exception may
  105. # have been raised and caught by an exit method
  106. raise exc[0], exc[1], exc[2]
  107. class closing(object):
  108. """Context to automatically close something at the end of a block.
  109. Code like this:
  110. with closing(<module>.open(<arguments>)) as f:
  111. <block>
  112. is equivalent to this:
  113. f = <module>.open(<arguments>)
  114. try:
  115. <block>
  116. finally:
  117. f.close()
  118. """
  119. def __init__(self, thing):
  120. self.thing = thing
  121. def __enter__(self):
  122. return self.thing
  123. def __exit__(self, *exc_info):
  124. self.thing.close()