From 47627d51644d3fcc57390455ef845f72e6387485 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Mon, 8 Mar 2010 15:20:28 +0000 Subject: [PATCH] #7624: Fix isinstance(foo(), collections.Callable) for old-style classes. --- Lib/_abcoll.py | 33 +++++++++++++++++++++++---------- Lib/abc.py | 8 ++++++++ Lib/test/test_collections.py | 32 ++++++++++++++++++++++++++++++-- Misc/NEWS | 3 +++ 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/Lib/_abcoll.py b/Lib/_abcoll.py index 692a0d7967..326c1f9b50 100644 --- a/Lib/_abcoll.py +++ b/Lib/_abcoll.py @@ -21,6 +21,14 @@ __all__ = ["Hashable", "Iterable", "Iterator", ### ONE-TRICK PONIES ### +def _hasattr(C, attr): + try: + return any(attr in B.__dict__ for B in C.__mro__) + except AttributeError: + # Old-style class + return hasattr(C, attr) + + class Hashable: __metaclass__ = ABCMeta @@ -31,11 +39,16 @@ class Hashable: @classmethod def __subclasshook__(cls, C): if cls is Hashable: - for B in C.__mro__: - if "__hash__" in B.__dict__: - if B.__dict__["__hash__"]: - return True - break + try: + for B in C.__mro__: + if "__hash__" in B.__dict__: + if B.__dict__["__hash__"]: + return True + break + except AttributeError: + # Old-style class + if getattr(C, "__hash__", None): + return True return NotImplemented @@ -50,7 +63,7 @@ class Iterable: @classmethod def __subclasshook__(cls, C): if cls is Iterable: - if any("__iter__" in B.__dict__ for B in C.__mro__): + if _hasattr(C, "__iter__"): return True return NotImplemented @@ -69,7 +82,7 @@ class Iterator(Iterable): @classmethod def __subclasshook__(cls, C): if cls is Iterator: - if any("next" in B.__dict__ for B in C.__mro__): + if _hasattr(C, "next"): return True return NotImplemented @@ -84,7 +97,7 @@ class Sized: @classmethod def __subclasshook__(cls, C): if cls is Sized: - if any("__len__" in B.__dict__ for B in C.__mro__): + if _hasattr(C, "__len__"): return True return NotImplemented @@ -99,7 +112,7 @@ class Container: @classmethod def __subclasshook__(cls, C): if cls is Container: - if any("__contains__" in B.__dict__ for B in C.__mro__): + if _hasattr(C, "__contains__"): return True return NotImplemented @@ -114,7 +127,7 @@ class Callable: @classmethod def __subclasshook__(cls, C): if cls is Callable: - if any("__call__" in B.__dict__ for B in C.__mro__): + if _hasattr(C, "__call__"): return True return NotImplemented diff --git a/Lib/abc.py b/Lib/abc.py index 8aeb2af616..c37ed8fd61 100644 --- a/Lib/abc.py +++ b/Lib/abc.py @@ -4,6 +4,11 @@ """Abstract Base Classes (ABCs) according to PEP 3119.""" +# Instance of old-style class +class _C: pass +_InstanceType = type(_C()) + + def abstractmethod(funcobj): """A decorator indicating abstract methods. @@ -124,6 +129,9 @@ class ABCMeta(type): if subclass in cls._abc_cache: return True subtype = type(instance) + # Old-style instances + if subtype is _InstanceType: + subtype = subclass if subtype is subclass or subclass is None: if (cls._abc_negative_cache_version == ABCMeta._abc_invalidation_counter and diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 4246b23357..a7be8d5128 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -231,6 +231,27 @@ class ABCTestCase(unittest.TestCase): C = type('C', (abc,), stubs) self.assertRaises(TypeError, C, name) + def validate_isinstance(self, abc, name): + stub = lambda s, *args: 0 + + # new-style class + C = type('C', (object,), {name: stub}) + self.assertIsInstance(C(), abc) + self.assertTrue(issubclass(C, abc)) + # old-style class + class C: pass + setattr(C, name, stub) + self.assertIsInstance(C(), abc) + self.assertTrue(issubclass(C, abc)) + + # new-style class + C = type('C', (object,), {'__hash__': None}) + self.assertNotIsInstance(C(), abc) + self.assertFalse(issubclass(C, abc)) + # old-style class + class C: pass + self.assertNotIsInstance(C(), abc) + self.assertFalse(issubclass(C, abc)) class TestOneTrickPonyABCs(ABCTestCase): @@ -259,6 +280,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.assertEqual(hash(H()), 0) self.assertFalse(issubclass(int, H)) self.validate_abstract_methods(Hashable, '__hash__') + self.validate_isinstance(Hashable, '__hash__') def test_Iterable(self): # Check some non-iterables @@ -283,6 +305,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.assertEqual(list(I()), []) self.assertFalse(issubclass(str, I)) self.validate_abstract_methods(Iterable, '__iter__') + self.validate_isinstance(Iterable, '__iter__') def test_Iterator(self): non_samples = [None, 42, 3.14, 1j, "".encode('ascii'), "", (), [], @@ -302,6 +325,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.assertIsInstance(x, Iterator) self.assertTrue(issubclass(type(x), Iterator), repr(type(x))) self.validate_abstract_methods(Iterator, 'next') + self.validate_isinstance(Iterator, 'next') def test_Sized(self): non_samples = [None, 42, 3.14, 1j, @@ -319,6 +343,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.assertIsInstance(x, Sized) self.assertTrue(issubclass(type(x), Sized), repr(type(x))) self.validate_abstract_methods(Sized, '__len__') + self.validate_isinstance(Sized, '__len__') def test_Container(self): non_samples = [None, 42, 3.14, 1j, @@ -336,6 +361,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.assertIsInstance(x, Container) self.assertTrue(issubclass(type(x), Container), repr(type(x))) self.validate_abstract_methods(Container, '__contains__') + self.validate_isinstance(Container, '__contains__') def test_Callable(self): non_samples = [None, 42, 3.14, 1j, @@ -355,6 +381,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.assertIsInstance(x, Callable) self.assertTrue(issubclass(type(x), Callable), repr(type(x))) self.validate_abstract_methods(Callable, '__call__') + self.validate_isinstance(Callable, '__call__') def test_direct_subclassing(self): for B in Hashable, Iterable, Iterator, Sized, Container, Callable: @@ -515,8 +542,9 @@ class TestCounter(unittest.TestCase): [('a', 3), ('b', 2), ('c', 1)]) self.assertEqual(c['b'], 2) self.assertEqual(c['z'], 0) - self.assertEqual(c.has_key('c'), True) - self.assertEqual(c.has_key('z'), False) + with test_support.check_py3k_warnings(): + self.assertEqual(c.has_key('c'), True) + self.assertEqual(c.has_key('z'), False) self.assertEqual(c.__contains__('c'), True) self.assertEqual(c.__contains__('z'), False) self.assertEqual(c.get('b', 10), 2) diff --git a/Misc/NEWS b/Misc/NEWS index 1f16de8d8d..611f187140 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -15,6 +15,9 @@ Core and Builtins Library ------- +- Issue #7624: Fix isinstance(foo(), collections.Callable) for old-style + classes. + - Issue #7143: get_payload used to strip any trailing newline from a base64 transfer-encoded payload *after* decoding it; it no longer does. This is a behavior change, so email's minor version number is now -- 2.40.0