[Python-checkins] r78800 - in python/trunk: Lib/_abcoll.py Lib/abc.py Lib/test/test_collections.py Misc/NEWS

florent.xicluna python-checkins at python.org
Mon Mar 8 16:20:28 CET 2010


Author: florent.xicluna
Date: Mon Mar  8 16:20:28 2010
New Revision: 78800

Log:
#7624: Fix isinstance(foo(), collections.Callable) for old-style classes.


Modified:
   python/trunk/Lib/_abcoll.py
   python/trunk/Lib/abc.py
   python/trunk/Lib/test/test_collections.py
   python/trunk/Misc/NEWS

Modified: python/trunk/Lib/_abcoll.py
==============================================================================
--- python/trunk/Lib/_abcoll.py	(original)
+++ python/trunk/Lib/_abcoll.py	Mon Mar  8 16:20:28 2010
@@ -21,6 +21,14 @@
 
 ### 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 @@
     @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 @@
     @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 @@
     @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 @@
     @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 @@
     @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 @@
     @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
 

Modified: python/trunk/Lib/abc.py
==============================================================================
--- python/trunk/Lib/abc.py	(original)
+++ python/trunk/Lib/abc.py	Mon Mar  8 16:20:28 2010
@@ -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 @@
         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

Modified: python/trunk/Lib/test/test_collections.py
==============================================================================
--- python/trunk/Lib/test/test_collections.py	(original)
+++ python/trunk/Lib/test/test_collections.py	Mon Mar  8 16:20:28 2010
@@ -231,6 +231,27 @@
             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 @@
         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 @@
         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 @@
             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 @@
             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 @@
             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 @@
             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 @@
                          [('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)

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Mon Mar  8 16:20:28 2010
@@ -15,6 +15,9 @@
 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


More information about the Python-checkins mailing list