From: Florent Xicluna Date: Tue, 23 Mar 2010 12:37:29 +0000 (+0000) Subject: Merged revisions 78800 via svnmerge from X-Git-Tag: v2.6.6rc1~547 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d474f3a23953d55a34588c75cfc2641c4d0da81b;p=python Merged revisions 78800 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk ........ r78800 | florent.xicluna | 2010-03-08 16:20:28 +0100 (lun, 08 mar 2010) | 2 lines #7624: Fix isinstance(foo(), collections.Callable) for old-style classes. ........ --- diff --git a/Lib/_abcoll.py b/Lib/_abcoll.py index d8cdc98c4c..82ded379a4 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 95126d8a18..681c753215 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 4a068e057c..d1fd60eec5 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -210,6 +210,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.assertTrue(isinstance(C(), abc)) + self.assertTrue(issubclass(C, abc)) + # old-style class + class C: pass + setattr(C, name, stub) + self.assertTrue(isinstance(C(), abc)) + self.assertTrue(issubclass(C, abc)) + + # new-style class + C = type('C', (object,), {'__hash__': None}) + self.assertFalse(isinstance(C(), abc)) + self.assertFalse(issubclass(C, abc)) + # old-style class + class C: pass + self.assertFalse(isinstance(C(), abc)) + self.assertFalse(issubclass(C, abc)) class TestOneTrickPonyABCs(ABCTestCase): @@ -238,6 +259,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.assertEqual(hash(H()), 0) self.failIf(issubclass(int, H)) self.validate_abstract_methods(Hashable, '__hash__') + self.validate_isinstance(Hashable, '__hash__') def test_Iterable(self): # Check some non-iterables @@ -262,6 +284,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.assertEqual(list(I()), []) self.failIf(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'), "", (), [], @@ -281,6 +304,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.failUnless(isinstance(x, Iterator), repr(x)) self.failUnless(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, @@ -298,6 +322,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.failUnless(isinstance(x, Sized), repr(x)) self.failUnless(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, @@ -315,6 +340,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.failUnless(isinstance(x, Container), repr(x)) self.failUnless(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, @@ -334,6 +360,7 @@ class TestOneTrickPonyABCs(ABCTestCase): self.failUnless(isinstance(x, Callable), repr(x)) self.failUnless(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: @@ -475,7 +502,7 @@ class TestCollectionABCs(ABCTestCase): self.validate_abstract_methods(MutableSequence, '__contains__', '__iter__', '__len__', '__getitem__', '__setitem__', '__delitem__', 'insert') -import doctest, collections +import collections def test_main(verbose=None): NamedTupleDocs = doctest.DocTestSuite(module=collections) diff --git a/Misc/NEWS b/Misc/NEWS index 9dfb8049d3..38c5f36680 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -23,6 +23,9 @@ Core and Builtins Library ------- +- Issue #7624: Fix isinstance(foo(), collections.Callable) for old-style + classes. + - Issue #7512: shutil.copystat() could raise an OSError when the filesystem didn't support chflags() (for example ZFS under FreeBSD). The error is now silenced.