First lambda capture of local variable always False - python

Connecting a lambda function to a Qt widget, lambda needs to capture two local variables and pass them to an external function. I'm having an issue that's stumping me because it is definitely positional:
pushbutton.clicked.connect(lambda ca=current_answer, a=correct_answer:
self.ap.parse_answer(ca, a))
passes (False, "desired correct_answer string"), while switching the order of the local variable captures:
pushbutton.clicked.connect(lambda a=correct_answer,ca=current_answer:
self.ap.parse_answer(ca, a))
passes ("desired current_answer string", False)
Whichever variable I capture first is always set to False while the second variable capture is always what is intended.
This suggests that there isn't something else in my code assigning False to either current_answer or correct_answer, and a print() statement inserted immediately prior to calling the lambda function confirms that both variables are set to the desired strings. Maybe I'm just not capturing the variables correctly, but after doing some reading I can't find any issue with my syntax. My variable captures look the same as numerous examples I have found.

You are always seeing False as the first argument passed in to your callback lambda because Qt defines the signal QAbstractButton::clicked to take a single argument with a default value of False. Since your lambda is handling that signal, it gets called with False.
For the benefit of others: because the lambda gets called only with a single argument, the second and third parameters get their default values, which the OP has now defined as the values of the in-scope variables answer and current_answer.

I'm pretty sure it's just the following:
When we define default arguments in the function definition f(keyword=default...) but in the call we don't explicitly state keyword=value then it cares about order of arguments, even if they appear to match the keyword. So I believe your lambda function cares about the order of what it's receiving, not the default values you're assigning. In your first example, ca gets whatever the first argument is being sent to lambda and a gets whatever the second one is. In your second example a gets whatever the first argument is and ca gets whatever the second one is. The fact that you've defined default values for them isn't doing anything, unless whatever is calling the lambda is sending it nothing.
If I define a function
def f(a=1,b=2):
return (a,b)
and then call it as
a=True
b=False
(x,y) = f(a,b)
I'll find x=True, b=False
but if instead I call it as
a=True
b=False
(x,y) = f(b,a)
I'll find x=False, b=True because when I call it this way it understands that the first argument of the call is a and the second is b.
If I use the third option (which is what I think you're anticipating)
(x,y) = f(b=False, a=True)
then I get x=True, y=False. I think this is the behavior you thought you would get.

Based on the comments of BrenBarn and Lambda Fairy I decided to assume Qt was resetting the value of the first argument to False.
As a work-around, I added a third positional argument to ap.parse_answer, and had the lambda expression capture a trivial local variable before capturing the two vital variables:
pushbutton.clicked.connect(lambda sacrificial="",a=answer,ca=current_answer:
self.ap.parse_answer(sacrificial, ca, a))
The variable that Qt writes over just gets ignored by the called function, and the code works.
If I add a print(sacrificial_variable) call to ap.parse_answer(), not surprisingly its value is False.

Related

Why are values from a dict not being returned even if keys are present when passing a function as default?

I'm a bit puzzled by the following behaviour. I am retrieving values from a dict such that if a key does not exist I call a function which creates the value and inserts it into the dict. I do this through the default parameter of the dictionary.get method. The problem is that the default function keeps being called EVEN IF the value is already present in the dictionary.
Really mindboggling. Any ideas of why is this happening?
dictionary = {}
def load_default(key):
print("Inside function")
value = "world"
dictionary[key] = value
return value
print(dictionary)
'{}' #empty dict, everything ok
value = dictionary.get("hello", load_default("hello"))
'Inside function' # we ask for "hello", which does not exist so we call load_default
print(dictionary)
"{'hello': 'world'}" # the dict now contains de key "hello"
value = dictionary.get("hello", load_default("hello"))
'Inside function' # we ask for "hello" again, but load_default is called instead ("Inside function" is printed) I would expect the dict to return only the value and not call `load_default`
In the given example, I would expect the dictionary to return the value world WITHOUT calling the load_default function
In the given example, I would expect the dictionary to return the value world WITHOUT calling the load_default function.
When executing dictionary.get("hello", load_default("hello")) Python evaluates the expressions passed to .get() method as parameter first and then calls the method with values obtained from the evaluation of the provided parameter.
The first passed expression is a value "hello" so it can be passed to .get() as it is. The second as parameter passed expression is load_default("hello") which needs to be evaluated to a value as it is a call to a function. To obtain a value the function must be executed and the resulting return value of the function will be then passed as second parameter to .get().
At that stage .get() isn't executed yet so the assumption that the load_default() function will be run only if the key isn't in the dictionary is wrong leading to confusion.
In other words .get() will be executed AFTER it gets its parameter passed as values and because a function call is not a value it needs to be evaluated to a value so load_default() will be run at each call of .get().
P.S. Python in its new versions is actually continuously optimized for speed. So theoretically it could happen that what is explained above may become not always the case due to some optimizations which won't run same function or method call again and again if nothing was changed in relation to previous calls with known return value and known side-effects which could be saved in memory by the optimization algorithm and then reused at next function call. Generally the observed resulting behavior as such should stay the same and the use of optimizations perceivable only by suddenly shorter time required to execute the same function or method with same parameter (what could lead to confusion while benchmarking a function/method for speed as suddenly an otherwise time expensive function call generates almost immediately its results).
P.S. P.S. With deep going time optimizations it could happen that in some future Python versions the to a function/method passed parameter will be evaluated only 'on demand' and the expectation that a function call as parameter won't be executed if the value of this parameter isn't used eventually the right one.
The conclusion from both the P.S. and P.S. P.S. is that it is a good idea not to rely writing own code on the currently probably still true fact that parameter passed to a function/method are evaluated before the function/method is executed.

How does a (python) function process arguments?

I am wondering how a function is processed in a certain language due to the two errors caused by the following two functions in Python.
def fun(a=[0], b: int):
pass
The error reads that SyntaxError: non-default argument follows default argument.
# to show that this function is not trivial
def fun(a:list=[0], b=len(a)):
pass
Araises the error: NameError: name 'a' is not defined
The first example shows obviously that a is processed before b, that is the compiler should know a when it encounters b, but it seems that the compiler doesn't as shown in the second example.
When(or how) is a parameter defined in a function? Or how can I know what's going on under the hood? I mean the related part of the compiler code, may be implemented in context-free grammar.
The parameter a is a local variable that doesn't actually get defined until the function is run; it's not available as a default argument value for b. Such a value needs to exist when the function is defined.
The first example shows obviously that a is processed before b
Not really, at least not in the full meaning of "processed".
The "compiler"/"pre-processor" (non of these terms really apply to Python, but let's let them) can easily check if you use = with an argument before another argument which does not have it by just building the AST tree, without "executing" the actual code.
So, fun(a=0, b) is wrong on the language syntax level (hence the SyntaxError). It is detected before the code is even executed, and its presence stops the parsing and the "compilation" of the code.
fun(a=0, b=a) is logically wrong. The syntax checking mechanism can't/won't detect it. It is only detected by the interpreter when it is trying to execute the code.
def fun(a=0, b):
Here you have a default value for a, but none for b. That means you must provide a value for b when calling the function, but not for a. However, when you call it like so:
fun(42)
Which argument will receive the value? b, since a already has one?
That syntax just raises all sorts of questions, hence you must list all parameters with a default value last. That is the reason for that error.
def fun(a=0, b=a):
Here you're trying to assign the value of a variable a as the default value for b. That is fine in general:
bar = 'baz'
def fun(foo=bar): ...
The default value is evaluated once at function declaration time. The a in b=a does in no way refer to the a in a=0 from the same function declaration. It's looking for a variable a in scope, which it doesn't find.
Arguments are processed in the order they appear, but if you are going to set a default parameter for any argument then they need to come at the end. Proper syntax would be def (b, a=0). In this case b is a required argument, and a is optional which defaults to 0 if nothing is passed.

Cannot understand why python doesn't re-use the parameter when you repeatedly call the same function

I'm watching a tutorial on python and it uses this example:
def add_spam(menu=[]):
menu.append("spam")
return menu
If you call add_spam() repeatedly then the menu list increases in size. This is new to me since I am from a C# background. But OK, fair enough.
To get around this it says that you should set the menu parameter to None but I cannot understand why that works. Here is the code they use:
def add_spam(menu=None):
if menu is None:
menu = []
menu.append('spam')
return menu
If you call it the first time though menu will be set to [], and the second time surely if it's 'remembering' the parameter as in the first example, menu will be [] at that point and therefore it will just append spam to the list as in the first example.
The video neglects any explanation other than you should use an immutable type so I cannot understand how this works.
Edit cos I still don't get it:
What I'm seeing is that the function captures the variable, so it takes menu=[] and stores that secretly somewhere, imagine a private variable called _menu, so that if you call it again it doesn't re-evaluate it just continues to use _menu and thus it grows.
In the second example, I do not understand why it isn't simply taking menu=None and storing that secretly as _menu, so _menu = None and then when you call the 2nd function, it sets _menu=[] and it continues exactly as the first example and it grows.
The fact that None is immutable doesn't seem relevant to me, as you're not doing None=[] you're doing menu=[] so menu then stops being what it was before and becomes [] and you can modify it as you like.
Unless it's some hard-coded feature that if you put None it will not do the copying behaviour then I do not understand the difference between the two.
Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby).
https://docs.python-guide.org/writing/gotchas/
From Python's docs, some more detail:
Default parameter values are evaluated from left to right when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call.
https://docs.python.org/3/reference/compound_stmts.html#function-definitions
Lists are mutable objects, whereas None is immutable, hence the behavior you see.
Setting menu = [] inside the function is just rebinding the name menu to a new object inside the scope of that function call. It doesn't change the original menu object in the scope where the function was defined. Appending to menu within the function, on the other hand, actually modifies the object in memory associated with the name menu in the function definition (because lists are mutable). This object was created when the function was first defined, and is shared between calls of the function.
If you want to understand more, look into the python scoping/namespace behavior, of which default argument sharing is mostly just a subset. But basically,
def foo(bar=None): #executed once
bar = [] #executed every time the function is called

How does 'lambda variable=variable: somefunction()' work? [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
What do lambda function closures capture?
(7 answers)
Closed 6 months ago.
I was searching Stack Overflow for a solution to a problem of mine when I stumbled upon this user submitted solution (to someone else's question):
appsMenu.add_command(label=app, command=openApp(Apps[app]))
Command parameters that call functions need to be wrapped in a lambda, to prevent them from being called right away. Additionally, commands bound within a for loop need the looping variable as a default argument, in order for it to bind the right value each time.
appsMenu.add_command(label=app, command=lambda app=app: openApp(Apps[app]))
As you can see, the user wrote lambda app=app, and for the love of me I cannot figure out why. What does it do?
The lambda expression is used to define a one-line (anonymous) function. Thus, writing
f = lambda x: 2*x
is (more or less) equivalent to
def f(x):
return 2*x
Further, as for normal functions, it is possible to have default arguments also in a lambda function. Thus
k = 2
f = lambda x=k: 2*x
can be called either as f() where x will be the value of k i.e. 2 or as f(3) where x = 3. Now, to make it even more strange, since the lambda function has its own name space, it is possible to exchange the k with x as
x = 2
f = lambda x=x: 2*x
which then means that the default value of f() will be the (outer) x value (i.e. 2) if no other parameter is set.
Thus, the expression
lambda app=app: openApp(Apps[app])
is a function (unnamed) that takes one argument app with the default value of the (outer) variable app. And when called, it will execute the instruction openApp(Apps[app]) where Apps is a global (outer) object.
Now, why would we want that, instead of just writing command=openApp(Apps[app]) when calling .add_command()? The reason is that we want the command to execute not when defining the command, i.e., when calling .add_command(), but at a later time, namely when the menu item is selected.
Thus, to defer to the execution we wrap the execution of openApp(Apps[app]) in a function. This function will then be executed when the menu item is selected. Thereby we will call openApp then, instead of right now (at the time we want to add the command) which would be the case otherwise.
Now, since the lambda function is called with no arguments, it will use its default parameter (outer) app as (inner) app. That makes the whole argument a bit redundant, as the outer variable is directly accessible within the lambda expression. Therefore, it is possible to simplify the expression to lambda: openApp(Apps[app]) i.e. wiht no arguments, making the full line
appsMenu.add_command(label=app, command=lambda: openApp(Apps[app]))
To me, this looks a bit simpler, and is the way I would write it, but conceptually there is no difference between this and the code with default parameters.

Python default arguments and argument names

I was wondering if the 'a=a', and 'b=b' can lead to problems/unexpected behaviour? code works fine in the example.
def add_func(a=2,b=3):
return a+b
a=4
b=5
answer = add_func(a=a, b=b)
Thanks
Not that I know of, although I'd love to be proved wrong.
The formal language reference defines the lexical structure of a function call. The important bit is that it defines a "keyword_item" as identifier "=" expression. Also, here's what it says about how the arguments to the call are interpreted:
If keyword arguments are present, they are first converted to
positional arguments, as follows. First, a list of unfilled slots is
created for the formal parameters. If there are N positional
arguments, they are placed in the first N slots. Next, for each
keyword argument, the identifier is used to determine the
corresponding slot (if the identifier is the same as the first formal
parameter name, the first slot is used, and so on). If the slot is
already filled, a TypeError exception is raised. Otherwise, the value
of the argument is placed in the slot, filling it (even if the
expression is None, it fills the slot). When all arguments have been
processed, the slots that are still unfilled are filled with the
corresponding default value from the function definition.
This lists a few possible scenarios.
In the simple case, like you mentioned, where there are two formal arguments (a and b), and if you specify the function call using keyword parameters like add_func(a=a, b=b), here's what happens:
Two slots are created to hold the parameters.
Since you didn't provide any positional arguments in the call (just keyword arguments), none of the slots are filled initially.
Each of your keyword arguments are analyzed individually, and the identifier of your argument (the "a" in the a= part) is compared with all of the formal parameters names of the function (the names that were given the parameters when the function was defined, in our case, a and b).
When a match occurs, the value of the keyword arguments (in this case, 4!) is used to fill the corresponding slot.
This repeats until all keyword arguments are analyzed. If all slots aren't filled, then Python tries to assign a default value to the remaining slots if one exists. If not, an error is raised.
So, Python treats the "identifier" in a keyword argument completely differently. This is only true for keyword arguments, though; obviously, if you tried something like add_func(b, a), even though your parameters themselves are called b and a, this would not be mapped to the formal parameters in the function; your parameters would be backwards. However, add_func(b=b, a=a) works fine; the positions don't matter as long as they are keyword arguments.
It depends on whether or not the global objects pointed to are mutable or immutable. immutable objects such as your integers are copies when modified, so it's safe. Mutable objects such as lists are modified in-place, and are NOT safe to use this way. Any change to them persists between calls and may (and probably will) cause unexpected behaviors.
This:
a=[]
def f(a=a):
pass
Is the same as:
def f(a=[]):
pass
Which is a known bad practice in Python programs.

Categories

Resources