Sunday, March 20, 2011

Loop function parameters for sanity check

I have a Python function in which I am doing some sanitisation of the input parameters:

def func(param1, param2, param3):
    param1 = param1 or ''
    param2 = param2 or ''
    param3 = param3 or ''

This caters for the arguments being passed as None rather than empty strings. Is there an easier/more concise way to loop round the function parameters to apply such an expression to all of them. My actual function has nine parameters.

From stackoverflow
  • You probably want something more like this:

    def func(param1='', param2='', param3=''):
    

    It's far more concise and idiomatic.

    See http://docs.python.org/tutorial/controlflow.html#default-argument-values

    Mat : I have some default argument values in my real function, but I am still concerned where parameters are passed with a value of 'None'. For example, I could call func(None, None).
    mipadi : That won't do what he wants -- you could still pass 'None' as a parameter.
    Kamil Kisiel : Right, sorry, I misunderstood the question. In that case, I recommend Kiv's answer below.
  • def func(x='', y='', z='hooray!'):
        print x, y, z
    
    In [2]: f('test')
    test  hooray!
    
    In [3]: f('test', 'and')
    test and hooray!
    
    In [4]: f('test', 'and', 'done!')
    test and done!
    
  • You could do some list manipulation:

    def func(param1, param2, param3):
        param1, param2, param3 = map(lambda x: x or '', (param1, param2, param3))
    

    but I'm not sure that's better than just writing out the nine lines, since once you get to nine parameters, that's a heinously long line.

    You could change the declaration of the function:

    def func(*args):
        param1, param2, param3 = map(lambda x: x or '', args)
    

    but then you lose the documentation that comes from having real parameter names, as well as the possibility of changing the defaults, etc. And you still have a pretty fugly line there to unpack them.

    I say write out the nine lines, or change the function to have fewer parameters: nine is kind of a lot anyway!

  • This looks like a good job for a decorator. How about this:

    def sanitized(func):
        def sfunc(*args, **kwds):
            return func(*[arg or '' for arg in args],
                        **dict((k, v or '') for k,v in kwds.iteritems()))
        sfunc.func_name = func.func_name
        sfunc.func_doc = func.func_doc
        return sfunc
    

    You would use this on your function like so:

    @sanitized
    def func(param1, param2, param3):
        print param1, param2, param3
    

    Then the parameters will be replaced by the empty string if they are false:

    >>> func('foo', None, 'spam')
    foo  spam
    

    (Note that this will still mess up the function signature as Ned Batchelder points out in his answer. To fix that you could use Michele Simionato's decorator module-- I think you'd just need to add a @decorator before the definition of sanitized)

    Kiv : Does this work with keyword arguments though?
    Kiv : I think you also need to pass along the kwargs too: **dict((k, arg or '') for k, arg in kwargs.items()))
    dF : Fixed to support kwargs, thanks Kiv
    Kiv : Yes, this is by far the best solution (other than refactoring the original code to not require it.)
    Autoplectic : you should probably change the `arg or ''` to `'' if arg is None else arg` or something similar just in case arg is 0 or {} or something non-None.

0 comments:

Post a Comment