How does a (python) function process arguments? - python

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.

Related

How to make function's arguments optional as a group?

I was just wondering what would be the preferred way in Python to make a group of arguments of a function optional, but only as the whole group.
Meaning: they have to either all be given, or none.
For an example, let's say I want to make a print function, that takes a message string as first positional argument and optionally a file-like object and an encoding as second and third arguments.
Now I want this function to print to stdout if no file is given, and to the file otherwise.
The tricky bit is this: I want this function to always require an encoding to be specified whenever a file is used. And calling this function with an encoding, but no file should also be forbidden.
In Java, I could overload the function and give implementations for both valid variants:
public void print(string message);
public void print(string message, File f, string encoding);
This allows me to call this function in exactly the two ways I want to be possible, with either one or all three arguments.
In Python, I can make single arguments optional by supplying a default value, but I cannot group them together.
def print(msg, file=None, encoding=None)
allows me to call the function by providing a message and none, both or just any one of the other parameters:
print("test")
print("test", file=someFile)
print("test", encoding="utf-8")
print("test", file=someFile, encoding="utf-8")
These are all valid calls to the Python declaration above, even though with my implementation, setting an encoding or file without the other one might make no sense.
I am aware that I could simply check both optionals for an invalid default value and raise an Exception at runtime whenever I find only one is set, but I think that is bad for a couple of reasons:
The Exception is raised only if the invalid call is executed, so it might not occur during testing.
I have no way of telling that both parameters are required as a pair by just looking at the declaration or an auto-generated quick reference without diving into the implementation.
No code analysis tool would be able to warn me about an invalid call.
So is there any better way to syntactically specify that a number of optional arguments are grouped together?
Python is not supporting overloading methods. And there is not a really good way to simulate an overloading design. So best you can do is using if statements with different arguments. Like you do in your method.
Or you can use **kwargs as argument and use if only the desired argument is defined.
def a_very_important_method(**kwargs)
if kwargs["arg1"] is not None:
# logic
if kwargs["arg2"] is not None:
# another logic
a_very_important_method(arg1="value1", arg2="value2")
I mean you could make one parameter expect a tuple as input. Like idk an 2D-array might have a size attribute which requires an input in the shape (x, y). Though that won't save you from checking at runtime whether the supplied values make any sense, does it?
After reading the other answers, it seems to me like the most simple and readable solution would be to write the function with all parameters mandatory and then add a second, "wrapper"- function which has a reduced set of parameters, passes these arguments to the original function on and also gives default values for the other parameters:
def print(msg, file, encoding):
# no default values here, so no parameter is optional
pass
def printout(msg):
# forward the argument and provide default values for the others
print(msg, sys.stdout, "")

Why positional arguments need to be specified before keyword arguments in Python?

I understand passing positional arguments first and then passing the keyword arguments is a rule in python.
And that's why these are wrong:
def fun(x,y):
print(x,y)
fun3(y=4,3)
SyntaxError: positional argument follows keyword argument
and this is wrong too.
def fun2(**kwargs,*args):
File "<stdin>", line 1
def fun2(**kwargs,*args):
^
SyntaxError: invalid syntax
Python strictly checks that I am passing positional arguments first.
What I don't understand. Why?
Isn't this intuitive:
def test(x,y,z):
print(x,y,z)
and then calling the function as
test(z=3,1,2)
should first assign keyword argument z's value 3 and then sequentially assign 1 and 2 to the remaining unassigned variables x and y respectively.
It's not even that python doesn't check if the variable is already assigned or not, because the following gives an error like:
def test2(x,y):
print(x,y)
test2(1,x=1)
TypeError: test2() got multiple values for argument 'x'
Got multiple values for x. So python definitely knows which variable have already received the values. Why can't it then just check for which variables haven't received the values and then assign them those positional argument values sequentially.
I am making notes for my reference and I am stuck while writing anything about the logic behind this behavior of python.
See it seems your argument is valid.
But I like to point out when you said, "Isn't this intuitive:"
Tell me any other object oriented language where you have seen this, and is quite popular.
So the thing is python is popular because it is very easy to adapt to,
I don't know about you but I switched to python recently from C, and there I use to
create functions like length, reverse, sort etc. in python the exact most common names are present of the similar function. It is so easy to adapt, unlike other language where they change name or insert these functions hidden in some utils class like java.
To answering your question.
Like other say it will increase complexity.
And this is why other popular object oriented language didn't adopt it.
And so since its not available in other popular language.
Python didn't adopt it, cause it would make adapting to python difficult. And also due to underlying reason that it would make things complex.
"Isn't this intuitive:" for me, politely, NO, it is not intuitive.
If I pass argument at position 0 in function call, it would expect it to go to position 0 argument in function defination. Cause That's how I did in C. And I believe cause that's how its done in most object oriented language.
Python functions follow the order of the arguments when it is defined and assigns the value to them in the same order when we call the function. Doing what you want to do will confuse it like you tried to do in the second problem (TypeError: test2() got multiple values for argument 'x').
As we get errors by doing so, it is still better but if such feature had been added to python, there maybe some more bugs (especially which won't produce errors). E.g, assigning a value to an argument which is wrong as per our logic.

How to implement function overloading with different argument types in Python?

Suppose python supported function overloading, how would we define overloaded functions for adding two numbers and concatenating two strings?
I just want to know how do we assign a particular data type to a parameter in the function definition or will it depend on the arguments given in the function call.
Would it be like this:
def num(a=int,b=int):
return a+b
def strng(a=str,b=str):
return a+b
python is dynamically typed language. Hence, type checks are not strictly enforced. In defining a function, you can define an optional type for the parameters.
def function(a:int=0, b:int=0): return a + b
The type definition here really does nothing peculiar but probably helps with IDE autocompletion. You can pass in a string and you won't get any errors but it'll definitely throw an error when adding a number to a string.

First lambda capture of local variable always False

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.

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