Mailing List Archive

Lambda returning tuple question, multi-expression
When making a UI there are a lot of binding/trace operations that need
to occur that lead to a lot of annoying 1 use function definitions. I
don't really see lambda use like below.

Giving 2 working lambda examples using a returned tuple to accomplish
multiple expressions - what sort of gotchas, if any, might make the
following bad practice if I am missing something?


Example 1:

import tkinter as tk

main = tk.Tk()
e1 = tk.Entry(master=main)
e1["state"] = "disabled"
e1.pack()
e2 = tk.Entry(master=main)
e2["state"] = "disabled"
e2.pack()
e3 = tk.Entry(master=main)
e3["state"] = "disabled"
e3.pack()

b = tk.Button(master=main, text="Enable")
b.config(
command=lambda: (
e1.config(state="normal"),
e2.config(state="normal"),
e3.config(state="normal")
)
)
b.pack()


Example 2:

import tkinter as tk

main = tk.Tk()
l = tk.Label(master=main)
l.a = {"seconds":0}
l._updater = lambda: (
l.a.update({"seconds": 1 + l.a["seconds"]}),
l.config(text=l.a["seconds"]),
l.after(ms=1000, func=l._updater)
)
l._updater()
l.pack()

--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 08Mar2023 16:56, aapost <aapost@idontexist.club> wrote:
>When making a UI there are a lot of binding/trace operations that need
>to occur that lead to a lot of annoying 1 use function definitions. I
>don't really see lambda use like below.
>
>Giving 2 working lambda examples using a returned tuple to accomplish
>multiple expressions - what sort of gotchas, if any, might make the
>following bad practice if I am missing something?

There're no real gotchas: tuples are evaluated left to right, so you
should have things happen in the order you've expressed them (if the
order matters). What you lose with a lambda is control constructs like
loops and if-statements (well, there's the `x if foo else y` but that
gets cumbersome quickly). Once things get complicated you may want to
define a more complication function using `def`:

def callback1():
... do complicated stuff ...

b.config(command=callback1)

The only other issue, which applies across the board with GUIs and is
nothing specific to lambdas is that the GUI only renders and operates
while the main loop is running. When your callbacks do trivial stuff
you're fine. If they block (eg waiting for user input or network calls
etc) the GUI is also blocked. You need threads or other concurrent
approaches if the GUI is to stay responsive.

The flip side of that is that a concurrent context like a Thread should
not interact with the GUI directly. In things like Qt I've actually had
that mistake crash the app because the Qt framework is (was?) not thread
safe. You need to arrange that GUI actions occur in the main programme
thread. I think the same applies with tk, and is anyway probably good
practice for any GUI. It's not as hard as it sounds - typically when
something happens asynchronously you arrange to issue an "event", and
the GUI mainloop will process that as it happens - the event callback
will be fired (called) by the main loop itself and thus the callback
gets to do its thing in the main loop.

Cheers,
Cameron Simpson <cs@cskk.id.au>
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/8/2023 4:56 PM, aapost wrote:
> b = tk.Button(master=main, text="Enable")
> b.config(
>     command=lambda: (
>         e1.config(state="normal"),
>         e2.config(state="normal"),
>         e3.config(state="normal")
>     )
> )

It's hard to understand what you are trying to do here. I don't
remember just now what .config() will return, but the lambda will return
a tuple of *something*, probably (None, None, None). Even if the tuple
does contain three non-None values, config() requires named parameters,
not a tuple. In the course of executing the lambda, your three controls
e1, e2, and e2 will get configured, but you could just write a function
to do that:

def set_entries_enabled_state(enabled = True):
state = 'normal' if enabled else 'disabled'
for e in (e1, e2, e3):
e.config(state=state)

def config_b_and_entries(enabled = True):
state = 'normal' if enabled else 'disabled'
b.config(state = state)
set_entries_enabled_state(enabled)

This is easy to read and understand. (I may not have remembered some Tk
details correctly).


--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/8/23 16:56, aapost wrote:
> Thomas > Cameron

> def set_entries_enabled_state(enabled = True):
> state = 'normal' if enabled else 'disabled'
> for e in (e1, e2, e3):
> e.config(state=state)
>
> def config_b_and_entries(enabled = True):
> state = 'normal' if enabled else 'disabled'
> b.config(state = state)
> set_entries_enabled_state(enabled)

I admit adding commenting might be useful to clearly express intent for
patterns I don't commonly see. But when you are managing a bunch of
widgets and experimenting with the design, the variable name word salad
approach 'in the name of readability' starts to melt your brain a bit
and it all starts too look like a wall of..:

the_thing.the_things_thing.do_this_thing_to_that_other_thing_but_only_if_this_one_time()

So the button b in the example only needs a reference to a callable
configured, set with the command= parameter in .config()

The code:

def func():
pass
b.config(command=func)

Is equivalent. So that if the button is clicked, code at func (or the
lambda) gets called.

In both cases (as per my intent), care about the return values of the
expressions does not matter and I am fairly certain regardless of what
the callable returns, it does not get evaluated/assigned/considered
anywhere. (at least that is how I have been considering it, a void
foo(void) if you will..). Now as far as the tuple, yes, left to right
evaluation is what I expect, (hope anyway, lol),

meaning (3, False, 1, x := 5+9, print("hello{}".format(x)))
would return a tuple of (3, False, 1, 14, None) which gets assigned to
nothing and the string "hello14" printed to console.

Now.. When you want to assign a callable that requires args, the main
examples people give are a combo of them both,

def func(x,y,z):
pass
x = y = x = "something"
b.config(command=lambda x,y,z: func(x,y,z))

So as far as the examples given above (which I can't really parse), if
you meant for passing in a bool value, to do so would require something
like:

b.config(command=lambda enabled: config_b_and_entries(enabled))

Which that type of thing to me gets even harder to grok after a while,
and I guess for me I find having to go to a different scope or a
separate file to parse a bunch of definitions like these:

def set_entries_enabled_state(enabled = True):

def config_b_and_entries(enabled = True):

ends up taking me out of an object oriented focus / headspace of the
layout at hand.


And it is sort of like.. Ok I can either

b.config(command=lambda: (
a.expr,
b.expr.update({something: "morecomplicated"}),
c.expr
)
)

OR..

b.config(command=lambda a=a, b=b, c=c, s=something: foo(a, b, c, s))

somewhere else:
def foo(a, b, c, something):
a.expr
b.expr.update({something: "morecomplicated"})
c.expr


When you are trying to add a bit of dynamic feel to the tediousness of
the widget management, keeping things kind of contained to their object
just feels more natural to me (at the moment anyway).. (all the packing,
unpacking, making collections of widgets within frames appear or go away
based on states, colour changes based on input, dynamic widget
generation and where their relative attachment should go, etc)

I read a lot of sentiment against complicated lambdas suggesting one
should go for more functions, but I guess I feel pushing a more
complicated lambda to contain behavior more closely to an instance feels
more intuitive at the moment (and the sentiment against it doesn't
really resonate with me), so long as it isn't introducing some inherent
behavioral flaw or bug I am blind to..

Of course I might change my mind at some point during a refactor and
think "what the hell is that, why didn't I just..".. Which will probably
come in a few weeks. lol

One additional note on the Thread comment, I haven't really needed to
dig in to that too deeply, but Threading is amazing for tkinter UI
troubleshooting,

if you add something like:
t = threading.Thread(target=maintk.mainloop)
and run it with python -i
so long has you have attached every widget to some relative position on
to the root (maintk in this case), you can interact with any
object/widget directly live to see what is going on or what a change does.

--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/8/2023 11:19 PM, aapost wrote:
> In both cases (as per my intent)

Well, that's the trouble. You haven't stated your intent, so we're
forced to try to reverse engineer it. Below I state what my
reverse-engineering effort thinks is your intent. It would be better if
you actually said clearly what you want to achieve.

> So as far as the examples given above (which I can't really parse), if
> you meant for passing in a bool value, to do so would require something
> like:
>
> b.config(command=lambda enabled: config_b_and_entries(enabled))

As best as I can understand what you are trying to do here, it seems
like you want to enable/disable those Entry widgets when you configure
the b widget to be enabled/disabled. That way their states would all
track each other, with only a single command needing to be issued.

That seems like a sensible goal. The function config_b_and_entries()
that I suggested is not meant to be the target of a lambda expression
that is the argument of b.config(). It is meant to be called *instead*
of b.config(command = lambda.....). I can't see any benefit of trying
to force this coordination of states by using an obscure lambda
expression when you can achieve the same result with a straightforward,
easy to read function or method call.

OTOH, if you are not trying to achieve this coordination of states, then
what are you trying to do? Don't go making us guess any more.


--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/9/23 00:13, Thomas Passin wrote:
>

lol.. 'us'..

So.. to give an example from your own code:

but_play = Tk.Button(_frame, text='Play', width = BUTTONWIDTH + 1, pady
= PADY, command=lambda x=plotmgr:play_macro(x), bg = BUTTON_BG, font =
NEWFONT)

Can be written as:

b = Tk.Button(master=_frame)
b.config(text='Play', width = BUTTONWIDTH + 1, pady = PADY,
command=lambda x=plotmgr:play_macro(x), bg = BUTTON_BG, font = NEWFONT)

.config() is just a way of adding/modifying most of the same
initialization arguments after the instantiation of the object.

tkinter objects are flexible by design, You can also do
b["text"] = "adsfa"
b["command"] = lambda: (a,b,c,d)

I could also rewrite the original example with the exact same result:
b = tk.Button(
master=main,
text="Enable",
command=lambda: (
e1.config(state="normal"),
e2.config(state="normal"),
e3.config(state="normal")
)
)
b.pack()

b is not changing states at any point. Nothing "happens" to state when
.config() is called. b does nothing without a command= configured.
.config() is binding the button press event via command= to a call so
than an action can occur on button press. There is no 'instead' of
b.config() (unless of course using one of the equivalent examples above
to do the exact same thing)

The disconnect is not in the not understanding of my code, it's in the
not understanding of .config(). (which is no big deal, I forgot what a
file buffer was just this week, lol)

So, as far as the examples, they are simplified abstract
psuedo-like-code illustrations to accompany the ask of and open ended
question regarding a programming practice.

(I did forgot to include main.mainloop() at the end of each one, as I
was using python -i when I quickly wrote them)

example 1 described:
A button press does sequence of actions against widgets, i.e. I have 3
disabled widgets, I press a button, now in sequence all 3 are enabled so
I can type in them. Without a lambda tuple sequence this requires a def
and an another lambda.

example 2 described:
dict with an attribute assigned to a label (you can't assign directly to
external variables within a lambda, but you can call something with a
method like .update(), which why would that be considered any different
than any other state changing call?)
Then lambda assigned to a label and called to start it up, recursively
performs an update to said widget every 1 second. There are several ways
to do this without a lambda tuple sequence, none as concise.

The 'what I am trying to do' is ask a question regarding opinions and
practices on issuing a sequence of actions within a lambda via a tuple
(since the common practice approaches against it - mainly with tkinter -
feel more convoluted), and in doing so leaving it open ended to get a
feel on what opinions are, and to see if any opinions influence mine.
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 08/03/2023 21:56, aapost wrote:
> When making a UI there are a lot of binding/trace operations that need
> to occur that lead to a lot of annoying 1 use function definitions. I
> don't really see lambda use like below.

Lambdas are very common in GUI callbacks but I admit I've never seen
tuples used to create multiple expressions. That's a neat trick I
hadn't thought of and will probably use.

> Giving 2 working lambda examples using a returned tuple to accomplish
> multiple expressions - what sort of gotchas, if any, might make the
> following bad practice if I am missing something?

Not bad practice per-se but you are still limited to expressions, no
loops for example (although you could fake it with a list comp,
but that gets ugly fast!)

Also layout is all important here. It could get very messy to read if
indentation isn't clear. You only have to look at some Javascript code
with function definitions as arguments to functions to see how clunky
that can be.

Similarly debugging so much code passed as arguments might be an
issue - no easy way to step into the lambda.

But as an alternative to writing many typical event handlers it's
definitely a valid approach that I'll be trying.

> b = tk.Button(master=main, text="Enable")
> b.config(
> command=lambda: (
> e1.config(state="normal"),
> e2.config(state="normal"),
> e3.config(state="normal")
> )
> )

You could of course single line that with:

b = tk.Button(master=main,
text="Enable",
command=lambda: (
e1.config(state="normal"),
e2.config(state="normal"),
e3.config(state="normal")
)
)

It's not a radical change from using a lamba as a
callback but it does extend the use case to cover
a common GUI scenario.

I like it. I wish I'd thought of it years ago.
Thanks for sharing.

--
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
Other than the admittedly subjective viewpoint that using the lambda is confusing, there?s probably nothing wrong with the lambda approach. A couple of alternatives:


def e():
for e in (e1,e2,e3):
e.config(state="normal")
b = tk.Button(master=main, text="Enable",command=e)

or

b = tk.Button(master=main, text="Enable",command=lambda: [e.config(state="normal") for e in (e1, e2, e3)])

From: Python-list <python-list-bounces+gweatherby=uchc.edu@python.org> on behalf of aapost <aapost@idontexist.club>
Date: Wednesday, March 8, 2023 at 5:15 PM
To: python-list@python.org <python-list@python.org>
Subject: Lambda returning tuple question, multi-expression
*** Attention: This is an external email. Use caution responding, opening attachments or clicking on links. ***

When making a UI there are a lot of binding/trace operations that need
to occur that lead to a lot of annoying 1 use function definitions. I
don't really see lambda use like below.

Giving 2 working lambda examples using a returned tuple to accomplish
multiple expressions - what sort of gotchas, if any, might make the
following bad practice if I am missing something?


Example 1:

import tkinter as tk

main = tk.Tk()
e1 = tk.Entry(master=main)
e1["state"] = "disabled"
e1.pack()
e2 = tk.Entry(master=main)
e2["state"] = "disabled"
e2.pack()
e3 = tk.Entry(master=main)
e3["state"] = "disabled"
e3.pack()

b = tk.Button(master=main, text="Enable")
b.config(
command=lambda: (
e1.config(state="normal"),
e2.config(state="normal"),
e3.config(state="normal")
)
)
b.pack()


Example 2:

import tkinter as tk

main = tk.Tk()
l = tk.Label(master=main)
l.a = {"seconds":0}
l._updater = lambda: (
l.a.update({"seconds": 1 + l.a["seconds"]}),
l.config(text=l.a["seconds"]),
l.after(ms=1000, func=l._updater)
)
l._updater()
l.pack()

--
https://urldefense.com/v3/__https://mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!i8mp9fRKlBLziXCn6-OIC8fNx0LBohis8m6VARp17Igg5036wrTflGiwwptY18Rgkriw5MquUKxe9Fglqpu8FHEy$<https://urldefense.com/v3/__https:/mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!i8mp9fRKlBLziXCn6-OIC8fNx0LBohis8m6VARp17Igg5036wrTflGiwwptY18Rgkriw5MquUKxe9Fglqpu8FHEy$>
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On Thu, 9 Mar 2023 at 09:14, aapost <aapost@idontexist.club> wrote:
>
> When making a UI there are a lot of binding/trace operations that need
> to occur that lead to a lot of annoying 1 use function definitions. I
> don't really see lambda use like below.
>
> Giving 2 working lambda examples using a returned tuple to accomplish
> multiple expressions - what sort of gotchas, if any, might make the
> following bad practice if I am missing something?

IMO there's nothing wrong with this, but it's worth considering a few
alternatives. For instance, the single-use function might be less
annoying if you don't have to also name the function in the
constructor; this could be achieved with proper use of a decorator, or
possibly use of a class namespace, with an appropriate helper. But
otherwise, this tuple trick seems pretty much fine to me, although
you're still restricted to a series of expressions (no statements), so
your code is still going to get a bit awkward if it does anything
complicated.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/9/2023 3:29 AM, aapost wrote:
> The 'what I am trying to do' is ask a question regarding opinions and
> practices on issuing a sequence of actions within a lambda via a tuple
> (since the common practice approaches against it - mainly with tkinter -
> feel more convoluted), and in doing so leaving it open ended to get a
> feel on what opinions are, and to see if any opinions influence mine.

I finally realized why I am uncomfortable with doing this kind of thing.
It's because, IMHO, lambda expressions should not have side effects
and should not require much mental effort to grasp what they do. Yes,
you can do so. You can even print from a lambda (and it might be useful
for debugging):

lambda x: print(f'The lambda received {x}') or x*x

The Python Reference on readthedocs.io also has a tk example that
specifically wants the side effect (see
https://python-reference.readthedocs.io/en/latest/docs/operators/lambda.html):

>>> # this is a code snippet from a Tkinter gui app
>>> # in this case lambda is quite convenient
>>> self.btn_cancel = Button(self.progress_container, text='Cancel',
>>> command=lambda: subprocess.call('taskkill /f /im uberzip.exe',
>>> shell=True))

Maybe so, but I think it's better not to have side effects hidden away
in expressions that are hard to read and understand. And being
anonymous, there is no function name to remind you what the action is
suppose to do. Much better (still IMHO, of course):

def kill_uberzip():
"""Kill an external running program named uberzip.exe."""
subprocess.call('taskkill /f /im uberzip.exe', shell=True))

self.btn_cancel = Button(self.progress_container, text='Cancel',
command = kill_uberzip())

This way, it's easy to understand what btn_cancel() will do each time
you scan that line of code. Using the lambda makes you reparse the line
and spend mental effort each time you scan it. And this way, you know
directly that the button is going to cause a side effect outside your
program, which you have to infer (an indirect mental operation) when you
scan the lambda.

For this particular example, it might turn out that there could be more
than one instance of uberzip.exe running at the same time. Which one
should be killed, and how do you kill the right one? With the function,
you can get those details under control, but I hate to think what might
happen to the lambda expression.

Yes, of course, there can be times when the lambda expression is
somewhat easy to understand and the side effects are harmless. In that
case, it may be easy enough to grasp quickly that the anonymous function
would not benefit from having a name. So OK, it's not a hard-and-fast
rule.
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 09Mar2023 09:06, Alan Gauld <learn2program@gmail.com> wrote:
>On 08/03/2023 21:56, aapost wrote:
>> When making a UI there are a lot of binding/trace operations that need
>> to occur that lead to a lot of annoying 1 use function definitions. I
>> don't really see lambda use like below.
>
>Lambdas are very common in GUI callbacks but I admit I've never seen
>tuples used to create multiple expressions. That's a neat trick I
>hadn't thought of and will probably use.

I often uses as a debugging hack. Given:

foo=lambda: expr

I'll often write:

foo=lambda: (X("foo happening here!"), ...maybe more..., expr)[-1]

to embed some debug tracing in a lambda defined expression.

Cheers,
Cameron Simpson <cs@cskk.id.au>
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 09Mar2023 09:06, Alan Gauld <learn2program@gmail.com> wrote:
>Also layout is all important here. It could get very messy to read if
>indentation isn't clear. You only have to look at some Javascript code
>with function definitions as arguments to functions to see how clunky
>that can be.

Just a note that some code formatters use a trailing comma on the last
element to make the commas fold points. Both yapf (my preference) and
black let you write a line like (and, indeed, flatten if short enough):

( a, b, c )

but if you write:

( a, b, c, )

they'll fold the lines like:

( a,
b,
c,
)

in varying flavours of indentation depending on tuning. The point being
that if, like me, you often have a code formatter active-on-save it can
be hinted to nicely present complex tuples (or parameter lists and
imports).

It isn't magic, but can be quite effective.

Cheers,
Cameron Simpson <cs@cskk.id.au>
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On Fri, 10 Mar 2023 at 07:43, Thomas Passin <list1@tompassin.net> wrote:
>
> On 3/9/2023 3:29 AM, aapost wrote:
> > The 'what I am trying to do' is ask a question regarding opinions and
> > practices on issuing a sequence of actions within a lambda via a tuple
> > (since the common practice approaches against it - mainly with tkinter -
> > feel more convoluted), and in doing so leaving it open ended to get a
> > feel on what opinions are, and to see if any opinions influence mine.
>
> I finally realized why I am uncomfortable with doing this kind of thing.
> It's because, IMHO, lambda expressions should not have side effects
> and should not require much mental effort to grasp what they do. Yes,
> you can do so. You can even print from a lambda (and it might be useful
> for debugging):
>
> lambda x: print(f'The lambda received {x}') or x*x

I'm not sure why lambda functions shouldn't have side effects. They
can be used for anything, as long as it's a single expression (which
can, of course, be composed of other expressions).

> The Python Reference on readthedocs.io also has a tk example that
> specifically wants the side effect (see
> https://python-reference.readthedocs.io/en/latest/docs/operators/lambda.html):
>
> >>> # this is a code snippet from a Tkinter gui app
> >>> # in this case lambda is quite convenient
> >>> self.btn_cancel = Button(self.progress_container, text='Cancel',
> >>> command=lambda: subprocess.call('taskkill /f /im uberzip.exe',
> >>> shell=True))
>
> Maybe so, but I think it's better not to have side effects hidden away
> in expressions that are hard to read and understand. And being
> anonymous, there is no function name to remind you what the action is
> suppose to do. Much better (still IMHO, of course):

The purpose of the lambda function is to tie the effect to the
function. Otherwise, you have to go elsewhere to figure out what each
one does. It's not inherently better to give names to everything,
especially when those functions are only ever used once. In fact, I'd
say that that is usually *worse*.

> def kill_uberzip():
> """Kill an external running program named uberzip.exe."""
> subprocess.call('taskkill /f /im uberzip.exe', shell=True))
>
> self.btn_cancel = Button(self.progress_container, text='Cancel',
> command = kill_uberzip())

And this is one of the reasons it's worse: with the lambda function,
you won't fall into this trap. It's a classic bug, and a rather nasty
one; can you see it? If not, this is a VERY strong argument in favour
of the lambda function. Even if you can, the mere fact that you made
this error indicates how easy it is to slip into the faulty way of
writing it.

> This way, it's easy to understand what btn_cancel() will do each time
> you scan that line of code.

Only if the name is enough information, and it seldom is. Whereas with
the lambda function, you have the entire code there, so you can
understand what btn_cancel does without having to bookmark your spot
there, go elsewhere, and find out what kill_uberzip() does.

> For this particular example, it might turn out that there could be more
> than one instance of uberzip.exe running at the same time. Which one
> should be killed, and how do you kill the right one? With the function,
> you can get those details under control, but I hate to think what might
> happen to the lambda expression.

Maybe, but this is just the example getting in the way. Obviously the
larger a function needs to be, the less reasonable to make it a lambda
function.

That's why I'm of the opinion that it's better to use a different
system, such as function decorators or a class namespace. For example:

self.btn_cancel = Button(...)

@on(self.btn_cancel, "command")
def kill_uberzip(): ...

Or alternatively:

class btn_cancel(Button):
def command(self):
...

These kinds of systems allow much more flexibility than lambda
functions, without the issues of name repetition and disconnection.

> Yes, of course, there can be times when the lambda expression is
> somewhat easy to understand and the side effects are harmless. In that
> case, it may be easy enough to grasp quickly that the anonymous function
> would not benefit from having a name. So OK, it's not a hard-and-fast
> rule.

Definitely not a hard-and-fast rule, but in defense of the Python's
lambda, at least we don't have resonance cascades happening.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/9/23 04:06, Alan Gauld wrote:
>

Thank you for the feedback, I appreciate the comments.

To add a little extra, there is actually a reason I lean toward overuse
of .config() for a lot of things even though they could be sent to the
constructor (other than that w["attribute"]= doesn't work in a lambda).

It comes from a use case I needed to solve in the shortcoming of tk with
frames not being scrollable, requiring a canvas.

When dynamically generating a series of widgets (i.e. loading a custom
json/xml layout config file), getting the scrollable area right is a bit
of a chore with a catch-22 situation.

With an inner frame in a canvas, pack_propogate(tk.True) is the default
on the frame, so that the children dictate the size, but in doing so it
prevents the frame from expanding to the full width of the area it's
given within the UI layout.

If you turn pack_propogate to tk.False, it breaks the ability for the
canvas to return bbox(tk.ALL) (which is kind of rough anyway as the tcl
docs state "The return value may overestimate the actual bounding box by
a few pixels."). So you end up with 1,1 and no way of knowing what size
scrollable area to set. Trying to obtain this value from a source
outside of the canvas requires knowing what you are placing the canvas
in each time, and was creating a similar catch-22 as the outer widget
doesn't know what it wants it's size to be without knowing what the
inner widget wants.. Switching to False, grabbing bbox, then back to
True of course causes an unsightly flicker and disrupts the smoothness
of the user experience.

So my solution is to create a widget instantiator which does a few
things, mainly adding something I call a "ghost".

it's something like this:

instantiator(qlass, master, config_args=None, pack_args=None,
init_args=None, ghost=False):
if not init_args:
init_args = {}
object = qlass(master=master, **init_args)
if hasattr(master, "ghost"):
object.ghost = qlass(master=master.ghost, **init_args)
elif ghost:
object.ghost = qlass(master=tk.Frame(), **init_args)

When i pass it a canvas and say ghost=True, the canvas gets a .ghost
duplicate, which is attached to an arbitrary frame that I never pack and
stays invisible.

Subsequent widgets created to the canvas then see that their parent,
starting with the canvas, have a ghost, and in return get a ghost of
themselves attached to their parents ghost.

This allows you to get an accurate bbox size from the unseen ghost
canvas that mirrors the visible version.

Keeping the init_args down to only what is necessary helps in
consistency, and the subsequent config_args and pack_args I have in
their respective dicts.

This also allows me to create a quality of life pack function I call ppack()

def ppack(self):
self.pack(**self.pack_args)
if hasattr(self, "ghost"):
self.ghost.pack(**self.ghost.pack_args)
return self

That allows each primary widget to manage and track their own set of
configurations.

Of course I could strip a lot of that crap out if I find a better and
smooth way of obtaining those bbox numbers, but I didn't see any quick
solutions in glancing through the tcl/tk source so I went with what
works for now.







--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/9/23 16:37, Cameron Simpson wrote:
> On 09Mar2023 09:06, Alan Gauld <learn2program@gmail.com> wrote:

> Just a note that some code formatters use a trailing comma on the last
> element to make the commas fold points. Both yapf (my preference) and
> black let you write a line like (and, indeed, flatten if short enough):
>
>     ( a, b, c )
>
> but if you write:
>
>     ( a, b, c, )
>
> they'll fold the lines like:
>
>     ( a,
>       b,
>       c,
>     )
> Cameron Simpson <cs@cskk.id.au>


Thanks for the info, good to know, I actually do like the idea of
trailing commas for tuples (helps prevent things like the difference
between ("abc") and ("abc",) and makes swapping things around nicer.

I've just been using a lot of json lately and it has been subconsciously
training me different, lol.
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 09Mar2023 17:55, aapost <aapost@idontexist.club> wrote:
>On 3/9/23 16:37, Cameron Simpson wrote:
>>Just a note that some code formatters use a trailing comma on the last
>>element to make the commas fold points. Both yapf (my preference) and
>>black let you write a line like (and, indeed, flatten if short
>>enough):
>>
>>     ( a, b, c )
>>
>>but if you write:
>>
>>     ( a, b, c, )
>>
>>they'll fold the lines like:
>>
>>     ( a,
>>       b,
>>       c,
>>     )
>>Cameron Simpson <cs@cskk.id.au>
>
>
>Thanks for the info, good to know, I actually do like the idea of
>trailing commas for tuples (helps prevent things like the difference
>between ("abc") and ("abc",) and makes swapping things around nicer.

Just keep in mind that if you restructure code with copy/paste it can be
a small issue (not really tied to the trailing comma but commas in
general. More than once I've been bitten by doing roughly this:

f( a,
b=x+y,
)

shuffled to:

b=x+y,
f( a,
b=b,
)

Whoops! It shows up almost immediately, but the first time it took me a
while to see that stray comma.

>I've just been using a lot of json lately and it has been
>subconsciously training me different, lol.

Yes, it hates the trailing comma. So annoying.

Cheers,
Cameron Simpson <cs@cskk.id.au>
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/9/23 15:25, Thomas Passin wrote:
> >>> # this is a code snippet from a Tkinter gui app
> >>> # in this case lambda is quite convenient
> >>> self.btn_cancel = Button(self.progress_container, text='Cancel',
> >>>     command=lambda: subprocess.call('taskkill /f /im uberzip.exe',
> >>>     shell=True))

> def kill_uberzip():
>     """Kill an external running program named uberzip.exe."""
>     subprocess.call('taskkill /f /im uberzip.exe', shell=True))
>
> self.btn_cancel = Button(self.progress_container, text='Cancel',
>      command = kill_uberzip())
>
> This way, it's easy to understand what btn_cancel() will do each time
> you scan that line of code.  Using the lambda makes you reparse the line
> and spend mental effort each time you scan it.  And this way, you know
> directly that the button is going to cause a side effect outside your
> program, which you have to infer (an indirect mental operation) when you
> scan the lambda.
>
> For this particular example, it might turn out that there could be more
> than one instance of uberzip.exe running at the same time.  Which one
> should be killed, and how do you kill the right one?  With the function,
> you can get those details under control, but I hate to think what might
> happen to the lambda expression.
>
> Yes, of course, there can be times when the lambda expression is
> somewhat easy to understand and the side effects are harmless.  In that
> case, it may be easy enough to grasp quickly that the anonymous function
> would not benefit from having a name.  So OK, it's not a hard-and-fast
> rule.

The not knowing which uberzip to kill is a design choice, the bad design
of the the example is not really the fault of the lambda.

And in general with naming anything, much has to do with context.
Buttons perform actions, it should be implicitly understood at the
button level and in context of where it is placed what it is a button is
for. A button without a command has no use, so it is understood it needs
to do something, with good design it would be better to know what the
buttons purpose is, rather than having to then parse an additional
function to figure it out.


Case in point, to solve the subprocess problem, (ignoring the "whys" and
the lack of usefulness of this example, just grok the pattern, maybe you
are designing a game of whack-a-mole and are watching 4 things and need
to do something when you see a change from 1 of them)


import tkinter as tk
import subprocess

main = tk.Tk()
main.pids = {
"messages": None,
"syslog": None,
"kern": None,
"user": None,
}

tk.Button(
master=main,
text="Start tailing logs",
command=lambda: (
main.pids.update({"messages" :subprocess.Popen(["tail", "-n", "1",
"-f", "/var/log/messages"])}),
main.pids.update({"syslog" :subprocess.Popen(["tail", "-n", "1", "-f",
"/var/log/syslog"])}),
main.pids.update({"kern" :subprocess.Popen(["tail", "-n", "1", "-f",
"/var/log/kern.log"])}),
main.pids.update({"user" :subprocess.Popen(["tail", "-n", "1", "-f",
"/var/log/user.log"])}),
),
).pack()

tk.Button(
master=main,
text="Kill messages tail",
command=lambda: (
main.pids["messages"].kill() if main.pids["messages"] else None,
),
).pack()

tk.Button(
master=main,
text="Kill syslog tail",
command=lambda: (
main.pids["syslog"].kill() if main.pids["syslog"] else None,
),
).pack()

tk.Button(
master=main,
text="Kill kern tail",
command=lambda: (
main.pids["kern"].kill() if main.pids["kern"] else None,
),
).pack()

tk.Button(
master=main,
text="Kill user tail",
command=lambda: (
main.pids["user"].kill() if main.pids["user"] else None,
),
).pack()

tk.Button(
master=main,
text="Kill all tails",
command=lambda: (
main.pids["messages"].kill() if main.pids["messages"] else None,
main.pids["syslog"].kill() if main.pids["syslog"] else None,
main.pids["kern"].kill() if main.pids["kern"] else None,
main.pids["user"].kill() if main.pids["user"] else None,
),
).pack()

main.mainloop()


--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/10/23 18:46, aapost wrote:

>         main.pids.update({"messages" :subprocess.Popen(["tail", "-n",
> "1", "-f", "/var/log/messages"])}),
>         main.pids.update({"syslog" :subprocess.Popen(["tail", "-n",
> "1", "-f", "/var/log/syslog"])}),
>         main.pids.update({"kern" :subprocess.Popen(["tail", "-n", "1",
> "-f", "/var/log/kern.log"])}),
>         main.pids.update({"user" :subprocess.Popen(["tail", "-n", "1",
> "-f", "/var/log/user.log"])}),
>     ),

To pre-emptively address the bug there it would need to be:

main.pids.update({"messages" :subprocess.Popen(["tail", "-n", "1", "-f",
"/var/log/messages"])}) if not main.pids["messages"] else None,
main.pids.update({"syslog" :subprocess.Popen(["tail", "-n", "1", "-f",
"/var/log/syslog"])}) if not main.pids["syslog"] else None,
main.pids.update({"kern" :subprocess.Popen(["tail", "-n", "1", "-f",
"/var/log/kern.log"])}) if not main.pids["kern"] else None,
main.pids.update({"user" :subprocess.Popen(["tail", "-n", "1", "-f",
"/var/log/user.log"])}) if not main.pids["user"] else None,

which does start to break down readability due to line length, as there
isn't really an indention rule set for something uncommonly used.

but some renaming makes the pattern clearer

pids.update({"messages" :subprocess.Popen(["cmd1"])}) if not
pids["messages"] else None,
pids.update({"syslog" :subprocess.Popen(["cmd2"])}) if not
pids["syslog"] else None,
pids.update({"kern" :subprocess.Popen(["cmd3"])}) if not pids["kern"]
else None,
pids.update({"user" :subprocess.Popen(["cmd4"])}) if not pids["user"]
else None,

and adding a comment to say something like
# starting a series of processes via lambda tuple sequence if process
not running
#pattern: p.update({"name":sp.Popen(cmd)}) if not p["name"] else None,

--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/10/2023 7:07 PM, aapost wrote:
> which does start to break down readability due to line length, as there
> isn't really an indention rule set for something uncommonly used.
>
> but some renaming makes the pattern clearer
>
> pids.update({"messages" :subprocess.Popen(["cmd1"])}) if not
> pids["messages"] else None,
> pids.update({"syslog" :subprocess.Popen(["cmd2"])}) if not
> pids["syslog"] else None,
> pids.update({"kern" :subprocess.Popen(["cmd3"])}) if not pids["kern"]
> else None,
> pids.update({"user" :subprocess.Popen(["cmd4"])}) if not pids["user"]
> else None,

I'd make the pattern in this example even more understandable and less
error-prone:

def update_pids(target):
cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
pids.update({target: subprocess.Popen(cmd)}) if not \
pids[target] else None

lambda x: ( # The Tk callback includes an event arg, doesn't it?
update_pids('messages'),
update_pids('syslog'),
# etc
)

--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 2023-03-10 at 22:16:05 -0500,
Thomas Passin <list1@tompassin.net> wrote:

> I'd make the pattern in this example even more understandable and less
> error-prone:
>
> def update_pids(target):
> cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
> pids.update({target: subprocess.Popen(cmd)}) if not \
> pids[target] else None

I might be missing something, but how is that more understandable and
less error prone than any of the following:

if not pids[target]:
cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
pids.update({target: subprocess.Popen(cmd)})

or:

cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
pids[target] or pids.update({target: subprocess.Popen(cmd)})

or:

if pids[target]:
pass
else:
cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
pids.update({target: subprocess.Popen(cmd)})

Picking a nit, that's not a good place to continue that line with the
backslash, either. IMO, "not pids[target]" should be atomic.
--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/10/2023 10:37 PM, 2QdxY4RzWzUUiLuE@potatochowder.com wrote:
> On 2023-03-10 at 22:16:05 -0500,
> Thomas Passin <list1@tompassin.net> wrote:
>
>> I'd make the pattern in this example even more understandable and less
>> error-prone:
>>
>> def update_pids(target):
>> cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
>> pids.update({target: subprocess.Popen(cmd)}) if not \
>> pids[target] else None
>
> I might be missing something, but how is that more understandable and
> less error prone than any of the following:

The main point is that there can be an easy-to-read and
easy-to-understand expression to use in the lambda. I don't care about
the exact details here. The command wasn't even mine. But you only
have to scan it and grasp it once, in the def:, and then only until you
get it working. In the lambda, my suggestions makes it much easier to
understand what the lambda is expected to do - and it will still be
clear in three months when the code gets revisited.


> if not pids[target]:
> cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
> pids.update({target: subprocess.Popen(cmd)})
>
> or:
>
> cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
> pids[target] or pids.update({target: subprocess.Popen(cmd)})
>
> or:
>
> if pids[target]:
> pass
> else:
> cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
> pids.update({target: subprocess.Popen(cmd)})
>
> Picking a nit, that's not a good place to continue that line with the
> backslash, either.
I only continued the line for the purposes of the post because it would
have gotten line wrapped otherwise. If it were my code in a real module
I would probably have treated it differently.

> IMO, "not pids[target]" should be atomic.

Again, it wasn't my code. I don't even know if the code fragments would
actually work - they were presented as illustrative examples by the OP.
And anyway, these nits obscure the point of my post. In a nutshell, it
is to use good naming and encapsulation to make your code as close to
self-documenting and as error-free as is reasonably possible, whenever
you can. Good function names are only one step towards this goal.

Lambdas often work against this goal, so it's worthwhile spending some
effort to see how to counteract this tendency.

And it's a goal. You can never meet it 100%. That's OK. There will
always be a matter of taste involved. That's OK too. It's still a
worthwhile goal.

--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/10/23 22:16, Thomas Passin wrote:
> On 3/10/2023 7:07 PM, aapost wrote:
>> which does start to break down readability due to line length, as
>> there isn't really an indention rule set for something uncommonly used.
>>
>> but some renaming makes the pattern clearer
>>
>> pids.update({"messages" :subprocess.Popen(["cmd1"])}) if not
>> pids["messages"] else None,
>> pids.update({"syslog" :subprocess.Popen(["cmd2"])}) if not
>> pids["syslog"] else None,
>> pids.update({"kern" :subprocess.Popen(["cmd3"])}) if not pids["kern"]
>> else None,
>> pids.update({"user" :subprocess.Popen(["cmd4"])}) if not pids["user"]
>> else None,
>
> I'd make the pattern in this example even more understandable and less
> error-prone:
>
> def update_pids(target):
>     cmd = ["tail", "-n", "1", "-f", f"/var/log/{target}"]
>     pids.update({target: subprocess.Popen(cmd)}) if not \
>         pids[target] else None
>
> lambda x: ( # The Tk callback includes an event arg, doesn't it?
>             update_pids('messages'),
>             update_pids('syslog'),
>             # etc
>           )
>

So yeah, that's along the same lines. cmd could be in a function, or
just outside the lambdas. Could also do from subprocess import Popen to
shorten that, etc.

The .trace( on the tkinter Vars use events, so you have to do something
like
lambda _a, _b, _c: stuff

But not in the above case. My focus these last couple week hasn't used
any of those so the specifics aren't as fresh (still heading back in
that direction), when required python will definitely let you know, lol.

The additional note in the above is, when taking the def route above,
the thing you would have to consider is what scope is the dictionary pids?

Do you need to submit it to the lambda and subsequently the function
such as
lambda pids=pids: (
update_pids("messages", pids),
update_pids("syslog", pids),

So that update_pids can access it? Or in your design do you have a
separate management of it that def update_pids already has access to?
(either is valid depending on design intent).

In the direction I am going, in trying to build some compound widgets,
and I need to grab stuff from one widget and send it to another within
the same widget group, so in cases where it goes to an external
function, it has to be sent as an arg through the lambda
whatever=whatever: ( function(whatever), )


--
https://mail.python.org/mailman/listinfo/python-list
Re: Lambda returning tuple question, multi-expression [ In reply to ]
On 3/10/2023 11:15 PM, aapost wrote:
> On 3/10/23 22:16, Thomas Passin wrote:
[...]
> The additional note in the above is, when taking the def route above,
> the thing you would have to consider is what scope is the dictionary pids?
>
> Do you need to submit it to the lambda and subsequently the function
> such as
> lambda pids=pids: (
>     update_pids("messages", pids),
>     update_pids("syslog", pids),
>
> So that update_pids can access it? Or in your design do you have a
> separate management of it that def update_pids already has access to?
> (either is valid depending on design intent).

It's easy enough to try out. I'm not going to simulate the dictionary,
because it's basically just another argument, and I assume that your
example code uses it because it needs to be in that form. For the scope
question, see below after the basic example.

>>> def printme(x): print(x) # Let's not quibble about inline defs!

>>> printme('xxx')
xxx
>>> cmd = lambda x: (
... printme('this'),
... printme('is'),
... printme('a test')
... )

(Yes, I know it's not recommended to assign a lambda to a variable name,
but I'm doing it here just to demonstrate that the lambda works as desired).

>>> cmd(2)
this
is
a test
(None, None, None)

So it executes the intended steps and returns a tuple of three None
values, as expected. When used as the target of the "command" arg in
the Tk control constructor, I presume the tuple would be ignored just as
a return of None would be. But that would need to be tested.

If returning a tuple instead of None were to cause a problem, you could
do this:

cmd = lambda x: (
printme('this'),
printme('is'),
printme('a test')
) and None

>>> cmd(2)
this
is
a test

But now you are introducing a construct whose purpose is not totally
obvious, does not cause any intended effect, and in fact is only
required by the nature of the code receiving the callback, which you
cannot know by reading this code. So if this construct turns out to be
needed, we're forced to take a step away from having the code be as
direct and understandable as possible. It's still better in that way
than the earlier illustrations (which BTW would also have required the
"and None" construct).

To test out the scope question:

>>> def printme(x): print(y) # Print some other variable, not "x"
>>> y = 'I am y' # Will "y" get passed through into the lambda?

>>> cmd = lambda x: (
... printme('this'),
... printme('is'),
... printme('a test')
... ) and None

>>> cmd(2)
I am y
I am y
I am y

But maybe the original value of "y" gets baked in at compile time. Let's
see:

>>> y = 'I am a post-compile assignment'
>>> cmd(2)
I am a post-compile assignment
I am a post-compile assignment
I am a post-compile assignment

Good, the current value of "y" gets used.

--
https://mail.python.org/mailman/listinfo/python-list