Alternative to multi-line lambdas: Assign-anywhere def statements

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Jan 25 21:46:46 EST 2015


Yawar Amin wrote:

> Hi Steven,
> 
> On Saturday, January 24, 2015 at 11:27:33 PM UTC-5, Steven D'Aprano
> wrote:
>> [...]
>> > Doesn't the traceback tell us exactly where the lambda was called
>> > from?
>> 
>> Yes (assuming the source code is available, which it may not be), but
> 
> If the source code is not available, then you're equally unable to debug
> a lambda function and a named function.

If source code to a library is unavailable, debugging is significantly more
difficult, but it may still be possible. At least in the sense that you can
identify the nature of the bug and develop a work-around in your own code.
Obviously there needs to be *some* source code for you to edit, otherwise
there's nothing for you to edit :-)

Even in the absence of source code, you may still be able to report a bug to
the application developer and show the full traceback. Even without source,
tracebacks show the names of functions, and lambdas all share the same
name:

[steve at ando ~]$ cat demo.py
def fail(x):
        f = lambda a: 1/a
        g = lambda a: f(a-1)
        return g(x)

print fail(1)
[steve at ando ~]$ python -m compileall demo.py
Compiling demo.py ...
[steve at ando ~]$ python demo.pyc
Traceback (most recent call last):
  File "demo.py", line 6, in <module>
    print fail(1)
  File "demo.py", line 4, in fail
    return g(x)
  File "demo.py", line 3, in <lambda>
    g = lambda a: f(a-1)
  File "demo.py", line 2, in <lambda>
    f = lambda a: 1/a
ZeroDivisionError: integer division or modulo by zero
[steve at ando ~]$ mv demo.py demo-save.py  # hide the source
[steve at ando ~]$ python demo.pyc
Traceback (most recent call last):
  File "demo.py", line 6, in <module>
  File "demo.py", line 4, in fail
  File "demo.py", line 3, in <lambda>
  File "demo.py", line 2, in <lambda>
ZeroDivisionError: integer division or modulo by zero


In this toy example, it is still easy to debug even without names. It's a
three line function, how hard could it be? :-) But that isn't always going
to be the case.



[...]
> True, Python's current traceback reporting doesn't tell us the exact
> column number on which the exception occurred. So for example with this:
[...]
> So the problem is there are two lambdas in line 3, so you need to
> examine them both to figure out which one caused the exception. The
> simple solution to this is to just put the lambdas on different lines:
> 
>     print(
>       (lambda: 1)(),
>       (lambda: 1 / 0)()
>     )
> 
> Now you get a blindingly obvious traceback:


It's blindingly obvious because it is trivial. You're still thinking about
the easy cases, not the hard cases:

py> def make_funcs():
...     closures = []
...     for i in range(5, -5, -1):
...         closures.append(lambda x, i=i: x/i)
...     return closures
...
py> results = [f(5) for f in make_funcs()]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
  File "<stdin>", line 4, in <lambda>
ZeroDivisionError: division by zero


That's still not a hard case, because all the closures have a very similar
form and it's easy to spot that one of them divides by zero. But it
demonstrates one way that line numbers alone don't help.

But do understand I'm not saying that lambdas cannot be debugged. I'm saying
that *function names are a useful debugging tool*, so if you take away the
function name that makes debugging just that little bit harder. Not
necessarily in every single case, and not necessarily hard to the point
that you spend days trying to track down the error, but hard enough in
enough circumstances that using lambda for arbitrarily complex functions
would be a bad idea. (The same applies to comprehensions: they should be
kept simple.) That shouldn't be controversial.



-- 
Steven




More information about the Python-list mailing list