# HG changeset patch # User Andrew Barnert # Date 1452031303 28800 # Tue Jan 05 14:01:43 2016 -0800 # Branch issue25864 # Node ID 48c57af8da0c4bf7de7961cd1819f81515be9e8b # Parent 164b564e3c1ab51fbe1c2c29ca019de7fbf6ffc4 Issue #25958: Make `__spam__ = None` consistent Setting a special method `__spam__ = None` in a class has always not only blocked the corresponding feature with a `TypeError`. For example, if you set `__iter__ = None`, calling `iter()` on an instance of your class will raise a `TypeError`, without falling back to the old-style sequence protocol (`__getitem__`). However, there were a few problems: 1. Only a few of the ABCs in `collections.abc` with subclass hooks understood `None`. For example, `__iter__ = None` makes your class a virtual instance of `Iterable`, when you intended exactly the reverse. 2. In most cases, the `TypeError` has a useless message, like `'NoneType' object is not callable`, instead of something nicer like `'MyClass' object is not iterable`. 3. There was nothing in the documentation explaining this behavior. 4. A few non-internal "protocols" don't follow the same rule. In particular, `pickle`/`deepcopy` use `None` rather than `NotImplemented` to trigger fallback, meaning they can't use `None` to block fallback. This patch fixes the first three problems, and also includes two other related changes: * All `collections.abc.*.__subclasshook__` methods now use the same code to search for method implementations, and that code treats `None` as blocking. * The `__iter__`, `__reversed__`, and `__contains__` methods now use a custom `TypeError` message for `None`, as these (along with `__hash__`, which already had one) are the ones most likely to come up in realistic code. * The Data Model section of the reference documents `None` blocking (with footnotes for uncommon but surprising cases, like `__rspam__`). * Some new unit tests are added to the test suite. * `collections.abc.Mapping.__reversed__ = None`. (#25864) * `collections.abc.Reversible` added. (#25987) diff -r 164b564e3c1a -r 48c57af8da0c Doc/library/collections.abc.rst --- a/Doc/library/collections.abc.rst Sun Jan 03 17:58:24 2016 -0800 +++ b/Doc/library/collections.abc.rst Tue Jan 05 14:01:43 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 164b564e3c1a -r 48c57af8da0c Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst Sun Jan 03 17:58:24 2016 -0800 +++ b/Doc/reference/datamodel.rst Tue Jan 05 14:01:43 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 assign 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 164b564e3c1a -r 48c57af8da0c Lib/_collections_abc.py --- a/Lib/_collections_abc.py Sun Jan 03 17:58:24 2016 -0800 +++ b/Lib/_collections_abc.py Tue Jan 05 14:01:43 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 164b564e3c1a -r 48c57af8da0c Lib/test/test_augassign.py --- a/Lib/test/test_augassign.py Sun Jan 03 17:58:24 2016 -0800 +++ b/Lib/test/test_augassign.py Tue Jan 05 14:01:43 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 164b564e3c1a -r 48c57af8da0c Lib/test/test_binop.py --- a/Lib/test/test_binop.py Sun Jan 03 17:58:24 2016 -0800 +++ b/Lib/test/test_binop.py Tue Jan 05 14:01:43 2016 -0800 @@ -388,6 +388,34 @@ 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 FallbackBlockingTests(unittest.TestCase): + def test_fallback_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) + if __name__ == "__main__": unittest.main() diff -r 164b564e3c1a -r 48c57af8da0c Lib/test/test_collections.py --- a/Lib/test/test_collections.py Sun Jan 03 17:58:24 2016 -0800 +++ b/Lib/test/test_collections.py Tue Jan 05 14:01:43 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,64 @@ 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_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 None blocking + class J: + def __iter__(self): return iter([]) + def __reversed__(self): return reversed([]) + class K(J): + __iter__ = None + class L(J): + __reversed__ = None + self.assertTrue(issubclass(J, Reversible)) + self.assertTrue(isinstance(J(), Reversible)) + self.assertFalse(issubclass(K, Reversible)) + self.assertFalse(isinstance(K(), Reversible)) + self.assertFalse(issubclass(L, Reversible)) + self.assertFalse(isinstance(L(), Reversible)) def test_Iterator(self): non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()] @@ -1219,6 +1277,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 164b564e3c1a -r 48c57af8da0c Lib/test/test_contains.py --- a/Lib/test/test_contains.py Sun Jan 03 17:58:24 2016 -0800 +++ b/Lib/test/test_contains.py Tue Jan 05 14:01:43 2016 -0800 @@ -84,6 +84,27 @@ 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, and inherits from + a perfectly good container, but __contains__ = None blocks + both of these from working.""" + 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 164b564e3c1a -r 48c57af8da0c Lib/test/test_enumerate.py --- a/Lib/test/test_enumerate.py Sun Jan 03 17:58:24 2016 -0800 +++ b/Lib/test/test_enumerate.py Tue Jan 05 14:01:43 2016 -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 164b564e3c1a -r 48c57af8da0c Lib/test/test_functools.py --- a/Lib/test/test_functools.py Sun Jan 03 17:58:24 2016 -0800 +++ b/Lib/test/test_functools.py Tue Jan 05 14:01:43 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 164b564e3c1a -r 48c57af8da0c Lib/test/test_iter.py --- a/Lib/test/test_iter.py Sun Jan 03 17:58:24 2016 -0800 +++ b/Lib/test/test_iter.py Tue Jan 05 14:01:43 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 164b564e3c1a -r 48c57af8da0c Objects/enumobject.c --- a/Objects/enumobject.c Sun Jan 03 17:58:24 2016 -0800 +++ b/Objects/enumobject.c Tue Jan 05 14:01:43 2016 -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 164b564e3c1a -r 48c57af8da0c Objects/typeobject.c --- a/Objects/typeobject.c Sun Jan 03 17:58:24 2016 -0800 +++ b/Objects/typeobject.c Tue Jan 05 14:01:43 2016 -0800 @@ -5819,6 +5819,13 @@ _Py_IDENTIFIER(__contains__); func = lookup_maybe(self, &PyId___contains__); + if (func == Py_None) { + Py_DECREF(func); + PyErr_Format(PyExc_TypeError, + "argument of type '%.200s' is not a container", + Py_TYPE(self)->tp_name); + return -1; + } if (func != NULL) { args = PyTuple_Pack(1, value); if (args == NULL) @@ -6204,6 +6211,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); # HG changeset patch # User Andrew Barnert # Date 1452044854 28800 # Tue Jan 05 17:47:34 2016 -0800 # Branch issue25864 # Node ID 5188e79c9d98abbb678a41365925dc51a4d0bff6 # Parent 48c57af8da0c4bf7de7961cd1819f81515be9e8b None-blocking tests, minor fixes In addition to a few small fixes from the reviews of the second patch, Serhiy pointed out that, if we're going to document categorically that None blocks fallbacks, we need to actually test for that. So, I went through all of the special methods defined in the data model to see what fallbacks are defined, then skimmed the code to try to find any that weren't documented. I think I have the complete list. Already tested: * Normal inheritance (for __hash__) test_unicode.py: * __str__ -> __repr__ * __format__ -> str (indirectly via object.__format__) test_bytes.py: * __bytes__ -> iter * __bytes__ -> being a bytes instance (i.e., inheriting bytes) * __bytes__ -> implementing the buffer interface (e.g., inheriting bytearray) test_binop.py: * __spam__ -> __rspam__ (tested with __eq__) * __ne__ -> __eq__ test_bool.py * __bool__ -> __len__ * __bool__ -> always True Not Tested * __*attr*__ (unless someone has some useful tests to suggest?) test_collections.py * __iter__ -> __getitem__ * __reversed__ -> __getitem__ and __len__ test_contains.py * __contains__ -> __iter__ (-> __getitem__) test_augassign.py * __ispam__ -> __spam__ (tested with __iadd__) I'm still only testing each piece of code once, rather than once for every class that's affected by it (e.g., no __isub__ because it's the same code as __iadd__). Also, notice that I'm not testing any of the *attr* methods, because I'm not sure what the tests should be there. If we really need something, I can sit down, think through all the edge cases, verify that I'm right, and write tests for them. But I don't think the benefit is worth the risk of me getting it wrong. (Is there ever a good reason to, e.g., set __getattr__ = None? "Type that can't be attributed" isn't really a concept in Python's type system.) Finally, some of the specific tests Serhiy suggested adding don't seem to actually test anything, like "a classes without __iadd__, but with __add__ but to None"--what kind of implementation defect could that catch? But the rest all seemed useful, so I've added them. diff -r 48c57af8da0c -r 5188e79c9d98 Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst Tue Jan 05 14:01:43 2016 -0800 +++ b/Doc/reference/datamodel.rst Tue Jan 05 17:47:34 2016 -0800 @@ -2435,7 +2435,7 @@ 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 assign the method to + 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. diff -r 48c57af8da0c -r 5188e79c9d98 Lib/test/test_binop.py --- a/Lib/test/test_binop.py Tue Jan 05 14:01:43 2016 -0800 +++ b/Lib/test/test_binop.py Tue Jan 05 17:47:34 2016 -0800 @@ -404,8 +404,18 @@ """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): - def test_fallback_blocking(self): + def test_fallback_rmethod_blocking(self): e, f, s, x = E(), F(), S(), X() self.assertEqual(e, e) self.assertEqual(e, f) @@ -416,6 +426,13 @@ # 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 48c57af8da0c -r 5188e79c9d98 Lib/test/test_bool.py --- a/Lib/test/test_bool.py Tue Jan 05 14:01:43 2016 -0800 +++ b/Lib/test/test_bool.py Tue Jan 05 17:47:34 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 48c57af8da0c -r 5188e79c9d98 Lib/test/test_bytes.py --- a/Lib/test/test_bytes.py Tue Jan 05 14:01:43 2016 -0800 +++ b/Lib/test/test_bytes.py Tue Jan 05 17:47:34 2016 -0800 @@ -822,6 +822,34 @@ PyBytes_FromFormat, b'%c', c_int(256)) + def test_bytes_blocking(self): + class Seq: + def __getitem__(self, i): + if 0 <= i < 4: + return i + raise IndexError + class SeqBlocked(Seq): + __bytes__ = None + s, sb = Seq(), SeqBlocked() + self.assertEqual(bytes(s), b'\x00\x01\x02\x03') + self.assertRaises(TypeError, bytes, sb) + + class BytesSubclass(bytes): + pass + class BytesSubclassBlocked(BytesSubclass): + __bytes__ = None + bs, bsb = BytesSubclass(b'ab'), BytesSubclassBlocked(b'ab') + self.assertEqual(bytes(bs), b'ab') + self.assertRaises(TypeError, bytes, bsb) + + class BytesLikeSubclass(bytearray): + pass + class BytesLikeSubclassBlocked(BytesLikeSubclass): + __bytes__ = None + bls, blsb = BytesLikeSubclass(b'ab'), BytesLikeSubclassBlocked(b'ab') + self.assertEqual(bytes(bls), b'ab') + self.assertRaises(TypeError, bytes, blsb) + class ByteArrayTest(BaseBytesTest, unittest.TestCase): type2test = bytearray diff -r 48c57af8da0c -r 5188e79c9d98 Lib/test/test_collections.py --- a/Lib/test/test_collections.py Tue Jan 05 14:01:43 2016 -0800 +++ b/Lib/test/test_collections.py Tue Jan 05 17:47:34 2016 -0800 @@ -689,14 +689,14 @@ self.validate_abstract_methods(Iterable, '__iter__') self.validate_isinstance(Iterable, '__iter__') # Check None blocking - class J: + class It: def __iter__(self): return iter([]) - class K(J): + class ItBlocked(It): __iter__ = None - self.assertTrue(issubclass(J, Iterable)) - self.assertTrue(isinstance(J(), Iterable)) - self.assertFalse(issubclass(K, Iterable)) - self.assertFalse(isinstance(K(), Iterable)) + 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 @@ -732,20 +732,29 @@ 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 J: + class Rev: def __iter__(self): return iter([]) def __reversed__(self): return reversed([]) - class K(J): + class RevItBlocked(Rev): __iter__ = None - class L(J): + class RevRevBlocked(Rev): __reversed__ = None - self.assertTrue(issubclass(J, Reversible)) - self.assertTrue(isinstance(J(), Reversible)) - self.assertFalse(issubclass(K, Reversible)) - self.assertFalse(isinstance(K(), Reversible)) - self.assertFalse(issubclass(L, Reversible)) - self.assertFalse(isinstance(L(), Reversible)) + 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()] diff -r 48c57af8da0c -r 5188e79c9d98 Lib/test/test_unicode.py --- a/Lib/test/test_unicode.py Tue Jan 05 14:01:43 2016 -0800 +++ b/Lib/test/test_unicode.py Tue Jan 05 17:47:34 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 48c57af8da0c -r 5188e79c9d98 Objects/enumobject.c --- a/Objects/enumobject.c Tue Jan 05 14:01:43 2016 -0800 +++ b/Objects/enumobject.c Tue Jan 05 17:47:34 2016 -0800 @@ -252,8 +252,9 @@ 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"); + PyErr_Format(PyExc_TypeError, + "'%.200s' object is not reversible", + Py_TYPE(seq)->tp_name); return NULL; } if (reversed_meth != NULL) { @@ -265,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 48c57af8da0c -r 5188e79c9d98 Objects/typeobject.c --- a/Objects/typeobject.c Tue Jan 05 14:01:43 2016 -0800 +++ b/Objects/typeobject.c Tue Jan 05 17:47:34 2016 -0800 @@ -5822,7 +5822,7 @@ if (func == Py_None) { Py_DECREF(func); PyErr_Format(PyExc_TypeError, - "argument of type '%.200s' is not a container", + "'%.200s' object is not a container", Py_TYPE(self)->tp_name); return -1; }