Andrew Barnert added the comment:

> I propose to solve the narrow problem by indeed supporting the setting of 
> certain special methods to None similar to __hash__. This should be limited 
> to those methods for which this adds value, e.g. where the complete absence 
> of the method causes a fall-back to a different API (e.g. __getitem__+__len__ 
> in the case of __reversed__) -- the None value should just block the fallback

I believe that currently, except for the minor problem in #25864, you can set 
any special method to None and it _will_ block any fallback (and also block 
inheritance, of course, the reason it's used with __hash__):

 * __iter__ and __reversed__ directly fall back to another method.

 * __contains__ falls back to iterating, which will indirectly fall back on 
__iter__ (or on __getitem__, of course).

 * __iadd__ and friends fall back on __add__ and friends.

 * __add__ and friends fall back on __radd__ (on the other operand, but 
typically it will be of the same type).

 * __lt__ and friends are similar to __add__.

 * __getattr__ and friends are a pile of special cases.

 * I think everything else doesn't have a fallback, and would just raise a 
TypeError anyway--although setting to None is still useful as a way to block 
inheritance, as used for __hash__.

In all cases, if the primary method exists but is None, the implementation will 
try to call it, and let the TypeError from NoneType.__call__ escape, which 
blocks the fallback. (And, of course, it blocks the MRO search for an inherited 
method.)

Of course the message is the useless and cryptic "'NoneType' object is not 
callable" rather than the nice one you get from hash. But technically, it's all 
already working as desired.

We probably need to document that setting special methods to None blocks any 
fallback implementation (both for people who write such classes, and to ensure 
that other Pythons do the same thing as CPython). 

Practically, I think we need to improve the error message for the ones that are 
likely to come up, but probably not for all of them. I think this means either 
just reversed, or reversed+iter, or maybe reversed+iter+in. (Plus hash, of 
course, but that code is already there.)

Also, I suppose we should have tests for this behavior.

> This will require some changes in the ABCs that test for the presence of a 
> given method (Iterable/__iter__, Container/__contains__, 
> Reversible/__reversed__ -- the latter ABC should be added) and in 
> implementations such as reversed(), iter() and 'in'. Maybe we should just do 
> this for every ABC in collections.abc (plus Reversible) that has a 
> __subclasshook__ that tests for the presence of the special method.

Now that I think about it, I'm +0.5 on changing all of them. Imagine, as a 
reader, trying to guess why some do the check and some don't.

Also, I think they should test for None instead of falsiness like Hashable 
does. (Hopefully nobody ever writes something that's falsey but callable, but 
why tempt fate?)

---

I'll write a patch that expands the #25864 patch:

 * collections.abc.Reversible added (as in #25987).

 * All collections.abc.* subclass hooks check for None.

 * Mapping.__reversed__ = None.

 * One paragraph added to datamodel docs.

 * iter, reversed, and 'in' operator all raise a custom TypeError when the 
special method is None the same way as hash (but everything else will continue 
to implicitly raise the ugly TypeError).

 * Unit tests for None blocking fallback on all special methods I can think of 
(treating all the __ispam__, add the __rspam__, all inheritance except for 
__hash__, etc. as a single method each, rather than copying-pasting a 
half-dozen times).

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue25958>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to