diff -r 398cb8c183da Doc/library/collections.abc.rst
--- a/Doc/library/collections.abc.rst Thu Jan 07 10:58:20 2016 -0800
+++ b/Doc/library/collections.abc.rst Mon Jan 18 11:25:33 2016 -0800
@@ -42,10 +42,11 @@
:class:`Iterator` :class:`Iterable` ``__next__`` ``__iter__``
:class:`Generator` :class:`Iterator` ``send``, ``throw`` ``close``, ``__iter__``, ``__next__``
:class:`Sized` ``__len__``
+:class:`Reversible` :class:`Iterable` ``__reversed__``
:class:`Callable` ``__call__``
:class:`Sequence` :class:`Sized`, ``__getitem__``, ``__contains__``, ``__iter__``, ``__reversed__``,
- :class:`Iterable`, ``__len__`` ``index``, and ``count``
+ :class:`Reversible`, ``__len__`` ``index``, and ``count``
:class:`Container`
:class:`MutableSequence` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods and
@@ -116,6 +117,13 @@
.. versionadded:: 3.5
+.. class:: Reversible
+
+ ABC for iterable classes that also provide the :meth:`__reversed__`
+ method.
+
+ .. versionadded:: 3.6
+
.. class:: Sequence
MutableSequence
diff -r 398cb8c183da Doc/reference/datamodel.rst
--- a/Doc/reference/datamodel.rst Thu Jan 07 10:58:20 2016 -0800
+++ b/Doc/reference/datamodel.rst Mon Jan 18 11:25:33 2016 -0800
@@ -1064,6 +1064,12 @@
operation raise an exception when no appropriate method is defined (typically
:exc:`AttributeError` or :exc:`TypeError`).
+Setting a special method to ``None`` indicates that the corresponding
+operation is not available. For example, if a class sets
+:meth:`__iter__` to ``None``, the class is not iterable, so calling
+:func:`iter` on its instances will raise a :exc:`TypeError` (without
+falling back to :meth:`__getitem__`). [#]_
+
When implementing a class that emulates any built-in type, it is important that
the emulation only be implemented to the degree that it makes sense for the
object being modelled. For example, some sequences may work well with retrieval
@@ -2055,7 +2061,7 @@
(``+``, ``-``, ``*``, ``@``, ``/``, ``//``, ``%``, :func:`divmod`,
:func:`pow`, ``**``, ``<<``, ``>>``, ``&``, ``^``, ``|``) with reflected
(swapped) operands. These functions are only called if the left operand does
- not support the corresponding operation and the operands are of different
+ not support the corresponding operation [#]_ and the operands are of different
types. [#]_ For instance, to evaluate the expression ``x - y``, where *y* is
an instance of a class that has an :meth:`__rsub__` method, ``y.__rsub__(x)``
is called if ``x.__sub__(y)`` returns *NotImplemented*.
@@ -2423,6 +2429,17 @@
controlled conditions. It generally isn't a good idea though, since it can
lead to some very strange behaviour if it is handled incorrectly.
+.. [#] The :meth:`__hash__`, :meth:`__iter__`, :meth:`__reversed__`, and
+ :meth:`__contains__` methods have special handling for this; others
+ will still raise a :exc:`TypeError`, but may do so by relying on
+ the behavior that ``None`` is not callable.
+
+.. [#] "Does not support" here means that the class has no such method, or
+ the method returns ``NotImplemented``. Do not set the method to
+ ``None`` if you want to force fallback to the right operand's reflected
+ method--that will instead have the opposite effect of explicitly
+ *blocking* such fallback.
+
.. [#] For operands of the same type, it is assumed that if the non-reflected method
(such as :meth:`__add__`) fails the operation is not supported, which is why the
reflected method is not called.
diff -r 398cb8c183da Lib/_collections_abc.py
--- a/Lib/_collections_abc.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/_collections_abc.py Mon Jan 18 11:25:33 2016 -0800
@@ -11,7 +11,7 @@
__all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator",
"Hashable", "Iterable", "Iterator", "Generator",
- "Sized", "Container", "Callable",
+ "Sized", "Container", "Reversible", "Callable",
"Set", "MutableSet",
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
@@ -62,6 +62,18 @@
### ONE-TRICK PONIES ###
+def _check_methods(C, *methods):
+ mro = C.__mro__
+ for method in methods:
+ for B in mro:
+ if method in B.__dict__:
+ if B.__dict__[method] is None:
+ return NotImplemented
+ break
+ else:
+ return NotImplemented
+ return True
+
class Hashable(metaclass=ABCMeta):
__slots__ = ()
@@ -73,11 +85,7 @@
@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
+ return _check_methods(C, "__hash__")
return NotImplemented
@@ -92,11 +100,7 @@
@classmethod
def __subclasshook__(cls, C):
if cls is Awaitable:
- for B in C.__mro__:
- if "__await__" in B.__dict__:
- if B.__dict__["__await__"]:
- return True
- break
+ return _check_methods(C, "__await__")
return NotImplemented
@@ -137,14 +141,7 @@
@classmethod
def __subclasshook__(cls, C):
if cls is Coroutine:
- mro = C.__mro__
- for method in ('__await__', 'send', 'throw', 'close'):
- for base in mro:
- if method in base.__dict__:
- break
- else:
- return NotImplemented
- return True
+ return _check_methods(C, '__await__', 'send', 'throw', 'close')
return NotImplemented
@@ -162,8 +159,7 @@
@classmethod
def __subclasshook__(cls, C):
if cls is AsyncIterable:
- if any("__aiter__" in B.__dict__ for B in C.__mro__):
- return True
+ return _check_methods(C, "__aiter__")
return NotImplemented
@@ -182,9 +178,7 @@
@classmethod
def __subclasshook__(cls, C):
if cls is AsyncIterator:
- if (any("__anext__" in B.__dict__ for B in C.__mro__) and
- any("__aiter__" in B.__dict__ for B in C.__mro__)):
- return True
+ return _check_methods(C, "__anext__", "__aiter__")
return NotImplemented
@@ -200,8 +194,7 @@
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
- if any("__iter__" in B.__dict__ for B in C.__mro__):
- return True
+ return _check_methods(C, "__iter__")
return NotImplemented
@@ -220,9 +213,7 @@
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
- if (any("__next__" in B.__dict__ for B in C.__mro__) and
- any("__iter__" in B.__dict__ for B in C.__mro__)):
- return True
+ return _check_methods(C, "__next__", "__iter__")
return NotImplemented
Iterator.register(bytes_iterator)
@@ -283,14 +274,8 @@
@classmethod
def __subclasshook__(cls, C):
if cls is Generator:
- mro = C.__mro__
- for method in ('__iter__', '__next__', 'send', 'throw', 'close'):
- for base in mro:
- if method in base.__dict__:
- break
- else:
- return NotImplemented
- return True
+ return _check_methods(C, '__iter__', '__next__',
+ 'send', 'throw', 'close')
return NotImplemented
@@ -308,8 +293,7 @@
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
- if any("__len__" in B.__dict__ for B in C.__mro__):
- return True
+ return _check_methods(C, "__len__")
return NotImplemented
@@ -324,8 +308,7 @@
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
- if any("__contains__" in B.__dict__ for B in C.__mro__):
- return True
+ return _check_methods(C, "__contains__")
return NotImplemented
@@ -340,8 +323,23 @@
@classmethod
def __subclasshook__(cls, C):
if cls is Callable:
- if any("__call__" in B.__dict__ for B in C.__mro__):
- return True
+ return _check_methods(C, "__call__")
+ return NotImplemented
+
+
+class Reversible(Iterable):
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __reversed__(self):
+ while False:
+ yield None
+
+ @classmethod
+ def __subclasshook__(cls, C):
+ if cls is Reversible:
+ return _check_methods(C, "__reversed__", "__iter__")
return NotImplemented
@@ -621,6 +619,8 @@
return NotImplemented
return dict(self.items()) == dict(other.items())
+ __reversed__ = None
+
Mapping.register(mappingproxy)
@@ -794,7 +794,7 @@
### SEQUENCES ###
-class Sequence(Sized, Iterable, Container):
+class Sequence(Sized, Reversible, Container):
"""All the operations on a read-only sequence.
diff -r 398cb8c183da Lib/test/test_augassign.py
--- a/Lib/test/test_augassign.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_augassign.py Mon Jan 18 11:25:33 2016 -0800
@@ -83,6 +83,10 @@
def __iadd__(self, val):
return aug_test3(self.val + val)
+ class aug_test4(aug_test3):
+ """Blocks inheritance, and fallback to __add__"""
+ __iadd__ = None
+
x = aug_test(1)
y = x
x += 10
@@ -106,6 +110,9 @@
self.assertTrue(y is not x)
self.assertEqual(x.val, 13)
+ x = aug_test4(4)
+ with self.assertRaises(TypeError):
+ x += 10
def testCustomMethods2(test_self):
output = []
diff -r 398cb8c183da Lib/test/test_binop.py
--- a/Lib/test/test_binop.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_binop.py Mon Jan 18 11:25:33 2016 -0800
@@ -388,6 +388,54 @@
self.assertEqual(op_sequence(eq, B, V), ['B.__eq__', 'V.__eq__'])
self.assertEqual(op_sequence(le, B, V), ['B.__le__', 'V.__ge__'])
+class E(object):
+ """Class that can test equality"""
+ def __eq__(self, other):
+ return True
+
+class S(E):
+ """Subclass of E that should fail"""
+ __eq__ = None
+class F(object):
+ """Independent class that should fall back"""
+
+class X(object):
+ """Independent class that should fail"""
+ __eq__ = None
+
+class SN(E):
+ """Subclass of E that can test equality, but not non-equality"""
+ __ne__ = None
+
+class XN:
+ """Independent class that can test equality, but not non-equality"""
+ def __eq__(self, other):
+ return True
+ __ne__ = None
+
+class FallbackBlockingTests(unittest.TestCase):
+ """Unit tests for None method blocking"""
+
+ def test_fallback_rmethod_blocking(self):
+ e, f, s, x = E(), F(), S(), X()
+ self.assertEqual(e, e)
+ self.assertEqual(e, f)
+ self.assertEqual(f, e)
+ # left operand is checked first
+ self.assertEqual(e, x)
+ self.assertRaises(TypeError, eq, x, e)
+ # S is a subclass, so it's always checked first
+ self.assertRaises(TypeError, eq, e, s)
+ self.assertRaises(TypeError, eq, s, e)
+
+ def test_fallback_ne_blocking(self):
+ e, sn, xn = E(), SN(), XN()
+ self.assertFalse(e != e)
+ self.assertRaises(TypeError, ne, e, sn)
+ self.assertRaises(TypeError, ne, sn, e)
+ self.assertFalse(e != xn)
+ self.assertRaises(TypeError, ne, xn, e)
+
if __name__ == "__main__":
unittest.main()
diff -r 398cb8c183da Lib/test/test_bool.py
--- a/Lib/test/test_bool.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_bool.py Mon Jan 18 11:25:33 2016 -0800
@@ -329,6 +329,17 @@
except (Exception) as e_len:
self.assertEqual(str(e_bool), str(e_len))
+ def test_blocked(self):
+ class A:
+ __bool__ = None
+ self.assertRaises(TypeError, bool, A())
+
+ class B:
+ def __len__(self):
+ return 10
+ __bool__ = None
+ self.assertRaises(TypeError, bool, B())
+
def test_real_and_imag(self):
self.assertEqual(True.real, 1)
self.assertEqual(True.imag, 0)
diff -r 398cb8c183da Lib/test/test_bytes.py
--- a/Lib/test/test_bytes.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_bytes.py Mon Jan 18 11:25:33 2016 -0800
@@ -917,6 +917,36 @@
PyBytes_FromFormat, b'%c', c_int(256))
+ def test_bytes_blocking(self):
+ class IterationBlocked(list):
+ __bytes__ = None
+ i = [0, 1, 2, 3]
+ self.assertEqual(bytes(i), b'\x00\x01\x02\x03')
+ self.assertRaises(TypeError, bytes, IterationBlocked(i))
+
+ # At least in CPython, because bytes.__new__ and the C API
+ # PyBytes_FromObject have different fallback rules, integer
+ # fallback is handled specially, so test separately.
+ class IntBlocked(int):
+ __bytes__ = None
+ self.assertEqual(bytes(3), b'\0\0\0')
+ self.assertRaises(TypeError, bytes, IntBlocked(3))
+
+ # While there is no separately-defined rule for handling bytes
+ # subclasses differently from other buffer-interface classes,
+ # an implementation may well special-case them (as CPython 2.x
+ # str did), so test them separately.
+ class BytesSubclassBlocked(bytes):
+ __bytes__ = None
+ self.assertEqual(bytes(b'ab'), b'ab')
+ self.assertRaises(TypeError, bytes, BytesSubclassBlocked(b'ab'))
+
+ class BufferBlocked(bytearray):
+ __bytes__ = None
+ ba, bb = bytearray(b'ab'), BufferBlocked(b'ab')
+ self.assertEqual(bytes(ba), b'ab')
+ self.assertRaises(TypeError, bytes, bb)
+
class ByteArrayTest(BaseBytesTest, unittest.TestCase):
type2test = bytearray
diff -r 398cb8c183da Lib/test/test_collections.py
--- a/Lib/test/test_collections.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_collections.py Mon Jan 18 11:25:33 2016 -0800
@@ -21,7 +21,7 @@
from collections import deque
from collections.abc import Awaitable, Coroutine, AsyncIterator, AsyncIterable
from collections.abc import Hashable, Iterable, Iterator, Generator
-from collections.abc import Sized, Container, Callable
+from collections.abc import Sized, Container, Reversible, Callable
from collections.abc import Set, MutableSet
from collections.abc import Mapping, MutableMapping, KeysView, ItemsView
from collections.abc import Sequence, MutableSequence
@@ -688,6 +688,73 @@
self.assertFalse(issubclass(str, I))
self.validate_abstract_methods(Iterable, '__iter__')
self.validate_isinstance(Iterable, '__iter__')
+ # Check None blocking
+ class It:
+ def __iter__(self): return iter([])
+ class ItBlocked(It):
+ __iter__ = None
+ self.assertTrue(issubclass(It, Iterable))
+ self.assertTrue(isinstance(It(), Iterable))
+ self.assertFalse(issubclass(ItBlocked, Iterable))
+ self.assertFalse(isinstance(ItBlocked(), Iterable))
+
+ def test_Reversible(self):
+ # Check some non-iterables
+ non_iterables = [None, 42, 3.14, 1j]
+ for x in non_iterables:
+ self.assertNotIsInstance(x, Reversible)
+ self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
+ # Check some non-reversible iterables
+ non_reversibles = [set(), frozenset(), dict(), dict().keys(),
+ dict().items(), dict().values(),
+ Counter(), Counter().keys(), Counter().items(),
+ Counter().values(), (lambda: (yield))(),
+ (x for x in []), iter([]), reversed([])
+ ]
+ for x in non_reversibles:
+ self.assertNotIsInstance(x, Reversible)
+ self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
+ # Check some reversible iterables
+ samples = [bytes(), str(),
+ tuple(), list(), OrderedDict(), OrderedDict().keys(),
+ OrderedDict().items(), OrderedDict().values(),
+ ]
+ for x in samples:
+ self.assertIsInstance(x, Reversible)
+ self.assertTrue(issubclass(type(x), Reversible), repr(type(x)))
+ # Check direct subclassing
+ class R(Reversible):
+ def __iter__(self):
+ return super().__iter__()
+ def __reversed__(self):
+ return super().__reversed__()
+ self.assertEqual(list(R()), [])
+ self.assertEqual(list(reversed(R())), [])
+ self.assertFalse(issubclass(str, R))
+ self.validate_abstract_methods(Reversible, '__reversed__', '__iter__')
+ # Check reversible non-iterable (which is not Reversible)
+ class RevNoIter:
+ def __reversed__(self): return reversed([])
+ class RevPlusIter(RevNoIter):
+ def __iter__(self): return iter([])
+ self.assertFalse(issubclass(RevNoIter, Reversible))
+ self.assertFalse(isinstance(RevNoIter(), Reversible))
+ self.assertTrue(issubclass(RevPlusIter, Reversible))
+ self.assertTrue(isinstance(RevPlusIter(), Reversible))
+ # Check None blocking
+ class Rev:
+ def __iter__(self): return iter([])
+ def __reversed__(self): return reversed([])
+ class RevItBlocked(Rev):
+ __iter__ = None
+ class RevRevBlocked(Rev):
+ __reversed__ = None
+ self.assertTrue(issubclass(Rev, Reversible))
+ self.assertTrue(isinstance(Rev(), Reversible))
+ self.assertFalse(issubclass(RevItBlocked, Reversible))
+ self.assertFalse(isinstance(RevItBlocked(), Reversible))
+ self.assertFalse(issubclass(RevRevBlocked, Reversible))
+ self.assertFalse(isinstance(RevRevBlocked(), Reversible))
def test_Iterator(self):
non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()]
@@ -1219,6 +1286,7 @@
def __iter__(self):
return iter(())
self.validate_comparison(MyMapping())
+ self.assertRaises(TypeError, reversed, MyMapping())
def test_MutableMapping(self):
for sample in [dict]:
diff -r 398cb8c183da Lib/test/test_contains.py
--- a/Lib/test/test_contains.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_contains.py Mon Jan 18 11:25:33 2016 -0800
@@ -84,6 +84,32 @@
self.assertTrue(container == constructor(values))
self.assertTrue(container == container)
+ def test_block_fallback(self):
+ # blocking fallback with __contains__ = None
+ class ByContains(object):
+ def __contains__(self, other):
+ return False
+ c = ByContains()
+ class BlockContains(ByContains):
+ """Is not a container
+
+ This class is a perfectly good iterable (as tested by
+ list(bc)), as well as inheriting from a perfectly good
+ container, but __contains__ = None prevents the usual
+ fallback to iteration in the container protocol. That
+ is, normally, 0 in bc would fall back to the equivalent
+ of any(x==0 for x in bc), but here it's blocked from
+ doing so.
+ """
+ def __iter__(self):
+ while False:
+ yield None
+ __contains__ = None
+ bc = BlockContains()
+ self.assertFalse(0 in c)
+ self.assertFalse(0 in list(bc))
+ self.assertRaises(TypeError, lambda: 0 in bc)
+
if __name__ == '__main__':
unittest.main()
diff -r 398cb8c183da Lib/test/test_enumerate.py
--- a/Lib/test/test_enumerate.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_enumerate.py Mon Jan 18 11:25:33 2016 -0800
@@ -223,7 +223,7 @@
def test_objmethods(self):
# Objects must have __len__() and __getitem__() implemented.
class NoLen(object):
- def __getitem__(self): return 1
+ def __getitem__(self, i): return 1
nl = NoLen()
self.assertRaises(TypeError, reversed, nl)
@@ -232,6 +232,13 @@
ngi = NoGetItem()
self.assertRaises(TypeError, reversed, ngi)
+ class Blocked(object):
+ def __getitem__(self, i): return 1
+ def __len__(self): return 2
+ __reversed__ = None
+ b = Blocked()
+ self.assertRaises(TypeError, reversed, b)
+
def test_pickle(self):
for data in 'abc', range(5), tuple(enumerate('abc')), range(1,17,5):
self.check_pickle(reversed(data), list(data)[::-1])
diff -r 398cb8c183da Lib/test/test_functools.py
--- a/Lib/test/test_functools.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_functools.py Mon Jan 18 11:25:33 2016 -0800
@@ -1435,7 +1435,8 @@
m = mro(D, bases)
self.assertEqual(m, [D, c.MutableSequence, c.Sequence,
c.defaultdict, dict, c.MutableMapping,
- c.Mapping, c.Sized, c.Iterable, c.Container,
+ c.Mapping, c.Sized, c.Reversible,
+ c.Iterable, c.Container,
object])
# Container and Callable are registered on different base classes and
diff -r 398cb8c183da Lib/test/test_iter.py
--- a/Lib/test/test_iter.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_iter.py Mon Jan 18 11:25:33 2016 -0800
@@ -53,6 +53,14 @@
def __getitem__(self, i):
return i
+class DefaultIterClass:
+ pass
+
+class NoIterClass:
+ def __getitem__(self, i):
+ return i
+ __iter__ = None
+
# Main test suite
class TestCase(unittest.TestCase):
@@ -944,6 +952,10 @@
self.assertEqual(next(it), 0)
self.assertEqual(next(it), 1)
+ def test_error_iter(self):
+ for typ in (DefaultIterClass, NoIterClass):
+ self.assertRaises(TypeError, iter, typ())
+
def test_main():
run_unittest(TestCase)
diff -r 398cb8c183da Lib/test/test_unicode.py
--- a/Lib/test/test_unicode.py Thu Jan 07 10:58:20 2016 -0800
+++ b/Lib/test/test_unicode.py Mon Jan 18 11:25:33 2016 -0800
@@ -980,6 +980,19 @@
def __format__(self, format_spec):
return int.__format__(self * 2, format_spec)
+ class M:
+ def __init__(self, x):
+ self.x = x
+ def __repr__(self):
+ return 'M(' + self.x + ')'
+ __str__ = None
+
+ class N:
+ def __init__(self, x):
+ self.x = x
+ def __repr__(self):
+ return 'N(' + self.x + ')'
+ __format__ = None
self.assertEqual(''.format(), '')
self.assertEqual('abc'.format(), 'abc')
@@ -1194,6 +1207,16 @@
self.assertEqual("0x{:0{:d}X}".format(0x0,16), "0x0000000000000000")
+ # Blocking fallback
+ m = M('data')
+ self.assertEqual("{!r}".format(m), 'M(data)')
+ self.assertRaises(TypeError, "{!s}".format, m)
+ self.assertRaises(TypeError, "{}".format, m)
+ n = N('data')
+ self.assertEqual("{!r}".format(n), 'N(data)')
+ self.assertEqual("{!s}".format(n), 'N(data)')
+ self.assertRaises(TypeError, "{}".format, n)
+
def test_format_map(self):
self.assertEqual(''.format_map({}), '')
self.assertEqual('a'.format_map({}), 'a')
diff -r 398cb8c183da Objects/enumobject.c
--- a/Objects/enumobject.c Thu Jan 07 10:58:20 2016 -0800
+++ b/Objects/enumobject.c Mon Jan 18 11:25:33 2016 -0800
@@ -250,6 +250,13 @@
return NULL;
reversed_meth = _PyObject_LookupSpecial(seq, &PyId___reversed__);
+ if (reversed_meth == Py_None) {
+ Py_DECREF(reversed_meth);
+ PyErr_Format(PyExc_TypeError,
+ "'%.200s' object is not reversible",
+ Py_TYPE(seq)->tp_name);
+ return NULL;
+ }
if (reversed_meth != NULL) {
PyObject *res = PyObject_CallFunctionObjArgs(reversed_meth, NULL);
Py_DECREF(reversed_meth);
@@ -259,8 +266,9 @@
return NULL;
if (!PySequence_Check(seq)) {
- PyErr_SetString(PyExc_TypeError,
- "argument to reversed() must be a sequence");
+ PyErr_Format(PyExc_TypeError,
+ "'%.200s' object is not reversible",
+ Py_TYPE(seq)->tp_name);
return NULL;
}
diff -r 398cb8c183da Objects/typeobject.c
--- a/Objects/typeobject.c Thu Jan 07 10:58:20 2016 -0800
+++ b/Objects/typeobject.c Mon Jan 18 11:25:33 2016 -0800
@@ -5815,6 +5815,13 @@
_Py_IDENTIFIER(__contains__);
func = lookup_maybe(self, &PyId___contains__);
+ if (func == Py_None) {
+ Py_DECREF(func);
+ PyErr_Format(PyExc_TypeError,
+ "'%.200s' object is not a container",
+ Py_TYPE(self)->tp_name);
+ return -1;
+ }
if (func != NULL) {
args = PyTuple_Pack(1, value);
if (args == NULL)
@@ -6200,6 +6207,13 @@
_Py_IDENTIFIER(__iter__);
func = lookup_method(self, &PyId___iter__);
+ if (func == Py_None) {
+ Py_DECREF(func);
+ PyErr_Format(PyExc_TypeError,
+ "'%.200s' object is not iterable",
+ Py_TYPE(self)->tp_name);
+ return NULL;
+ }
if (func != NULL) {
PyObject *args;
args = res = PyTuple_New(0);