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
Related
What is the difference between using *args and getting a list as input for having a dynamic number of inputs for a function.
Do these two ways have any advantages in comparison with the other?
It's just a difference in what type of arguments the function expects. They are fairly interchangeable; which you choose is mostly a matter of preference (though see the exceptions at the end of the answer).
Given the following definitions:
# Separate arguments
def foo(*args):
pass
# Single sequence of arguments
def bar(list_of_args):
pass
x = [1, 2, 3]
These two calls are equivalent:
foo(1, 2, 3)
foo(*x) # Unpack a list before calling
And these two calls are equivalent:
bar(x)
bar([1, 2, 3]) # Explicitly pack individual arguments
You mentioned **args in your question. The name of the parameter isn't really important; the special behavior is indicated by a parameter name beginning with one or two *s. One * indicates arbitrary positional arguments, as shown above. Two *s indicates arbitrary keyword arguments. Conventionally, the two most common names are *args and **kwargs.
A brief example similar to the above:
# Arbitrary keyword arguments
def foo(**kwargs):
pass
# Single dict d
def bar(d):
pass
d = {'a': 1, 'b': 2}
# Equivalent
foo(*d)
foo(a=1, b=2)
# Equivalent
bar(d)
bar(dict(a=1, b=2))
Exception: if the function actually expects to mutate a value, you can't replace that effectively with a */** parameter. Example:
def foo(**kwargs):
kwargs['foo'] = 3
This adds a mapping of 'foo' to 3 to the dictionary, but it is a temporary dictionary created just for the call to foo. Nothing outside the function has a reference to that dictionary, so the update doesn't have any effect after foo returns.
Similar logic applies to *args, but since args is a tuple, and tuples are immutable, there's little you could do with the argument anyway. That said, if the function really expects a list, and not just an arbitrary sequence of arguments, you have to define your function to handle that.
def foo(some_list): # can't use *args here
some_list.append(3)
You can't effectively define that as
def foo(*args):
args.append(3)
because foo receives a temporary tuple, not a list object that
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()
The easiest way to show this is an example:
def hat(a, b):
return a+b
def cat(c, d):
return c % 2, d % 2
If I type cat(4,5) I properly get (0,1). However if I try hat(cat(4,5)) instead of getting 1, I get an error saying hat needs more values. In my real function I am dealing with a lot more than 2 arguments, so what is the proper way to fix this
The cat function actually returns a tuple, when used as an argument to hat, it can only match the tuple against the first argument a. You need to expand the tuple into multiple arguments.
Try a call like:
hat(*cat(4,5))
The * will expand the tuple and bind against all the arguments.
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.
I'm currently learning Python, and I have problems modifying this function to work as I would like. This is a recursive function that finds gcd from two integers. data is a tuple with two integers. How to modify this recursive function to work with one parameter?
def gcd(data):
a, b = data
if b == 0: return a
return gcd(b, a % b)
If i execute it like this, i get
TypeError: checkio() takes 1 positional argument but 2 were given
If I try to gather arguments by defining def gcd(*data):, I get
ValueError: need more than 1 value to unpack.
Is this possible at all ?
Your function is expecting a single variable (a tuple), but you are passing two variables, try this:
return gcd((b, a % b))
Sure, you just need to be consistent. Either define gcd to take two parameters:
def gcd(a, b):
or wrap the values in a tuple when you call it:
return gcd((b, a % b))
I think you should just be able to put an extra pair of parentheses around your arguments, like so:
return gcd((b, a % b))
This will make your two positional arguments into a tuple passed as a single positional argument.
Alternatively you could drop the unpacking and just accept two positional arguments instead of a single tuple in the first place.
Passing a literal tuple as a parameter requires another set of parentheses to make clear that it is not 2 separate parameters:
return gcd((b, a % b))
Alternatively, the tuple could be assigned to a separate name first (with or without parentheses):
next_data = b, a % b
return gcd(next_data)
gcd(*data) would work, but you would also need to change your initial call to pass individual parameters as well, in the form of either gcd(a, b) or gcd(*data).