[Python-ideas] Fix that broken callable builtin

Steven D'Aprano steve at pearwood.info
Sat Apr 18 19:38:57 CEST 2015


On Sat, Apr 18, 2015 at 02:41:29PM +0300, Ionel Cristian Mărieș wrote:
> On Sat, Apr 18, 2015 at 2:23 PM, Steven D'Aprano <steve at pearwood.info>
> wrote:
> 
> > Are you sure this doesn't already work? It works for me in Python 3.3:
> 
> 
> It doesn't really work because of the incomplete checks done in
> `callable_builtin`. ​This is what I've tried:
> 
> >>> class DynamicCallable:
> > ...     is_callable = True
> > ...
> > ...     def __init__(self, target):
> > ...         self.target = target
> > ...
> > ...     @property
> > ...     def __call__(self):
> > ...         if self.is_callable:
> > ...             return self.target
> > ...         else:
> > ...             raise AttributeError("Not really ...")
> > ...
> > >>> dc = DynamicCallable(print)
> > >>> dc(1, 2, 3)
> > 1 2 3
> > >>> callable(dc)
> > True
> > >>> dc.is_callable = False
> > >>> callable(dc)
> > True ###### This should be False :(


Why do you think that it should be false? If you actually call dc, the 
__call__ method/function/property (whatever you want to name it) gets 
called, and it raises an exception just like you programmed it to. The 
traceback clearly shows __call__ in the stack.

hasattr(type(dc), '__call__') returns true, therefore dc is callable. I 
think that the fact that the __call__ method ends up raising an 
AttributeError is irrelevant -- lots of callables raise AttributeError:

def func(): return None.spam


However, I think *this* is a bug in callable:

py> class Callable:
...     def __getattribute__(self, name):
...             if name == '__call__':
...                     raise AttributeError  # Oh the lies we tell.
...             return super().__getattribute__(name)
...     def __call__(self):
...             return 23
...
py> x = Callable()
py> callable(x)
False
py> x()
23

Clearly x is callable, since I just called it and it returned a value, 
but callable() thinks it is not. Here is another example:

py> class X: pass
...
py> x = X()
py> x.__call__ = 42
py> callable(x)
True
py> x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'X' object is not callable


I think callable() is badly broken. (Or at least *was* broken in 3.3 
-- I don't have 3.4 handy to try it.)


-- 
Steve


More information about the Python-ideas mailing list