13

I'm working on a package that contains subpackages and several modules within each subpackage. Most of the functions need several arguments (~10) that are initiated in the "main" function. Usually, all the arguments are passed to methods that are called from within a function. What is the best way to pass around these parameters ?

Consider the following scenario:

def func(arg1, arg2, ...., argn):
  do something  
  ext_method(arg1, arg3,...argk) # Note: all the arguments are not passed to ext_method
  cleanup  

PS: Function nesting can go quite deep (say around 10-12 functions starting from main function). I considered two alternatives:

  1. Inspect method: Construct a dict of all the arguments in the main function with all the arguments. To call a function: Get its argspec using the method in inspect package, and filter out unnecessary arguments from the "global" arguments dict. It looks something like the below snippet.

    args = {arg1: obj1, arg2:obj2,...}  
    req_args = dict([(k, v) for k, v in args.items() if k in inspect.getargspec(ext_method).args])
    
  2. key-value method: ext_method() is called using argument key-value pairs

    ext_method(arg1=val1, arg3=val3,...,argk=valk)
    

I find the second method to be ineffective because its not easy to change the signature of ext_method() as more features are added to the package. Is method 1 a better alternative ?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Graddy
  • 2,828
  • 5
  • 19
  • 25

2 Answers2

22

More, better options:

  1. Add **kwargs to your functions and ignore extra arguments:

    ex_method(arg1, arg2, **kw)
    
  2. Pass around an object with all arguments as attributes; a namedtuple class would be ideal here. Each function only uses those attributes it needs.

I'd pick the latter. Stop moving around 10 arguments, start passing around just one.

Even better, if all those operations are really coupled to the data those arguments represent, perhaps you should be using a class instead:

class SomeConcept(object):
    def __init__(self, arg1, arg2, arg3, .., argN):
        self.arg1 = arg1
        # ... etc.

    def operation1(self, external_argument):
        # use `self` attributes, return something.

instead of all those separate functions.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I'd also use a namedtuple. Who cares if a few fields aren't used by all methods? There's an example like this in the book Code Complete, I'm pretty sure. – John Zwinck Feb 03 '14 at 12:42
  • 1
    To extend on this: if the data are coupled very tightly together, it might be worth to define an own class (or to extend a `namedtuple` class) and have the said functions as methods of this class. – glglgl Feb 03 '14 at 12:42
9

to pass multiple arguments, use the * operator and ** operator.

def foo(*arg, **karg):
    print arg, karg

The * operator pack all ordered argument in it, and the ** operator packs all unmatched key argument in it.

For example:

def foo(a, *arg):
    return a, arg

foo(1,2,3)
>> (1, [2, 3])
foo(1)
>> (1, [])

For key argument it works the same way,

def foo(a=1, b=2, **kargs):
    return a, b, kargs

foo(2, c=10, b=23)
>> (2, 23, {'c': 10})

Then you can mix them up, those sepcial cases are always at the end of the signature like this:

def foo(a, c, d=4, *arg, **karg):
    return a, c, d, arg, karg

foo(1, 2)
>> (1, 2, 4, [], {})

foo(1,2,3)
>> (1, 2, 3, [], {})

foo(1, 2, 3, 4, 5)
>> (1, 2, 3, [4, 5], {})

foo(1, 2, h=2, f=3, d=4, j=3)
>> (1, 2, 4, [], {'h': 2, 'f': 3, 'j': 3})

foo(1,2,3,4,5, o=2)
>> (1, 2, 3, [4 ,5], {'o': 2})

If you want to pass a dict to a function as argument or a list of argument, you can also use those operators:

a = [1,2,3]
b = {'c': 1, 'd': 3}

def foo(a, b, *arg, **karg):
    return a, b, arg, karg

foo(*a, **b)
>> (1, 2, [3], {'c': 1, 'd': 3})
Loïc Faure-Lacroix
  • 13,220
  • 6
  • 67
  • 99