Clarifications on Python tuple unpacking - python

It's understood that unpacking of a tuple can happen only between tuples/list
so
x,*y = (1,2,3,4)
is valid. However, if we try to do the unpacking on a single variable
*x = (1,2,3,4,5)
we get an error as x is not a list/tuple hence unpacking cannot occur. If that is the case then
how can we use *args to have multiple parameters in function
def max(* args):
for x in args:
print(x)
So here if I call max(1,2,3,4). Shouldn't we get an error coz *args is not a tuple therefore we can't do unpacking?

The catch is that the brackets of a parameter list always enclose a tuple.
They are brackets you could not omit. So they are not mixed up with operator-priority-brackets
By the way, fun fact:
write (NOTE THE COMMA AFTER THE x)
*x, = (1,2,3,4,5)
Then it works, just like you would neet to add a comma in a bracket to make it a tuple.
like (1) is no tuple, but (1,) is

Related

python tuple ending with comma difference

What is the difference between
("a")
and
("a",)
I noticed that for example mysql wrapper parameters formatting doesn't work with the first case, it must end with comma.
cursorA.execute(query, (url,))
if you write only one element in parentheses (), the parentheses () are ignored and not considered a tuple.
x = ("a")
print(type(x))
output: str
to generate a one-element tuple, a comma , is needed at the end.
x = ("a", )
print(type(x))
ouput : tuple
The First will create a string and the second will make a tuple. That's actually the difference between making a tuple and a string between two parentheses.
when using only parentheses, you are not creating a tuple, because the interpreter treats this as increasing operator precedence (just like parentheses in mathematics), and if you put a comma, the interpreter will understand that we are trying to create a tuple with one element, and do not increase priority
This code below which is not "Tuple" is :
x = ("a") # Is not Tuple
Same as this code below:
x = "a"
While this code below is "Tuple":
("a",) # Is Tuple

i want to return two values from one lambda and assign to the other one but i got error

I had tried to return values to a and b by using the below method
(lambda a,b:print(a,b))((lambda x:(x,[int(i)**len(x) for i in x]))('153'))
but this shows error,i need some help to fix this.
TypeError: <lambda>() missing 1 required positional argument: 'b'
The inner function returns a single tuple of two values, but the outer function expects two separate values. Use *-unpacking to have each value of the tuple passed as a separate parameter:
# v takes two parameters v provides one tuple of two values
(lambda a,b:print(a,b))(*(lambda x:(x,[int(i)**len(x) for i in x]))('153'))
# ^ unpack operator
Note that print already takes positional arguments – (lambda a,b:print(a,b)) can be replaced by just print. Also, Python3.8 introduces the := assignment operator, which can often be used instead of a lambda to emulate let expressions. This shortens the expression significantly:
# v print takes multiple arguments
print(*(x := '153', [int(i)**len(x) for i in x]))
# ^ assignment operator binds in current scope
#MisterMiyagi posted the correct answer using the given structure. However, I can't think of a case where using two lambdas in the way you did would be useful. Defining a function would make the code much more readable:
def print_values(string):
values = [int(i)**len(string) for i in string]
print(string, values)
print_values("153")
Or if you want it shorter:
def print_values(string):
print(string, [int(i)**len(string) for i in string])
print_values("153")

How to pass tuple and variable as args

I need to pass a large tuple and a single variable into a threaded task as arguments.
excelbtn_text.set("Outputting...")
excelClass = excelCL()
excel_thread = threading.Thread(target=excelClass.excelOut, args=(dcf_data_tuple, excelbtn_text))
excel_thread.daemon = True
excel_thread.start()
However I receive an error, TypeError: excelOut() missing 242 required positional arguments. Is there anyway I can get past this problem?
The error never occurred while I was only passing the tuple as an argument.
First, to pass a tuple and another value, you can just make another tuple with two members—the big tuple, and the other value. Exactly as you're doing:
args=(dcf_data_tuple, excelbtn_text)
But the arguments you pass have to match the method's function definition. Passing a valid tuple of 2 values as the arguments for a method doesn't work unless that method takes 2 parameters.
To resolve your confusion, first, this does not mean what you think it does:
args=(dcf_data_tuple)
Parentheses do not create a tuple; commas create a tuple. In other words, (2) is not a 1-element tuple containing the number 2, it's just the number 2. And (dct_data_tuple) is not a 1-element tuple containing the tuple dct_data_tuple, it's just dct_data_tuple.
So, the function definition for excelClass.excelOut is presumably taking not a single giant tuple as a parameter, but rather hundreds of separate parameters.
This is a bizarre design, but it's not actually illegal.
And that matches the exception you're getting: when you pass it 2 arguments (the first of which is a giant tuple), rather than hundreds arguments, it complains that you're missing 242 positional arguments:
TypeError: excelOut() missing 242 required positional arguments
The simplest way to fix this is to give excelOut a reasonable signature that matches what you want to pass it:
def excelOut(self, data_tuple, text):
# do stuff here
If you for some reason can't change its definition, then you have to look at what the definition is, and try to match it. If, for example, it looks like this:
def excelOut(self, data0, data1, … hundreds more, …, text):
… then you have to call it like this:
args=dcf_data_tuple + (excelbtn_text,)
Notice the comma at the end. That means (excelbtn_text,) is a 1-element tuple. And then we add the giant tuple to the 1-element tuple and get back a giant-plus-1-element tuple, which now matches the method's parameters.
If I understood your problem, you can add an asterisk before your tuple to pass the arguments unwrapped:
excelbtn_text.set("Outputting...")
excelClass = excelCL()
excel_thread = threading.Thread(target=excelClass.excelOut, args=(*dcf_data_tuple, excelbtn_text))
excel_thread.daemon = True
excel_thread.start()

How to understand variable-length argument in python function?

I want to write my own sum function to get the sum of a variable-length argument.
def myadd(*tuple):
sum=0
for element in tuple:
sum=sum+element
return(sum)
call method 1:
myadd(*(1,2,3,4))
It is the most formal way to call the function.no problem here.
call method 2:
myadd(1,2,3,4)
It also can get the result,why?
call method 3:
myadd((1,2,3,4))
error TypeError: unsupported operand type(s) for +: 'int' and 'tuple'.
In my point of view,call 2 and call 3 can not be accepted by python,there are no * operator in the position of arguments?can you tell me the pricinple of the operation on python function ?
You're mixing up variable-argument parameters and argument unpacking. This is a common mistake for beginners, because they both use the same * for syntax, and they're not completely unrelated… but they're not nearly as closely related as you think.
These two calls do the exact same thing:
myadd(*(1,2,3,4))
myadd(1, 2, 3, 4)
What the * means here is "take the following iterable, and unpack it into a bunch of separate arguments.
It doesn't matter whether the function you're calling was defined as f(*args), f(a, b, c, d), or f(a, b, *args), you're passing it 4 arguments.
This means method 1 is not "the most formal way to call the function"; in fact, it's just an obfuscated version of method 2.
This, on the other hand, does not do the same thing:
myadd((1, 2, 3, 4))
That passes a single argument, which happens to be a tuple.
So, your function is defined like this:
def myadd(*tuple):
This means whatever it arguments it's passed, no matter how they're passed (except for keyword arguments, but let's ignore that for the moment), they're going to be tossed into a list named tuple. So, let's look at your three cases.
In the first case, you're passing 4 arguments, all of which are integers. So, tuple gets a list of 4 integers. When you iterate over that list, each member is an integer, so you can add them up with no problem.
In the second case—which, again, is exactly the same—you're passing 4 integers, so tuple gets a list of 4 integers.
In the third case, you're passing 1 argument, which is a tuple, so tuple gets a list of 1 tuple. When you iterate over that list, each member is a tuple, and you can't add that to a number.
For more details, see Arguments and parameters, which has links to all the useful places to look in the docs, and hopefully a readable overview.
You are passing the whole tuple as one argument, and tuples cannot be added to numbers. If you want to pass all the tuple elements as individual arguments, use the * operator:
myadd(*x)
def myadd(x):
sum=0
for element in x:
sum=sum+element
return(sum)
x=(1,2,3)
print myadd(x)
output
6

Create tuple if not already one

How do I create a simple tuple containing a variable of any time without creating a tuple of tuples? For example a function can accept either an int or a tuple of ints. Inside the function I want to make sure that the variable is in fact a tuple.
So far I could think of
a = tuple(a)
and
a = (a,)
However the first one doesnt work if a is not iterable and the second one creates a tuple of a tuple if a is already one.
((1,2),)
I feel like I should be able to do that without checking the type first... What am I missing?
You can use exception handling; try calling iter() on the object, for example
try:
iter(a)
except TypeError:
# not an iterable, assume a single value
a = (a,)
If you were planning to iterate over the tuple to handle values anyway, you just store the output of the iter() call and use it directly:
try:
a = iter(a)
except TypeError:
a = (a,)
for elem in a:
You can also make your function signature accept multiple parameters:
def foobar(*a):
for elem in a:
Now a is always a tuple, but to pass in a tuple you'd have to call it with:
sometuple = (1, 2, 3)
foobar(*sometuple)
while a single value is passed in as:
foobar(singlevalue)
You can use a lambda function:
a = (lambda x: x if type(x) is tuple else (x,))(a)
Perhaps more verbose than what you wanted, but it allows you to keep it all in one line.
Unfortunately, there's really no way to do it without checking the type of the argument. When you try to construct a tuple from another tuple - e.g. t = (tup,) - it's treated as a generic Python object, i.e. it doesn't "know" that it's a tuple. In order for it to auto-expand the tuple, it will have to check it's type first.
A non-iterable object would also have to be identified, because it doesn't contain the necessary methods to iterate over it's elements. In other words, a non-iterable - e.g. a scalar - isn't just an iterable with a single element. They're completely different object types. It can be placed in a tuple, but not converted to one.
When you use the tuple function to create a tuple, it just treats the input as a generic iterable. It doesn't know that it's a tuple.
So whether the type check is done internally by tuple(), or by the calling function, it will still have to be done.

Categories