0

I am fairly new to Python3. I have a question with Variable Length Arguments(*args). Based on my understanding the reason why we use the Variable Length Arguments list(or tuple) is that they are useful when you need a function that may have different numbers of arguments. A function like this

def main():
    x = ('meow', 'grrr', 'purr')
    kitten(*x)

def kitten(*args):
    if len(args):
        for s in args:
            print(s)
    else: print('Empty')

if __name__ == '__main__': main()

gives the same output as

def main():
    x = ('meow', 'grrr', 'purr')
    kitten(x)

def kitten(args):
    if len(args):
        for s in args:
            print(s)
    else: print('Empty')

if __name__ == '__main__': main()

output

meow
grrr
purr

I don't see the difference, and why is it better to use Variable Length Arguments(*args). Could someone please explain this to me?

And also, what does the asterisk really do?

x = ('meow', 'grrr', 'purr')
print (*x)

output

meow grrr purr

seems like, it just takes the variables inside the tuple out. And if I do

print (len(*x))
print (type(*x))

it will give me error

print (len(*x))
TypeError: len() takes exactly one argument (3 given)
print(type(*x))
TypeError: type.__new__() argument 2 must be tuple, not str
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Jason
  • 21
  • 1
  • 6
  • What do you expect `len('meow', 'grrr', 'purr')` to do? Why? Similarly for `len`. `*x` is **not an expression**; it is special syntax. – Karl Knechtel Jun 17 '22 at 20:30
  • Does this answer your question? [What does \*\* (double star/asterisk) and \* (star/asterisk) do for parameters?](https://stackoverflow.com/questions/36901/what-does-double-star-asterisk-and-star-asterisk-do-for-parameters) – Karl Knechtel Jun 17 '22 at 20:30
  • I don't understand how there is a question here. You ask, "I don't see the difference, and why is it better to use Variable Length Arguments", but you correctly observe: "they are useful when you need a function that may have different numbers of arguments". **That is the answer to the question**. The example looks pointless because you use `*` for both the call *and* the function declaration. But we also have the ability to use it on only one side. – Karl Knechtel Jun 17 '22 at 20:35
  • As an aside: this is not really a 3.x question. Functions have supported `*args` and `**kwargs` arguments [since at least 2.0 and possibly before](https://docs.python.org/2.0/ref/function.html), and function calls have supported the corresponding syntax [since 2.2](https://docs.python.org/2.2/ref/calls.html). But even then - there is normally no reason to specify 3.x nowadays, because even Python 2.7 has been **officially unsupported for over two years**; it is approximately as obsolete as Windows 7. – Karl Knechtel Jun 17 '22 at 20:44
  • Thank you so much, Karl @KarlKnechtel! The sentence of "len. *x is not an expression; it is special syntax." really made sense to me. – Jason Jun 17 '22 at 23:07

2 Answers2

0

When * is used before a iterable like list or tuple, it expands (unpack) the content of the iterable. So, when you have:

x = ('meow', 'grrr', 'purr')
print(*x)

your output is:

meow grrr purr

Here, x is a tuple, which is iterable, so * expanded the content of x before printing them.

BUT

When, * is used infront of the parameter in a function or method, it allows us to pass variable number of arguments.

In your example,

def main():
x = ('meow', 'grrr', 'purr')
kitten(*x)

def kitten(*args):

you are expanding the values stored in x in line kitten(*x) before passing it to the function kitten. And your function kitten(*args) is defined to accept variable number of arguments. Because of *args, you were able to pass 3 arguments to it, meow grrr purr.

In second example:

def main():
    x = ('meow', 'grrr', 'purr')
    kitten(x)

def kitten(args):

You are passing only one argument to the function kitten(), which is x, and its type is tuple.

Advantage of using *args

If you choose to use list or tuple as in your example.

First, you need to create a list or tuple before calling the function.

Second, if you use list, you have to be cautious of the fact the list is mutable, so if the list is modified inside the function, changes can be seen outside of the function as well.

Example

def main():
    x = [1, 2, 3]
    >>> print(x)
    >>> [1, 2, 3]

    f(x)
    >>> print(x)
    >>> [9, 2, 3]

def f(x):
    x[0] = 9

main()

But, if you use *args, you don't have to worry about any of this stuff.

In your case, you have used tuple, which is immutable so, you don't have to worry about the mutable issue.

BhusalC_Bipin
  • 801
  • 3
  • 12
0

Yes, an example like

def main():
    kitten(*('meow', 'grrr', 'purr'))

def kitten(*args):
    for i in args:
        print(i)

looks pointless, because * is being used on both sides of the equation, and the values are provided directly. (I made it even more clearly pointless by inlining x.)

However, we do not have to do those things. For example, let's try using * only on the definition side:

def main():
    kitten('meow', 'grrr', 'purr')

def kitten(*args):
    for i in args:
        print(i)

Now we don't have to match up a specific number of arguments for the call, and we don't have to create a sequence (tuple, list etc.) locally to wrap the strings up into a single argument.

Alternately, using * only on the calling side, and providing the values indirectly:

def noises():
    return ('meow', 'grrr', 'purr')

def main():
    kitten(*noises())

def kitten(a, b, c):
    print(f'I am a kitten and I like to {a} and {b} and {c}.')

Even though the called function's logic doesn't involve iteration, we can treat our arguments as structured data that could be computed somewhere else, stored in a variable etc. as long as they correctly match the call.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • Thank you, @Karl. Now I can see why is so pointless to have `kitten(*('meow', 'grrr', 'purr'))` Also, the idea of taking out iteration is brilliant. – Jason Jun 17 '22 at 23:15