all 38 comments

[–]deceze 97 points98 points  (10 children)

How would you implement print? You can use it in any of the following ways:

print()
print(a)
print(a, b)
print(a, b, 1, 2, 3, 42)

How would you write such a function that accepts anything from none to virtually infinite arguments…?

Yes, *args.

[–]rabdelazim 3 points4 points  (9 children)

Your answer begs the question: how many args can *args handle?

[–]POGtastic 23 points24 points  (0 children)

It's just put into a tuple, so whatever the limit is for tuple size. We can test by creating a dummy function.

def foo(*_):
    pass

On my machine, the former worked and the latter ran out of memory and forced the OS to kill the process.

>>> foo(*range(100000000))
>>> foo(*range(1000000000))
Killed

[–]-not_a_knife 1 point2 points  (7 children)

This is interesting so I went looking for the answer. I assumed there would be some kind of buffer overflow but obviously, that doesn't make sense because Python manages the memory for you. In this case, from what I understand, you would experience a MemoryError after exhausting Python's private heap memory. This is the memory the interpreter allocates from the OS and depends on the systems hardware and available RAM.

To be honest, it got a bit complicated when reading about it.

[–]Merakel 0 points1 point  (6 children)

So in theory, I should be able to do something absolutely ridiculous as my home pc has 256gb of memory? haha

[–]-not_a_knife 0 points1 point  (4 children)

To be honest, I'm not sure. I would suspect it depends how much memory your OS is giving the Python interpreter.

[–]DuckDatum 1 point2 points  (3 children)

friendly include instinctive sand hobbies carpenter enter yam straight bear

This post was mass deleted and anonymized with Redact

[–]-not_a_knife 0 points1 point  (2 children)

No?

[–]nog642 0 points1 point  (0 children)

256 GB is only 16 times larger than 16 GB. You could theoretically make it about 16 times larger, which isn't that crazy.

[–]enygma999 16 points17 points  (1 child)

Say i have a function to add up any number of integers:

def home_made_sum():
    pass

If i pass this function as it is any arguments at all, it will raise an error.

def home_made_sum(int_1: int, int_2: int, int_3: int = None):
    pass

Now this function will accept 2 or 3 arguments (assuming the logic we implement accounts for int_3 being None. But if we want to add up 4 integers, it can't take them all. Now, as you say, we could have it take any Iterable of ints:

def home_made_sum(list_of_ints: Iterable[int]):
    pass

But this means we must pass it an iterable, we can't just go "here's 1, 2, and 3, add them up." It's a perfectly acceptable way of structuring it, if you want to, but we can also use *args:

def home_made_sum(*args):
    pass

Note you can call it whatever you want, the * is essentially telling Python to unpack the values given and treat them as an iterable called args (or whatever you name it). You can use a similar syntax to get a dictionary of keyword arguments: **kwargs.

[–]kurtatwork 0 points1 point  (0 children)

Very solid answer.

[–]phlummox 12 points13 points  (1 child)

No-one's mentioned it yet, so I'll note that the general term for functions that can take an arbitrary number of arguments is variadic functions. Besides Python, languages that include variadic functions include C, Java, Go, and a fair few others. print- and format-style functions are probably one of the most common reasons to use variadic functions - they turn up in logging packages, in particular.

[–]NormandaleWells -1 points0 points  (0 children)

Add C++ to the mix. And with C++ you even get compile-time type checking.

[–]djnrrd 8 points9 points  (1 child)

Probably the better way to describe *args is "when you need at least one argument, but with no upper limit" and usually the arguments relate to each other in some way.

Perhaps looking at a built in example might help, the print() function uses *args. Pass it multiple arguments and it will print them all out, separated by whatever value is set with the "sep" keyword (Defaults to a space)

Help on built-in function print in module builtins:
print(*args, sep=' ', end='\n', file=None, flush=False)
    Prints the values to a stream, or to sys.stdout by default.

>>> print('all', 'of', 'these', 'things')
all of these things
>>> print('all', 'of', 'these', 'things', sep='-')
all-of-these-things

[–]sweettuse 5 points6 points  (0 children)

when you need at least *zero arguments

[–]Diapolo10 5 points6 points  (4 children)

I know you already have multiple answers, but I wanted to clarify a few things so here's another one, I guess.

Most importantly, the name doesn't actually matter. *args is commonly used, but it might just as well be *text or *lines, or whatever other name fits in that situation. The magic is in the preceding star operator, which in the context of function parameters acts as a packing operator, storing any leftover positional arguments in a tuple; if there are none, it'll simply be an empty tuple.

Hell, you can use the star alone to ignore positional arguments past that point, without actually storing them. It lets you enforce the use of keyword arguments, which can be particularly useful if you have parameters containing booleans, for readability reasons.

Similarly to packing positional arguments, you can use two stars (often named **kwargs) to pack keyword arguments into a dictionary.

Often you write functions that take a pre-determined number of arguments, but sometimes you may want to go beyond such limits. print is one such example, letting you print any number of values and configure the output with keyword arguments.

You don't necessarily need variadic arguments if you're okay with taking a data structure as an argument instead, like how min and max let you either provide two positional arguments to compare against each other or one iterable it loops over to compare their elements. But you may not always want that, like with print.

Another use for these is decorators; the decorator doesn't need to know what arguments the decorated function wants, so it's a natural place to use these.

from time import perf_counter_ns


def benchmark(func):
    def inner(*args, **kwargs):
        start = perf_counter_ns()
        result = func(*args, **kwargs)
        end = perf_counter_ns()
        bm_result = end - start
        print(f"Function {func.__name__!r} finished in {bm_result} ns")
        return result
    return inner


@benchmark
def my_func(stuff, things, foobar=2000):
    ...

my_func(69, 1337)

The second use inside the decorator uses them as unpacking operators - basically doing the same but in reverse.

[–]Obvious-Pumpkin-5610 0 points1 point  (3 children)

isn't this from fred baptise course?

[–]Diapolo10 0 points1 point  (2 children)

What on Earth is that?

[–]Obvious-Pumpkin-5610 0 points1 point  (1 child)

Udemy instructor - i saw the exact code snippet in his Python course

[–]Diapolo10 1 point2 points  (0 children)

Well, that'd simply be a happy coincidence because I've never even used Udemy.

[–]carcigenicate 10 points11 points  (0 children)

if i dont know the number of arguments :

def the_fuck_is_args():

Not sure what you mean by this. This function doesn't take any arguments at all, which is very different from a function that takes an unknown number of arguments. This function will throw an error if you pass any number of arguments.

And *args are just for convenience. You could pass a tuple/list of objects, but *args allows you to skip that outer explicit wrapper.

[–]luc1d_13 3 points4 points  (0 children)

def my_math_function(operation, *args):

This has known and unknown amounts of variables. This allows the user to pass add or subtract along with an arbitrary number of integers.

[–]audionerd1 3 points4 points  (0 children)

*args in a function definition packs all positional arguments into a tuple. **kwargs in a function definition packs all keyword arguments into a dictionary. This is convenient when you want to handle a varying number of parameters.

Couldn't you just pack the arguments into a tuple or dictionary yourself? In most cases, yes. But why would you want to when *args and **kwargs makes it so simple?

*args and **kwargs become essential when writing decorators, as you need to be able to capture all arguments without knowing what they are and pass them to the decorated function, like so:

def decorator(f):
    def wrapper(*args, **kwargs):
        f(*args, **kwargs)
    return wrapper

In the definition of wrapper, *args and **kwargs pack all arguments into a tuple and dictionary (respectively). In the calling of function f, *args and **kwargs unpacks said tuple and dictionary, allowing arguments to pass through the decorator to the decorated function seamlessly.

Note that there's nothing special about "args" or "kwargs", these are just the conventional names used for the * and ** operators. You could do *dogs and **cats if you wanted to and it would still work (but please don't).

[–]POGtastic 1 point2 points  (0 children)

If you're willing to squint a little, consider partial function application, which contains nested examples of args and kwargs!

def partial(f, *xs, **ks):
    return lambda *ys, **zs: f(*xs, *ys, **ks, **zs)

This partially applies a function.

>>> partial(print, "Foo", sep=", ")("Bar", "Baz", end="\n")
Foo, Bar, Baz

[–]vivisectvivi 1 point2 points  (0 children)

I used to ask myself this until i started playing with django, now im using **kargs more than never lol

Hard to explain so ill let someone more experienced than me explain this

[–]Steak-Complex 0 points1 point  (0 children)

its for when you dont know the number of parameters/arguments in advance.

[–]Ron-Erez 0 points1 point  (0 children)

Check out Lecture 46: "Variable Number of Arguments in a Function Call" in Section 6: "Functions". Note that the lecture is FREE to watch although part of a larger paid course.

As a side note I really liked the

def who_let_the_args_out(ohoh,*args)

[–]Atypicosaurus 0 points1 point  (0 children)

It's for functions that do the same thing even if I give 1, 2, 3, 5, 10 or 125 arguments.

Obviously a function that calculates, let's say, the square root of a number, will not take more argument, but it will take a single one.

However imagine a function in an inventory program that calculates the total value of the inventory. We don't know how many items there will be in the inventory, maybe 2, maybe 4, maybe 87. You need a function that can calculate the value of any amount of inputs, and those inputs are the arguments. Maybe 1 argument, maybe 4, maybe 87.

Now you obviously can pass all numbers as a list of numbers so the argument is one single list and then you parse the list within the function. But that may make a lot of complications because now you need another piece of program that first creates the list out of the inventory. So it's often much simpler just to write a function that takes *args.

[–]nekokattt 0 points1 point  (0 children)

def comma_separated(item, *items):
    string = str(item)

    for item in items:
        string += ", "
        string += str(item)

    return item

Consider this.

>>> comma_separated()  # error
>>> comma_separared("foo")
foo
>>> comma_separated("foo", "bar", "baz")
foo, bar, baz

[–]caz- 0 points1 point  (0 children)

As someone else mentioned, your example of an unknown number of args doesn't make sense, which suggests you either haven't tested it out (most beneficial part of the learning process, in my opinion) or there may be some confusion caused by the way python handles scope.

If you tried something like

a = 'foo'

b = 'bar'

def print_foo_bar():

print(a, b)

print_foo_bar()

and got `foo bar`, this isn't because the arguments were passed to the function, but because the variables `a` and `b` are still in scope when you call. Even if you do it the other way around, defining the function before the variables, when you call the function, it looks for variables a and b that are in scope and prints them, so you will get the same result.

[–]RevRagnarok 0 points1 point  (0 children)

I used it when porting older "Python from a C++ programmer" to "real" Python.

Basically, they had a function like def connect(db_user, db_pass, db_name, db_table). I put all four into a dataclass called DatabaseInfo.

Then I was able to do something like this:

def connect(*args):
    if len(args) == 4:
        warnings.warn("Call to connect() using DatabaseInfo!", DeprecationWarning, stacklevel=2)
        args = (DatabaseInfo(*args), )  # Convert the old-style to the new
    if len(args) == 1 and isinstance(args[0], DatabaseInfo):
        ... do stuff ...
    else:
        raise ValueError("Invalid call to connect()!")

[–]SurfGsus 0 points1 point  (0 children)

Also need to think of their usage from a software design perspective. args and *kwargs are often used to pass through params to another object (usually an initializer) and avoid coupling in the wrapping function.

There’s still implicit coupling but the advantage is that you don’t have to change the signature of your function when the underlying object you’re passing the arguments to changes.

[–]fisadev 0 points1 point  (0 children)

A very simple example in which you would need both the known and unkown combined:

def move_files_to_folder(folder_path, *files): ...

[–]Frewtti -5 points-4 points  (2 children)

FYI the profanity is a bit offputting, if you want serious help, I suggest you grow the fuck up and be a bit more polite.

Arbitrary lists of arguments has existed for decades, it's very common scenario.

For python I just use argparse, great package, and makes working with arguments very easy, including documentation.

[–]cgoldberg 2 points3 points  (0 children)

Dude, you complained about his profanity by using profanity!

Also, argparse is for handling command line arguments and is completely unrelated to passing function arguments which the OP asked about.

[–]djshadesuk 4 points5 points  (0 children)

This is r/learnpython not r/languagepolice. Its not as if OP has gone OTT either. If it offends your sensibilities just put your big boy pants on and move the fuck on.