Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recommended Python Modules for Function Argument Handling?

There are many Python modules for parsing and coordinating command line options (argparse, getopt, blargs, etc). And Python is blessed with good built-in features/idioms for handling varied function arguments (e.g., default values, *varargs, **keyword_args). But when I read various projects' code for top-level functions, I see notably less discipline and standardization of function arguments than command line arguments.

For simple functions, this isn't an issue; the built-in argument features work great and are more than sufficient. But there are a lot of functionally rich modules whose top-level functions provide lots of different arguments and options (some complementary or exclusive), different modes of operation, defaults, over-rides, etc.--that is, they have argument complexity approaching that of command line arguments. And they seem to largely handle their arguments in ad hoc ways.

Given the number of command line processing modules out there, and how refined they've become over time, I'd expect at least a few modules for simplifying the wrangling of complicated function arguments. But I've searched PyPi, stackoverflow, and Google without success. So...are there function (not command line!) argument handling modules you would recommend?

---update with example---

It's hard to give a truly simple concrete example because the use case doesn't appear until you're dealing with a sophisticated module. But here's a shot at explaining the problem in code: A formatter module with defaults that can be overridden in formatter instantiation, or when the function/method is called. For having only a few options, there's already an awful lot of option-handling verbiage, and the option names are repeated ad nauseam.

defaults = { 'indent':     4,
              'prefix':    None,
              'suffix':    None,
              'name':      'aFormatter',
              'reverse':   False,
              'show_name': False
            }

class Formatter(object):

    def __init__(self, **kwargs):
        self.name    = kwargs.get('name',    defaults['name'])
        self.indent  = kwargs.get('indent',  defaults['indent'])
        self.prefix  = kwargs.get('prefix',  defaults['prefix'])
        self.suffix  = kwargs.get('suffix',  defaults['suffix'])
        self.reverse = kwargs.get('reverse', defaults['reverse'])
        self.show_name = kwargs.get('show_name', defaults['show_name'])

    def show_lower(self, *args, **kwargs):
        indent = kwargs.get('indent', self.indent) or 0
        prefix = kwargs.get('prefix', self.prefix) 
        suffix = kwargs.get('suffix', self.suffix)
        reverse = kwargs.get('reverse', self.reverse)
        show_name = kwargs.get('show_name', self.show_name)

        strings = []
        if show_name:
            strings.append(self.name + ": ")
        if indent:
            strings.append(" " * indent)
        if prefix:
            strings.append(prefix)
        for a in args:
            strings.append(a.upper() if reverse else a.lower())
        if suffix:
            strings.append(suffix)
        print ''.join(strings)

if __name__ == '__main__':
    fmt = Formatter()
    fmt.show_lower("THIS IS GOOD")
    fmt.show_lower("THIS", "IS", "GOOD")
    fmt.show_lower('this IS good', reverse=True)
    fmt.show_lower("something!", show_name=True)

    upper = Formatter(reverse=True)
    upper.show_lower("this is good!")
    upper.show_lower("and so is this!", reverse=False)
like image 885
Jonathan Eunice Avatar asked Feb 23 '12 16:02

Jonathan Eunice


2 Answers

When I first read your question, I thought to myself that you're asking for a band-aid module, and that it doesn't exist because nobody wants to write a module that enables bad design to persist.

But I realized that the situation is more complex than that. The point of creating a module such as the one you describe is to create reusable, general-case code. Now, it may well be that there are some interfaces that are justifiably complex. But those interfaces are precisely the interfaces that probably can't be handled easily by general-case code. They are complex because they address a problem domain with a lot of special cases.

In other words, if an interface really can't be refactored, then it probably requires a lot of custom, special-case code that isn't predictable enough to be worth generalizing in a module. Conversely, if an interface can easily be patched up with a module of the kind you describe, then it probably can also be refactored -- in which case it should be.

like image 135
senderle Avatar answered Oct 04 '22 19:10

senderle


  1. I don't think command line parsing and function argument processing have much in common. The main issue with the command line is that the only available data structure is a flat list of strings, and you don't have an instrument like a function header available to define what each string means. In the header of a Python function, you can give names to each of the parameters, you can accept containers as parameters, you can define default argument values etc. What a command line parsing library does is actually providing for the command line some of the features Python offers for function calls: give names to parameters, assign default values, convert to the desired types etc. In Python, all these features are built-in, so you don't need a library to get to that level of convenience.

  2. Regarding your example, there are numerous ways how this design can be improved by using the features the language offers. You can use default argument values instead of your defaults dictionary, you can encapsulate all the flags in a FormatterConfig class and only pass one argument instead of all those arguments again and again. But let's just assume you want exactly the interface you gave in the example code. One way to achieve this would be the following code:

    class Config(dict):
        def __init__(self, config):
            dict.__init__(self, config)
            self.__dict__ = self
    
    def get_config(kwargs, defaults):
        config = defaults.copy()
        config.update(kwargs)
        return Config(config)
    
    class Formatter(object):
    
        def __init__(self, **kwargs):
            self.config = get_config(kwargs, defaults)
    
        def show_lower(self, *args, **kwargs):
            config = get_config(kwargs, self.config)
    
            strings = []
            if config.show_name:
                strings.append(config.name + ": ")
            strings.append(" " * config.indent)
            if config.prefix:
                strings.append(config.prefix)
            for a in args:
                strings.append(a.upper() if config.reverse else a.lower())
            if config.suffix:
                strings.append(config.suffix)
            print "".join(strings)
    

    Python offers a lot of tools to do this kind of argument handling. So even if we decide not to use some of them (like default arguments), we still can avoid to repeat ourselves too much.

like image 39
Sven Marnach Avatar answered Oct 04 '22 20:10

Sven Marnach