Issue #5: support old-style classes in ExitStack

This commit is contained in:
Nick Coghlan 2016-05-02 16:41:56 +10:00
parent c8efb9adcc
commit 911b459522
4 changed files with 52 additions and 7 deletions

View file

@ -4,7 +4,9 @@ Release History
0.5.3 (not yet released)
^^^^^^^^^^^^^^^^^^^^^^^^
* TBD
* ``ExitStack`` now correctly handles context managers implemented as old-style
classes in Python 2.x (such as ``codecs.StreamReader`` and
``codecs.StreamWriter``)
0.5.2 (2016-05-02)
^^^^^^^^^^^^^^^^^^

View file

@ -288,6 +288,20 @@ else:
exc_type, exc_value, exc_tb = exc_details
exec ("raise exc_type, exc_value, exc_tb")
# Handle old-style classes if they exist
try:
from types import InstanceType
except ImportError:
# Python 3 doesn't have old-style classes
_get_type = type
else:
# Need to handle old-style context managers on Python 2
def _get_type(obj):
obj_type = type(obj)
if obj_type is InstanceType:
return obj.__class__ # Old-style class
return obj_type # New-style class
# Inspired by discussions on http://bugs.python.org/issue13585
class ExitStack(object):
"""Context manager for dynamic management of a stack of exit callbacks
@ -328,7 +342,7 @@ class ExitStack(object):
"""
# We use an unbound method rather than a bound method to follow
# the standard lookup behaviour for special methods
_cb_type = type(exit)
_cb_type = _get_type(exit)
try:
exit_method = _cb_type.__exit__
except AttributeError:
@ -358,7 +372,7 @@ class ExitStack(object):
returns the result of the __enter__ method.
"""
# We look up the special methods on the type to match the with statement
_cm_type = type(cm)
_cm_type = _get_type(cm)
_exit = _cm_type.__exit__
result = _cm_type.__enter__(cm)
self._push_cm_exit(cm, _exit)

View file

@ -310,10 +310,6 @@ Functions and classes provided:
foundation for higher level context managers that manipulate the exit
stack in application specific ways.
Context managers used with :class:`ExitStack` must be new-style classes -
this is the default on Python 3, but requires explicitly inheriting from
:class:`object` or another new-style class in Python 2.
.. versionadded:: 0.4
Part of the standard library in Python 3.3 and later

View file

@ -706,6 +706,39 @@ class TestExitStack(unittest.TestCase):
stack.push(cm)
self.assertIs(stack._exit_callbacks[-1], cm)
def test_default_class_semantics(self):
# For Python 2.x, this ensures compatibility with old-style classes
# For Python 3.x, it just reruns some of the other tests
class DefaultCM:
def __enter__(self):
result.append("Enter")
def __exit__(self, *exc_details):
result.append("Exit")
class DefaultCallable:
def __call__(self, *exc_details):
result.append("Callback")
result = []
cm = DefaultCM()
cb = DefaultCallable()
with ExitStack() as stack:
stack.enter_context(cm)
self.assertIs(stack._exit_callbacks[-1].__self__, cm)
stack.push(cb)
stack.push(cm)
self.assertIs(stack._exit_callbacks[-1].__self__, cm)
result.append("Running")
stack.callback(cb)
self.assertIs(stack._exit_callbacks[-1].__wrapped__, cb)
self.assertEqual(result, ["Enter", "Running",
"Callback", "Exit",
"Callback", "Exit",
])
with ExitStack():
pass
class TestRedirectStream: