6

I want to understand when should I use varargs vs a list type in the function parameter in Python 2.7

Suppose I write a function that processes a list of URLs. I could define the function in two different ways:

Option 1:

def process_urls(urls):
  if not isinstance(urls, list) or  isinstance(urls, tuple): 
      raise TypeError("urls should be a list or tuple type")

Option 2:

 def process_urls(*urls):
      # urls is guaranteed to be a tuple

Option 2 guarantees urls to be a tuple but can take in random number of positional arguments which could be garbage such as process_urls(['url1', 'url2'], "this is not a url")

From a programming standpoint, which option is preferred?

3
  • Seems somewhat opinion-based. Why assume that one of the two methods is unequivocally preferable? Commented Mar 29, 2019 at 3:01
  • The advantage of the 1st solution is that you can pass an iterator to it, that's the only difference I see. I think I would favor option 1 in this particular situation. Commented Mar 29, 2019 at 3:02
  • If possible I would rather have process_url(url) and use standard functions (in this case map) to create the batch processing. Commented Mar 29, 2019 at 3:16

2 Answers 2

4

The first, but without the type checking. Type checks kill duck typing. What if the caller wants to pass in a generator, or a set, or other iterable? Don't limit them to just lists and tuples.

Sign up to request clarification or add additional context in comments.

3 Comments

Why not type check for sequences and generators too then? If a function is going to be used/'abused' in so many ways, it would make sense to error-handle and typecheck to make function as 'safe' as possible.
This is a comment, not an answer.
It's an answer. Why do you say it's not?
2

Neither is unequivocally best. Each style has benefits in different situations.

Using a single iterable argument is going to be better most of the time, especially if the caller already has the URLs packed up into a list. If they have a list and needed to use the varargs style, they'd need to call process_urls(*existing_list_of_URLs) whould needlessly unpacks and then repacks the arguments. As John Kugelman suggests in his answer, you should probably not use explicit type checking to enforce the type of the argument, just assume it's an iterable and work from there.

Using a variable argument list might be nicer than requiring a list if your function is mostly going to be called with separate URLs. For instance, maybe the URLs are hard coded like this: process_urls("http://example.com", "https://stackoverflow.com"). Or maybe they're in separate variables, but the specific variable to be used are directly coded in: process_url(primary_url, backup_url).

A final option: Support both approaches! You can specify that your function accepts one or more arguments. If it gets only one, it expects an iterable containing URLs. If it gets more than one argument, it expects each to be a separate URL. Here's what that might look like:

def process_urls(*args):
    if len(args) == 1:
        args = args[0]

    # do stuff with args, which is an iterable of URLs

There's one downside to this, that a single URL string passed by itself will be incorrectly identified as a sequence of URLs, each consisting of a single character from the original string. That's such an awkward failure case, so you might want to explicitly check for it. You could choose to raise an exception, or just accept a single string as an argument as if it was in a container.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.