Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #84538 > unrolled thread

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

Started byEthan Furman <ethan@stoneleaf.us>
First post2015-01-24 17:58 -0800
Last post2015-02-12 09:17 -0700
Articles 12 — 8 participants

Back to article view | Back to comp.lang.python

This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by below is the oldest one visible, not the original post.


Contents

  Re: Alternative to multi-line lambdas: Assign-anywhere def statements Ethan Furman <ethan@stoneleaf.us> - 2015-01-24 17:58 -0800
    Re: Alternative to multi-line lambdas: Assign-anywhere def statements Yawar Amin <yawar.amin@gmail.com> - 2015-01-24 18:30 -0800
      Re: Alternative to multi-line lambdas: Assign-anywhere def statements Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2015-01-25 15:27 +1100
        Re: Alternative to multi-line lambdas: Assign-anywhere def statements Yawar Amin <yawar.amin@gmail.com> - 2015-01-25 17:26 -0800
          Re: Alternative to multi-line lambdas: Assign-anywhere def statements Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2015-01-26 13:46 +1100
    Re: Alternative to multi-line lambdas: Assign-anywhere def statements albert@spenarnc.xs4all.nl (Albert van der Horst) - 2015-02-11 13:04 +0000
      Re: Alternative to multi-line lambdas: Assign-anywhere def statements Clarence <clarence1126@gmail.com> - 2015-02-11 06:28 -0800
      Re: Alternative to multi-line lambdas: Assign-anywhere def statements Ethan Furman <ethan@stoneleaf.us> - 2015-02-11 08:52 -0800
      Re: Alternative to multi-line lambdas: Assign-anywhere def statements Terry Reedy <tjreedy@udel.edu> - 2015-02-11 21:06 -0500
      Re: Alternative to multi-line lambdas: Assign-anywhere def statements Ian Kelly <ian.g.kelly@gmail.com> - 2015-02-11 19:57 -0700
      Re: Alternative to multi-line lambdas: Assign-anywhere def statements Chris Angelico <rosuav@gmail.com> - 2015-02-12 14:56 +1100
      Re: Alternative to multi-line lambdas: Assign-anywhere def statements Ian Kelly <ian.g.kelly@gmail.com> - 2015-02-12 09:17 -0700

#84538 — Re: Alternative to multi-line lambdas: Assign-anywhere def statements

FromEthan Furman <ethan@stoneleaf.us>
Date2015-01-24 17:58 -0800
SubjectRe: Alternative to multi-line lambdas: Assign-anywhere def statements
Message-ID<mailman.18121.1422151185.18130.python-list@python.org>

[Multipart message — attachments visible in raw view] — view raw

On 01/24/2015 11:55 AM, Chris Angelico wrote:
> On Sun, Jan 25, 2015 at 5:56 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
>> If the non-generic is what you're concerned about:
>>
>> # not tested
>> dispatch_table_a = {}
>> dispatch_table_b = {}
>> dispatch_table_c = {}
>>
>> class dispatch:
>>   def __init__(self, dispatch_table):
>>     self.dispatch = dispatch_table
>>   def __call__(self, func):
>>     self.dispatch[func.__name__] = func
>>     return func
>>
>> @dispatch(dispatch_table_a)
>> def foo(...):
>>    pass
> 
> That's still only able to assign to a key of a dictionary, using the
> function name. 

This is a Good Thing.  The def statement populates a few items, __name__ being one of them.  One of the reasons lambda
is not encouraged is because its name is always '<lambda>', which just ain't helpful when the smelly becomes air borne!  ;)

--
~Ethan~

[toc] | [next] | [standalone]


#84543

FromYawar Amin <yawar.amin@gmail.com>
Date2015-01-24 18:30 -0800
Message-ID<4741da2c-8fc5-4c06-bc3a-cfadca890abf@googlegroups.com>
In reply to#84538
Hi Ethan,

On Saturday, January 24, 2015 at 9:00:12 PM UTC-5, Ethan Furman wrote:
> [...]
> __name__ being one of them.  One of the reasons lambda
> is not encouraged is because its name is always '<lambda>', which just
> ain't helpful when the smelly becomes air borne!  ;)

Doesn't the traceback tell us exactly where the lambda was called from?

Regards,

Yawar

[toc] | [prev] | [next] | [standalone]


#84552

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2015-01-25 15:27 +1100
Message-ID<54c470a9$0$12983$c3e8da3$5496439d@news.astraweb.com>
In reply to#84543
Yawar Amin wrote:

> Hi Ethan,
> 
> On Saturday, January 24, 2015 at 9:00:12 PM UTC-5, Ethan Furman wrote:
>> [...]
>> __name__ being one of them.  One of the reasons lambda
>> is not encouraged is because its name is always '<lambda>', which just
>> ain't helpful when the smelly becomes air borne!  ;)
> 
> 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 even
so, which is easier?

"Oh, the exception occurred in myfunc"

versus

"Hmmm, the exception occurred in a lambda, lets see now, that was called
by "f", but f might have called seven different lambdas, which one was it?
Oh, I see, f was called by g, and when we call g, this variable is set to
foo which only happens when bar was called first, which means that the
lambda must have been this one here..."


Using lambda is trading off convenience when writing the code for ease of
debugging the code when a problem occurs. Whether that trade-off is
worthwhile or not depends on factors such as how likely the lambda is to
have a bug, how many of them there are, and whether or not there is
uncertainty as to which one is called from where.



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#84584

FromYawar Amin <yawar.amin@gmail.com>
Date2015-01-25 17:26 -0800
Message-ID<566d71d9-3f89-4422-b454-77913b1b5e01@googlegroups.com>
In reply to#84552
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.

> even so, which is easier?
> 
> "Oh, the exception occurred in myfunc"
> 
> versus
> 
> "Hmmm, the exception occurred in a lambda, lets see now, that was
> called by "f", but f might have called seven different lambdas, which
> [...]

True, Python's current traceback reporting doesn't tell us the exact
column number on which the exception occurred. So for example with this:

    print((lambda: 1)(), (lambda: 1 / 0)())

You will get the following traceback:

    Traceback (most recent call last):
      File "test.py", line 3, in <module>
        print((lambda: 1)(), (lambda: 1 / 0)())
      File "test.py", line 3, in <lambda>
        print((lambda: 1)(), (lambda: 1 / 0)())
    ZeroDivisionError: integer division or modulo by zero

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:

    Traceback (most recent call last):
      File "test.py", line 5, in <module>
        (lambda: 1 / 0)()
      File "test.py", line 5, in <lambda>
        (lambda: 1 / 0)()
    ZeroDivisionError: integer division or modulo by zero


> Using lambda is trading off convenience when writing the code for ease
> of debugging the code when a problem occurs. Whether that trade-off is
> worthwhile or not depends on factors such as how likely the lambda is
> to have a bug, how many of them there are, and whether or not there is
> uncertainty as to which one is called from where.

I feel like this whole 'debugging' argument is a red herring, because
you can have _many_ different expressions in a line of code all of which
could have potentially caused an exception you're trying to debug. For
example, dispensing with the lambdas:

    print(1, 1 / 0)

Error:

    Traceback (most recent call last):
      File "test.py", line 3, in <module>
        print(1, 1 / 0)
    ZeroDivisionError: integer division or modulo by zero

Again, it all comes down to Python just not telling you precisely where
the error occurred, and instead only giving you the general vicinity.
Again, this can be fixed simply by just putting the expressions on
different lines.

Regards,

Yawar

[toc] | [prev] | [next] | [standalone]


#84589

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2015-01-26 13:46 +1100
Message-ID<54c5aa96$0$12983$c3e8da3$5496439d@news.astraweb.com>
In reply to#84584
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@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@ando ~]$ python -m compileall demo.py
Compiling demo.py ...
[steve@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@ando ~]$ mv demo.py demo-save.py  # hide the source
[steve@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

[toc] | [prev] | [next] | [standalone]


#85523

Fromalbert@spenarnc.xs4all.nl (Albert van der Horst)
Date2015-02-11 13:04 +0000
Message-ID<54db5356$0$3915$e4fe514c@dreader34.news.xs4all.nl>
In reply to#84538
In article <mailman.18121.1422151185.18130.python-list@python.org>,
Ethan Furman  <ethan@stoneleaf.us> wrote:
>-=-=-=-=-=-
>
>On 01/24/2015 11:55 AM, Chris Angelico wrote:
>> On Sun, Jan 25, 2015 at 5:56 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
>>> If the non-generic is what you're concerned about:
>>>
>>> # not tested
>>> dispatch_table_a = {}
>>> dispatch_table_b = {}
>>> dispatch_table_c = {}
>>>
>>> class dispatch:
>>>   def __init__(self, dispatch_table):
>>>     self.dispatch = dispatch_table
>>>   def __call__(self, func):
>>>     self.dispatch[func.__name__] = func
>>>     return func
>>>
>>> @dispatch(dispatch_table_a)
>>> def foo(...):
>>>    pass
>>
>> That's still only able to assign to a key of a dictionary, using the
>> function name.
>
>This is a Good Thing.  The def statement populates a few items, __name__ being one of them.  One
>of the reasons lambda
>is not encouraged is because its name is always '<lambda>', which just ain't helpful when the
>smelly becomes air borne!  ;)

That's the reason why my ideal Python doesn't attach a name to a lambda denotation:

x -> x**2

is a function object, not something that has a name.

It is not until we assign the object to a name (which becomes thereby a function)
that the __name__ thingy comes into play, like so.

f = x->x**2
or
f = x-> return x**2
for those who don't like Algol68

I've heard arguments that with -> the __name__ is not filled in correctly.
I can't see why the parser would understand more easily

def f(x):
    return x**2

than

f = x->
    return x**2

[I'm striving for simplification, doing away with both the lambda and
the def keywords. This is not a proposal for a language change, I'm
trying to explore possibilities here. ]

>
>--
>~Ethan~

Groetjes Albert
-- 
Albert van der Horst, UTRECHT,THE NETHERLANDS
Economic growth -- being exponential -- ultimately falters.
albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

[toc] | [prev] | [next] | [standalone]


#85531

FromClarence <clarence1126@gmail.com>
Date2015-02-11 06:28 -0800
Message-ID<3f48cb2f-0e2a-4f47-a4cf-de658fb7b4f0@googlegroups.com>
In reply to#85523
On Wednesday, February 11, 2015 at 8:04:33 AM UTC-5, Albert van der Horst wrote:
> In article <mailman.18121.1422151185.18130.python-list@python.org>,
> Ethan Furman  <ethan@stoneleaf.us> wrote:
> >-=-=-=-=-=-
> >
> >On 01/24/2015 11:55 AM, Chris Angelico wrote:
> >> On Sun, Jan 25, 2015 at 5:56 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
> >>> If the non-generic is what you're concerned about:
> >>>
> >>> # not tested
> >>> dispatch_table_a = {}
> >>> dispatch_table_b = {}
> >>> dispatch_table_c = {}
> >>>
> >>> class dispatch:
> >>>   def __init__(self, dispatch_table):
> >>>     self.dispatch = dispatch_table
> >>>   def __call__(self, func):
> >>>     self.dispatch[func.__name__] = func
> >>>     return func
> >>>
> >>> @dispatch(dispatch_table_a)
> >>> def foo(...):
> >>>    pass
> >>
> >> That's still only able to assign to a key of a dictionary, using the
> >> function name.
> >
> >This is a Good Thing.  The def statement populates a few items, __name__ being one of them.  One
> >of the reasons lambda
> >is not encouraged is because its name is always '<lambda>', which just ain't helpful when the
> >smelly becomes air borne!  ;)
> 
> That's the reason why my ideal Python doesn't attach a name to a lambda denotation:
> 
> x -> x**2
> 
> is a function object, not something that has a name.
> 
> It is not until we assign the object to a name (which becomes thereby a function)
> that the __name__ thingy comes into play, like so.
> 
> f = x->x**2
> or
> f = x-> return x**2
> for those who don't like Algol68
> 
> I've heard arguments that with -> the __name__ is not filled in correctly.
> I can't see why the parser would understand more easily
> 
> def f(x):
>     return x**2
> 
> than
> 
> f = x->
>     return x**2
> 
> [I'm striving for simplification, doing away with both the lambda and
> the def keywords. This is not a proposal for a language change, I'm
> trying to explore possibilities here. ]
> 
> >
> >--
> >~Ethan~
> 
> Groetjes Albert
> -- 
> Albert van der Horst, UTRECHT,THE NETHERLANDS
> Economic growth -- being exponential -- ultimately falters.
> albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

And then you do
    g = f
And what then? Or even
    g = f = x -> x ** 2
Of course, you can say that problem already exists
    def f(x): return x ** 2
    g = f
But that goes to the heart of the issue: part of the purpose of def is to give a function a name.

[toc] | [prev] | [next] | [standalone]


#85538

FromEthan Furman <ethan@stoneleaf.us>
Date2015-02-11 08:52 -0800
Message-ID<mailman.18661.1423673573.18130.python-list@python.org>
In reply to#85523

[Multipart message — attachments visible in raw view] — view raw

On 02/11/2015 05:04 AM, Albert van der Horst wrote:

> That's the reason why my ideal Python doesn't attach a name to a lambda denotation:
> 
> x -> x**2

Hmmm.  So now let's say you have a few dozen of these types of functions, and you want to talk about the twenty-sixth
one with a peer... are you going to say, "Hey, look at the twenty-sixth one -- I think it has a bug." or something more
like, "Hey, I think there's a bug in the hyp sin function." ?

People use names, names are good.

Python uses names, because names are good.

--
~Ethan~

[toc] | [prev] | [next] | [standalone]


#85559

FromTerry Reedy <tjreedy@udel.edu>
Date2015-02-11 21:06 -0500
Message-ID<mailman.18674.1423706801.18130.python-list@python.org>
In reply to#85523
On 2/11/2015 8:04 AM, Albert van der Horst wrote:

> It is not until we assign the object to a name (which becomes thereby a function)
> that the __name__ thingy comes into play, like so.

But __name__ would not come into play.

> f = x->x**2

This would mean to create an anonymous function object and then bind 'f' 
to that object in the current local namespace.  It would be the same as 
the discouraged

f = lambda x: x**2

> I've heard arguments that with -> the __name__ is not filled in correctly.

Because local namespace name binding does not and should not mutate the 
object the name is bound to.

> I can't see why the parser would understand more easily
>
> def f(x):
>      return x**2
> than
>
> f = x->
>      return x**2

The parser parses both equally well.  That is not the issue.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#85563

FromIan Kelly <ian.g.kelly@gmail.com>
Date2015-02-11 19:57 -0700
Message-ID<mailman.18677.1423709889.18130.python-list@python.org>
In reply to#85523
On Wed, Feb 11, 2015 at 7:06 PM, Terry Reedy <tjreedy@udel.edu> wrote:
>> I can't see why the parser would understand more easily
>>
>> def f(x):
>>      return x**2
>> than
>>
>> f = x->
>>      return x**2
>
>
> The parser parses both equally well.  That is not the issue.

The compiler could at some point recognize that the function is being
assigned to a simple name and transform the assignment into a def for
purposes of byte code generation. It could also do the same with
lambda, although it currently doesn't.

The reason I don't like this replacing def isn't because the name is
necessarily lost. It's because the lack of the well-defined def
statement encourages more complex usages like

    functions['f'] = x -> x**2

where such implicit transformations won't work.

[toc] | [prev] | [next] | [standalone]


#85565

FromChris Angelico <rosuav@gmail.com>
Date2015-02-12 14:56 +1100
Message-ID<mailman.18679.1423713376.18130.python-list@python.org>
In reply to#85523
On Thu, Feb 12, 2015 at 1:57 PM, Ian Kelly <ian.g.kelly@gmail.com> wrote:
> The reason I don't like this replacing def isn't because the name is
> necessarily lost. It's because the lack of the well-defined def
> statement encourages more complex usages like
>
>     functions['f'] = x -> x**2
>
> where such implicit transformations won't work.

That's actually where this started. I mean, if a function can be
called "<lambda>", why can't it be called "functions['f']"?

ChrisA

[toc] | [prev] | [next] | [standalone]


#85587

FromIan Kelly <ian.g.kelly@gmail.com>
Date2015-02-12 09:17 -0700
Message-ID<mailman.18692.1423757888.18130.python-list@python.org>
In reply to#85523
On Wed, Feb 11, 2015 at 8:56 PM, Chris Angelico <rosuav@gmail.com> wrote:
> On Thu, Feb 12, 2015 at 1:57 PM, Ian Kelly <ian.g.kelly@gmail.com> wrote:
>> The reason I don't like this replacing def isn't because the name is
>> necessarily lost. It's because the lack of the well-defined def
>> statement encourages more complex usages like
>>
>>     functions['f'] = x -> x**2
>>
>> where such implicit transformations won't work.
>
> That's actually where this started. I mean, if a function can be
> called "<lambda>", why can't it be called "functions['f']"?

If the name isn't an identifier then it will make it harder to look up
the function from the name via introspection. Another thing that might
cause problems is decoration:

    say_hello = classmethod(cls -> print("Hello from %s" % cls.__name__))

How would the name say_hello make it all the way onto the function
object here? I suppose there could be a syntax like:

    @classmethod
    say_hello = cls -> print("Hello from %s" % cls.__name__)

But now the assignment statement has to be treated grammatically as a
special form of assignment, just like a def statement.

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web