Question

I've been looking at the source code for an open source package that I'm using. Nearly every function uses *args instead of named arguments. I'm finding it hard to follow and use the code, because every time I want to call a function I have to go back, pick through the source code, and identify what the arguments should be, and what order they should be in. The question I have is this: Is there a compelling reason to use *args in every function, or is this an abuse of the concept? Thanks, -b

Was it helpful?

Solution

This might be a personal reasoning but I only use *args and **kwargs when I have a lot of optional fields where some fields are only used in a certain context.

The only other occasion I used args is when I was building an XML RPC client for a cloud API. Since I was just passing parameters to an underlying layer and since the function where generated dynamically, I had no other choices but to use *args as I had no way to know all the parameters in advance.

In most case, you won't even need it. I consider it to be mostly laziness than anything else.

Some people who comes from Java and C# might use this as a replacement for "params", but there are so many way to pass optional parameters in python.

And I agree that even if you use *args, you should have a very good documentation.

OTHER TIPS

Using *args in every function is a bad idea because it can mask errors in your code (calling a function with the wrong number of arguments). I suppose the rule of thumb should be to only use *args if you need *args.

In the Zen of Python, we are told to prefer explicit over implicit. Explicit arguments should be used whenever possible.

When there's no compelling reason to use *args, it's an abuse of the concept. Usually there are good names for arguments, which help comprehension. Even when there aren't, (x, y, z) tells you more than (*args).

And beyond making code more readable, it also helps to catch errors (e.g., if you call a (x, y, z) function with (2, 3) you'll get an error at the call, rather than deep inside the function), it's usually more concise, and it can even be more efficient.

But there are sometimes compelling reasons for widespread use of *args.

For example, if you're wrapping a lower-level (C or otherwise) module and want to do perfect forwarding, it's easier with *args. Even more so if you're automatically generating the wrapper code rather than writing it manually. Of course this is still a tradeoff—it's much easier for the developer of the wrapper module, but more difficult for the users—but sometimes the tradeoff is worth taking.

Without knowing the particular package you're referring to, it's impossible to guess whether it's a compelling use case or an abuse.

If you're wrapping an unknown function (the functions returned by decorators often do this), then you often need to use (*args, **kwargs).

Some class hierarchies use (*args, **kwargs) in methods that can need different signatures at different classes in the hierarchies (__init__ is a prime culprit). It's really helpful if you can avoid that, but can be necessary to work with multiple inheritance hierarchies in a sane manner (or as sane as is possible with multiple inheritance, at least).

I sometimes end up using **kwargs when I have a large number of optional arguments, but this requires a lot of documentation.

In a function that's consuming the *args itself (rather than passing them to some other function with an unknown signature, as in the decorator or class inheritance cases), then I tend to think that *args should almost never be used except to mean "zero or more of the same kind of thing". And in that case you should be naming it *websites or *spaceships or *watermelons, not *args. A bunch of unrelated parameters shouldn't be squashed into *args. Even worse would be for the function to use *args to take "an x, a y, and a z, or else an x and a z", where the second parameter does different things depending on how many parameters are passed. At that point they should clearly all have names and defaults (even if it's just the standard None default then see in the function which ones are non-None pattern) and be passed by keyword rather than by position.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top