6

My function expects a list or a tuple as a parameter. It doesn't really care which it is, all it does is pass it to another function that accepts either a list or tuple:

def func(arg): # arg is tuple or list
  another_func(x)
  # do other stuff here

Now I need to modify the function slightly, to process an additional element:

def func(arg): #arg is tuple or list
  another_func(x + ['a'])
  # etc

Unfortunately this is not going to work: if arg is tuple, I must say x + ('a',).

Obviously, I can make it work by coercing arg to list. But it isn't neat.

Is there a better way of doing that? I can't force callers to always pass a tuple, of course, since it simply shifts to work to them.

max
  • 49,282
  • 56
  • 208
  • 355
  • If the other function accepts either than I would send it a tuple as it will be faster for you to work with. Then what's not neat about `tuple(x) + ('a',)`? – aaronasterling Nov 17 '10 at 20:08
  • Are tuples faster in the implementation? – max Nov 17 '10 at 20:40
  • 2
    constructing a tuple from string and numeric literals is faster than constructing a list. More importantly, `tuple(x)` just returns `x` if it's already a tuple whereas `list(x)` copies `x` even if it's already a list. So by using a tuple, you cut most of the work out for half of your input cases. – aaronasterling Nov 17 '10 at 20:49
  • +1: good point about tuple vs list. – max Nov 17 '10 at 21:44

9 Answers9

7

If another_func just wants a iterable you can pass itertools.chain(x,'a') to it.

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
4

What about changing the other function to accept a list of params instead ?

def func(arg): # arg is tuple or list
  another_func('a', *x)
fabrizioM
  • 46,639
  • 15
  • 102
  • 119
  • 1
    What about when another_func needs several list arguments that are affected by the same issue, or if it already has an (unrelated) *x parameter? – max Nov 17 '10 at 21:41
  • @max: The answer's the same. Fix the other function. – S.Lott Nov 17 '10 at 21:57
  • something must be changed in the function's interface – fabrizioM Nov 17 '10 at 22:06
  • @S.Lott: I'm hontestly confused now. If `another_func` takes two lists, why is it a bad design? Say, one list is "list or tuple of columns to print", and another is "list or tuple of rows to exclude from print". How and why should I change its interface? – max Nov 18 '10 at 00:05
  • 1
    @max: A tuple with an indefinite number of elements is a design error. Tuples are usually used for a definite number of elements; the number of elements is usually fixed by the problem domain (x,y) coordinates, (r,g,b) colors, etc. A "variable-sized" tuple is simply inappropriate. It should be a list. Fix the function to work with a list. Fix the functions. "variable length" or "indefinite length" tuples are an error. Don't support them. Fix them. – S.Lott Nov 18 '10 at 00:42
  • @S.Lott: +1 for helpful feedback, thank you. I'm curious about this though, so I've asked a new question: http://stackoverflow.com/questions/4210981/python-variable-length-tuples. – max Nov 18 '10 at 02:03
3

how about:

l = ['a']
l.extend(x)

Edit: Re-reading question, I think this is more what you want (the use of arg and x was a little confusing):

tuple(arg) + ('a',)

As others have said, this is probably not the most efficient way, but it is very clear. If your tuples/lists are small, I might use this over less clear solutions as the performance hit will be negligible. If your lists are large, use the more efficient solutions.

Sean
  • 4,365
  • 1
  • 27
  • 31
  • `extend` returns `None`, as it mutates the list. –  Nov 17 '10 at 20:05
  • @max I originally had "['a'].extend(x)" which is a largely useless statement, because extend returns None, as delnan pointed out – Sean Nov 17 '10 at 21:45
2
def f(*args):
    print args

def a(*args):
    k = list(args)
    k.append('a')
    f(*k)

a(1, 2, 3)

Output:

(1, 2, 3, 'a')
pyfunc
  • 65,343
  • 15
  • 148
  • 136
2

If an iterable is enough you can use itertools.chain, but be aware that if function A (the first one called), also iterates over the iterable after calling B, then you might have problems since iterables cannot be rewinded. In this case you should opt for a sequence or use iterable.tee to make a copy of the iterable:

import itertools as it

def A(iterable):
    iterable, backup = it.tee(iterable)
    res = B(it.chain(iterable, 'a'))
    #do something with res
    for elem in backup:
        #do something with elem

def B(iterable):
   for elem in iterable:
       #do something with elem

Even though itertools.tee isn't really that efficient if B consumes all or most of the iterable, at that point it's simpler to just convert iterable to a tuple or a list.

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • +1 for the clarification about rewinding of iterators. In my case, iterable is enough; I guess for the sequence case I'll just coerce to tuple as suggested by @aaronasterling – max Nov 17 '10 at 21:43
1

My suggestion:

def foo(t):
    bar(list(t) + [other])

This is not very efficient though, you'd be better off passing around mutable things if you're going to be, well, mutating them.

salezica
  • 74,081
  • 25
  • 105
  • 166
1

You can use the type of the iterable passed to the first function to construct what you pass to the second:

from itertools import chain

def func(iterable):
    it = iter(iterable)
    another_func(type(iterable)(chain(it, ('a',))))

def another_func(arg):
    print arg

func((1,2))
# (1, 2, 'a')
func([1,2])
# [1, 2, 'a']
martineau
  • 119,623
  • 25
  • 170
  • 301
0

Have your function accept any iterable. Then use itertools.chain to add whatever sequence you want to the iterable.

from itertools import chain

def func(iterable):
    another_func(chain(iterable, ('a',)))
Steven Rumbalski
  • 44,786
  • 9
  • 89
  • 119
0

I'd say Santiago Lezica's answer of doing

def foo(t):
    bar(list(t) + [other])

is the best because it is the simplest. (no need to import itertools stuff and use much less readable chain calls). But only use it if you expect t to be small. If t can be large you should use one of the other solutions.

JanKanis
  • 6,346
  • 5
  • 38
  • 42