Search code examples
pythonpython-2.7argument-unpacking

Is there a better (i.e. more Pythonic) way to deal with packing/unpacking function arguments?


I'm writing a method to "adds ticks" to an object. (What "adding ticks" means doesn't matter for my question. You can also ignore the 'batch' variable in my code samples.) All the ticks will have the same width and length, but are at different tick positions, and there can be a variable number of ticks with each call. Currently my method looks like this:

    def addTicks(self, batch, (w,l), *ticks):
        for tick in ticks:
             # create Tick at location tick with width 'w' and length 'l'

This works just fine when I call it like this:

self.addTicks(self.tickBatch, (3,10), 5, 15, 25, 35, 45, 55)

But when I want to add ticks at all the integer positions from 0 to 59 I don't want to type out all the numbers. Currently I'm calling it like this:

self.addTicks(self.tickBatch, (2,7), *[i for i in range(0,60)])

i.e. I create a range, turn it into a list via list comprehension, and then unpack that list when calling so the method can receive it packed as a tuple. It works, but it seems a bit messy and possibly confusing to others reading the code.

My questions are:

  1. Would experienced Python programmers stumble over that construction the way I do?
  2. Is there a better way to achieve the same effect?
  3. Should I change my method's signature? I know I could replace *ticks with tickList, but then every time I called it I'd have to enclose the tick values in a list, and that seems noisier to me.

(This is for Python 2.7)


Edit: user freakish gave me the answer that *range(0,60) works. I can see that it works, but now I realize that I don't know how to describe it. We've "unpacked an iterator", but what does that give us? It's not a list, and it's not a tuple. Do we just say "the element of the iterator? (I can program Python okay, I just have trouble speaking it).


Edit: Okay, I've switched to a list (and stopped unpacking my width,length pair) and my method now looks like this

def addTicks(self, batch, tickDimensions, ticks):

This means I'm now calling it like this:

self.addTicks(self.tickBatch, (2,7), range(0,60))
self.addTicks(self.tickBatch, (3,10), [15, 25, 35, 45, 55, 5])

My initial instinct would be to make a list out of the range, but I guess it's Pythonic to realize that the range is just as good of an iterable as a list so we can pass it in directly?


Solution

  • I would go with option (3) and just store the ticks in a list from the start. In my opinion, the extra [] you would need to type is not a big problem and having a list would have some advantages from the point of view off the Zen of Python:

    • A function receiving a list is simpler than a variadic function.
    • Its more explicit that the ticks are a sequence if you use a list instead of variadic parameters.
    • If you use a list there is only one obvious way to pass the ticks to your function. On the other hand if you use varargs things are less obvious (as the existence of this question shows).

    Another possibility to consider would be to avoid the problem altogether by changing the addTicks function into an addTick one that you call inside a loop. (This might or might not be a good idea, depending on how your function actually works)