Ionel Cristian Mărieș added the comment: On Sun, Apr 19, 2015 at 10:29 PM, Steven D'Aprano <rep...@bugs.python.org> wrote:
> This bug report seems to be completely based on a false premise. In the > very first message of this issue, Ionel says: > > "it return True even if __call__ is actually an descriptor that raise > AttributeError (clearly not callable at all)." > > but that is wrong. It *is* callable, and callable() is correct to return > True. If you look at the stack trace, the __call__ method > (function/property, whatever you want to call it) is called, and it raises > an exception. That is no different from any other method or function that > raises an exception. > > It is wrong to think that raising AttributeError *inside* __call__ makes > the object non-callable. > > Ionel, I raised these issues on Python-list here: > > https://mail.python.org/pipermail/python-ideas/2015-April/033078.html > > but you haven't responded to them. I was hoping my other replies had addressed those issues. Note that the presence of __call__ on the callstack is merely an artefact of how @property works, and it's not actually the __call__ method (it's just something that property.__get__ calls). Here's an example that hopefully illustrates the issue more clearly: >>> class CallDescriptor: ... def __get__(self, inst, owner): ... target = inst._get_target() ... if callable(target): ... return target ... else: ... raise AttributeError('not callable') ... >>> class LazyProxy: ... __call__ = CallDescriptor() ... def __init__(self, get_target): ... self._get_target = get_target ... >>> def create_stuff(): ... # heavy computation here ... print("Doing heavey computation!!!!") ... return 1, 2, 3 ... >>> proxy = LazyProxy(create_stuff) >>> callable(proxy) ################### this should be false! True >>> hasattr(proxy, '__call__') Doing heavey computation!!!! False >>> >>> def create_callable_stuff(): ... # heavy computation here ... print("Doing heavey computation!!!!") ... def foobar(): ... pass ... return foobar ... >>> proxy = LazyProxy(create_callable_stuff) >>> callable(proxy) True >>> hasattr(proxy, '__call__') Doing heavey computation!!!! True Now it appears there's a second issue, slightly related - if you actually call the proxy object AttributeError is raised (instead of the TypeError): >>> proxy = LazyProxy(create_stuff) >>> callable(proxy) True >>> hasattr(proxy, '__call__') Doing heavey computation!!!! False >>> proxy() Doing heavey computation!!!! Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in __get__ AttributeError: not callable >>> >>> target = create_stuff() Doing heavey computation!!!! >>> target() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object is not callable Contrast that to how iter works - if the descriptor raise AttributeError then iter raises TypeError (as expected): >>> class IterDescriptor: ... def __get__(self, inst, owner): ... target = inst._get_target() ... if hasattr(type(target), '__iter__') and hasattr(target, '__iter__'): ... return target.__iter__ ... else: ... raise AttributeError('not iterable') ... >>> class LazyProxy: ... __iter__ = IterDescriptor() ... def __init__(self, get_target): ... self._get_target = get_target ... >>> def create_iterable_stuff(): ... # heavy computation here ... print("Doing heavey computation!!!!") ... return 1, 2, 3 ... >>> proxy = LazyProxy(create_iterable_stuff) >>> iter(proxy) Doing heavey computation!!!! <tuple_iterator object at 0x0000000002B7C908> >>> >>> def create_noniterable_stuff(): ... # heavy computation here ... print("Doing heavey computation!!!!") ... def foobar(): ... pass ... return foobar ... >>> proxy = LazyProxy(create_noniterable_stuff) >>> iter(proxy) Doing heavey computation!!!! Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'LazyProxy' object is not iterable >>> >>> proxy.__iter__ Doing heavey computation!!!! Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in __get__ AttributeError: not iterable So this is why I'm bringing this up. If `iter` wouldn't handle it like that then I'd think that maybe this is the intended behaviour. I hope the blatant inconsistency is more clear now , and you'll understand that this bug report is not just some flagrant misunderstanding of how __call__ works. To sum this up, the root of this issue is that `callable` doesn't do all the checks that are done right before actually performing the call (like the descriptor handling). It's like calling your doctor for an appointment where the secretary schedules you, but forgets to check if the doctor is in vacation or not. Thanks, -- Ionel Cristian Mărieș, http://blog.ionelmc.ro ---------- _______________________________________ Python tracker <rep...@bugs.python.org> <http://bugs.python.org/issue23990> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com