[Tutor] Calling instance method in IDLE magically calls __len__?

Peter Otten __peter__ at web.de
Fri Sep 18 12:19:03 CEST 2015


Dino Bektešević wrote:

> Hello,
> 
> For full disclosure, I'm using Python2.7 on Ubuntu 14.04. MWE bellow and
> at https://bpaste.net/show/3d38c96ec938 (until 2015-09-25 06:29:54, in the
> case spaces get messed up).
> 
> class Errors:
>     def __init__(self):
>         pass
>     def toFile(self):
>         pass
>     def __len__(self):
>         print "len is called"
>         return 0
> 
> Which is just fine if I call it over terminal, however calling it in IDLE:
> 
>>>> e = Errors()
>>>> len(e)
> len is called
> 0
> 
> as expected, but when try to call the method toFile, "len is called" gets
> printed as soon as I put parenthesis "(" behind the toFile.
> 
>>>> len is called
> e.toFile(
> 
> Now I recognize that I shouldn't use __len__ to print stuff, I should use
> __string__ or at least __repr__, but I found it weird that __len__ would
> get called in that situation. So out of a stupid mistake an interesting
> question!
> 
> Why does the "len is called" get printed to IDLE when you try to call
> toFile? How does the interpreter handle this? Any kind of clarification
> would be greatly appreciated.

This is a bug in Idle. In idlelib.CallTips.get_arg_text() there is a check

if ob.im_self:
    ...

to decide if the first argument (i. e. self) to the function should be 
ignored. This implicitly calls __len__() if im_self (in your case the Errors 
instance associated with the bound toFile method) has a __len__ and no 
__nonzero__ method:

>>> class Errors:
...     def __init__(self):
...         pass
...     def toFile(self):
...         pass
...     def __len__(self):
...         print "len is called"
...         return 0
... 
>>> e = Errors()
>>> if e: pass
... 
len is called
>>> if e.toFile.im_self:
...     pass
... 
len is called

Normally this is intended as it allows you to write the idiomatic

if some_list:
    print "the list is not empty"

instead of the long-winded

if len(some_list) > 0:
    ...

Therefore I recommend that you write test methods like __len__() without any 
side effects. 

However, the problem with Idle is that it decides that the method is unbound 
and therefore includes self into the list of arguments to be supplied by the 
user:

>>> import idlelib.CallTips
>>> idlelib.CallTips.get_arg_text(e.toFile)
len is called
'(self)'

For comparison the result when len(e) != 0:

>>> class Foo: # class with user-supplied len
...     def __init__(self, len): self._len = len
...     def __len__(self):
...             print "len =", self._len
...             return self._len
...     def bar(self, one, two): pass
... 
>>> idlelib.CallTips.get_arg_text(Foo(0).bar)
len = 0
'(self, one, two)'
>>> idlelib.CallTips.get_arg_text(Foo(1).bar)
len = 1
'(one, two)'
>>> idlelib.CallTips.get_arg_text(Foo(42).bar)
len = 42
'(one, two)'


This can be fixed by changing the check to the stricter

if ob.im_self is not None:
    ...

As this has already been done (see http://bugs.python.org/issue21654) you 
don't need to report the bug.



More information about the Tutor mailing list