On Sat, Apr 18, 2015 at 1:26 PM, Steven D'Aprano <steve@pearwood.info> wrote:

With new-style classes (but not classic classes), all dunder methods are
only accessed through the class, not the instance. Hence
type(obj).__call__ is correct and obj.__call__ is incorrect.

​Yes, this is correct. But unfortunately is also very commonly misunderstood, because even special methods will work through the descriptor protocol. The __new__ method is the only special case here (the infamous "special cased static method").

py> x.__call__ = lambda self: 23
 
The issue never was about patching __call__ on an instance, it's about making  `callable` respect how the method is actually looked up fully (lookup on type + descriptor protocol). What `callable` is missing now is an additional check that will run the descriptor protocol.

​Could this be changed? That deserves a new thread, at least, and
possibly a PEP, but briefly:​

​So what exactly are you proposing, making a PEP that documents the fact that functions are descriptors and the descriptor protocol is used even for special methods?

To give more context here, this is valid and it works right now:

​>>> class NonIter:
...     pass
...
>>> iter(NonIter())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NonIter' object is not iterable
>>>
>>> class DynamicNonIter:
...     has_iter = False
...
...     @property
...     def __iter__(self):
...         if self.has_iter:
...             from functools import partial
...             return partial(iter, [1, 2, 3])
...         else:
...             raise AttributeError("Not really ...")
...
>>> dni = DynamicNonIter()
>>> iter(dni)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'DynamicNonIter' object is not iterable
>>> dni.has_iter = True
>>> iter(dni)
<list_iterator object at 0x000000000362FF60>

​I don't see why `callable` shouldn't work the same.​


Thanks,
-- Ionel
Cristian Mărieș, http://blog.ionelmc.ro