diff -r 003f1f60a09c Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst Sun Dec 27 12:24:06 2015 -0800 +++ b/Doc/reference/datamodel.rst Mon Dec 28 12:32:02 2015 -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 diff -r 003f1f60a09c Lib/_collections_abc.py --- a/Lib/_collections_abc.py Sun Dec 27 12:24:06 2015 -0800 +++ b/Lib/_collections_abc.py Mon Dec 28 12:32:02 2015 -0800 @@ -200,8 +200,11 @@ @classmethod def __subclasshook__(cls, C): if cls is Iterable: - if any("__iter__" in B.__dict__ for B in C.__mro__): - return True + for B in C.__mro__: + if "__iter__" in B.__dict__: + if B.__dict__["__iter__"]: + return True + break return NotImplemented @@ -621,6 +624,8 @@ return NotImplemented return dict(self.items()) == dict(other.items()) + __reversed__ = None + Mapping.register(mappingproxy) diff -r 003f1f60a09c Lib/test/test_collections.py --- a/Lib/test/test_collections.py Sun Dec 27 12:24:06 2015 -0800 +++ b/Lib/test/test_collections.py Mon Dec 28 12:32:02 2015 -0800 @@ -688,6 +688,15 @@ self.assertFalse(issubclass(str, I)) self.validate_abstract_methods(Iterable, '__iter__') self.validate_isinstance(Iterable, '__iter__') + # Check None blocking + class J: + def __iter__(self): return iter([]) + class K(J): + __iter__ = None + self.assertTrue(issubclass(J, Iterable)) + self.assertTrue(isinstance(J(), Iterable)) + self.assertFalse(issubclass(K, Iterable)) + self.assertFalse(isinstance(K(), Iterable)) def test_Iterator(self): non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()] @@ -1219,6 +1228,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 003f1f60a09c Lib/test/test_enumerate.py --- a/Lib/test/test_enumerate.py Sun Dec 27 12:24:06 2015 -0800 +++ b/Lib/test/test_enumerate.py Mon Dec 28 12:32:02 2015 -0800 @@ -232,6 +232,13 @@ ngi = NoGetItem() self.assertRaises(TypeError, reversed, ngi) + class Blocked(object): + def __getitem__(self): 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 003f1f60a09c Lib/test/test_iter.py --- a/Lib/test/test_iter.py Sun Dec 27 12:24:06 2015 -0800 +++ b/Lib/test/test_iter.py Mon Dec 28 12:32:02 2015 -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 003f1f60a09c Objects/enumobject.c --- a/Objects/enumobject.c Sun Dec 27 12:24:06 2015 -0800 +++ b/Objects/enumobject.c Mon Dec 28 12:32:02 2015 -0800 @@ -250,6 +250,12 @@ return NULL; reversed_meth = _PyObject_LookupSpecial(seq, &PyId___reversed__); + if (reversed_meth == Py_None) { + Py_DECREF(reversed_meth); + PyErr_SetString(PyExc_TypeError, + "argument to reversed() must be a sequence"); + return NULL; + } if (reversed_meth != NULL) { PyObject *res = PyObject_CallFunctionObjArgs(reversed_meth, NULL); Py_DECREF(reversed_meth); diff -r 003f1f60a09c Objects/typeobject.c --- a/Objects/typeobject.c Sun Dec 27 12:24:06 2015 -0800 +++ b/Objects/typeobject.c Mon Dec 28 12:32:02 2015 -0800 @@ -6215,6 +6215,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);