summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorent Xicluna <florent.xicluna@gmail.com>2010-03-08 15:20:28 (GMT)
committerFlorent Xicluna <florent.xicluna@gmail.com>2010-03-08 15:20:28 (GMT)
commit47627d51644d3fcc57390455ef845f72e6387485 (patch)
tree18cc9f986951d458645b12178436b4be1529f8b9
parent6f682be82b4ce2c64590fdc1255bd0b82d33b0f0 (diff)
downloadcpython-47627d51644d3fcc57390455ef845f72e6387485.zip
cpython-47627d51644d3fcc57390455ef845f72e6387485.tar.gz
cpython-47627d51644d3fcc57390455ef845f72e6387485.tar.bz2
#7624: Fix isinstance(foo(), collections.Callable) for old-style classes.
-rw-r--r--Lib/_abcoll.py33
-rw-r--r--Lib/abc.py8
-rw-r--r--Lib/test/test_collections.py32
-rw-r--r--Misc/NEWS3
4 files changed, 64 insertions, 12 deletions
diff --git a/Lib/_abcoll.py b/Lib/_abcoll.py
index 692a0d7..326c1f9 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 8aeb2af..c37ed8f 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 4246b23..a7be8d5 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 1f16de8..611f187 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