views:

892

answers:

7

I want to refactor a big Python function into smaller ones. For example, consider this following code snippet:

x = x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9

Of course, this is a trivial example. In practice, the code is more complex. My point is that it contains many local-scope variables that would have to be passed to the extracted function, which could look like:

def mysum(x1, x2, x3, x4, x5, x6, x7, x8, x9):
    x = x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9
    return x

The problem is that pylint would trigger a warning about too many arguments. I could avoid the warning by doing something like:

def mysum(d):
    x1 = d['x1']
    x2 = d['x2']
    ...
    x9 = d['x9']
    x = x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9
    return x

def mybigfunction():
    ...
    d = {}
    d['x1'] = x1
    ...
    d['x9'] = x9
    x = mysum(d)

but this approach loos ugly to me, it requires writing a lot of code that is even redundant.

Is there a better way to do it?

+4  A: 

Simplify or break apart the function so that it doesn't require nine arguments (or ignore pylint, but dodges like the ones you're proposing defeat the purpose of a lint tool).

EDIT: if it's a temporary measure, disable the warning for the particular function in question using a comment as described here: http://lists.logilab.org/pipermail/python-projects/2006-April/000664.html

Later, you can grep for all of the disabled warnings.

Dave
My goal is to break the big function first. Then I could proceed to break the smaller parts further. But I want to avoid this specific pylint warning during the refactoring process, if this is possible.
Anonymous
+8  A: 

You could try using Python's variable arguments feature:

def myfunction(*args):
    for x in args:
        # Do stuff with specific argument here
htw
It's the same as using a list, see below.
Anonymous
+5  A: 

Perhaps you could turn some of the arguments into member variables. If you need that much state a class sounds like a good idea to me.

Brian Rasmussen
Doesn't work if I need to refactor a class method and passed variables are local to the big refactored method and are not used in the whole class.
Anonymous
No, but if you extract a new type instead you may be able to turn some of the state into member variables.
Brian Rasmussen
A: 

Python has some nice functional programming tools that are likely to fit your needs well. Check out lambda functions and map. Also, you're using dicts when it seems like you'd be much better served with lists. For the simple example you provided, try this idiom. Note that map would be better and faster but may not fit your needs:

def mysum(d):
   s = 0  
   for x in d:
        s += x
   return s

def mybigfunction():
   d = (x1, x2, x3, x4, x5, x6, x7, x8, x9)
   return mysum(d)

You mentioned having a lot of local variables, but frankly if you're dealing with lists (or tuples), you should use lists and factor out all those local variables in the long run.

easel
I can't use a list. In my trivial example my passed variables play the same role. But in a complex scenario the variables have different meanings, so replacing their names (that carry a logical meaning) with list items accessed by index would totally destroy the readability of the code.
Anonymous
You'll have to use the dict then. That said, you're not going to be able to clean stuff up much unless you change some of your requirements.Alternatively you could define a class for all this stuff and then push the logic into various class methods. probably cleaner than a massive if-then tree based on a dict, at least!
easel
+10  A: 

First, one of Perlis's epigrams:

"If you have a procedure with 10 parameters, you probably missed some."

Some of the 10 arguments are presumably related. Group them into an object, and pass that instead.

Making an example up, because there's not enough information in the question to answer directly:

class PersonInfo(object):
  def __init__(self, name, age, iq):
    self.name = name
    self.age = age
    self.iq = iq

Then your 10 argument function:

def f(x1, x2, name, x3, iq, x4, age, x5, x6, x7):
  ...

becomes:

def f(personinfo, x1, x2, x3, x4, x5, x6, x7):
  ...

and the caller changes to:

personinfo = PersonInfo(name, age, iq)
result = f(personinfo, x1, x2, x3, x4, x5, x6, x7)
Paul Hankin
+4  A: 

Do you want a better way to pas the arguments or just a way to stop pylint from nagging you about it? If the latter, I seem to recall that you could stop the nagging by putting pylint-controlling comments in your code along the lines of:

#pylint: disable-msg=R0913,R0914

In my opinion, there's nothing inherently wrong with passing a lot of arguments and solutions advocating wrapping them all up in some container argument don't solve any problems (other than the nagging pylint :-).

If you need to pass twenty arguments, then pass them. It may be that this is required because your function is doing too much and a re-factoring could assist there. But that's not a decision we can really make unless we see what the 'real' code is.

paxdiablo
+4  A: 

You can easily change the maximum allowed number of arguments in pylint. Just open your pylintrc file (generate it if you don't already have one) and change:

max-args=5

to:

max-args = 6 # or any value that suits you

From pylint's manual

Specifying all the options suitable for your setup and coding standards can be tedious, so it is possible to use a rc file to specify the default values. Pylint looks for /etc/pylintrc and ~/.pylintrc. The --generate-rcfile option will generate a commented configuration file according to the current configuration on standard output and exit. You can put other options before this one to use them in the configuration, or start with the default values and hand tune the configuration.

Nadia Alramli