Replacing 'if __name__ == __main__' with decorator (was: Double underscores -- ugly?)

grflanagan at gmail.com grflanagan at gmail.com
Thu Feb 21 04:39:26 EST 2008


On Feb 19, 10:01 am, Duncan Booth <duncan.bo... at invalid.invalid>
wrote:
> Berwyn <berh... at gmail.com> wrote:
> >> Is it just me that thinks "__init__" is rather ugly? Not to mention
> >> "if __name__ == '__main__': ..."?
>
> > That ugliness has long been my biggest bugbear with python, too.  The
> > __name__ == '__main__' thing is something I always have to look up,
> > every time I use it, too ... awkward.
>
> > I'd settle for:
>
> >     hidden def init(self):          # which could be extended to work
> > for everything "hidden x=3"
> >     ...
>
> > And for __name__ == '__main__' how about:
>
> >     if sys.main():
> >         ...
>
> Or even:
>
>   @hidden
>   def init(self): ...
>
> @main
> def mymainfunc():
>    ...
>
> The first of those probably wants some metaclass support to make it work
> cleanly, but here's a sample implementation for the second one:
>
> import sys, atexit
> def main(f):
>     """Decorator for main function"""
>     def runner():
>         sys.exit(f())
>     if f.func_globals['__name__']=='__main__':
>         atexit.register(runner)
>     return f
>
> print "define mymainfunc"
> @main
> def mymainfunc(args=sys.argv):
>     print "Got args", args
>     return 3
> print "end of script"
>
> If you have multiple functions marked as main that will run them in
> reverse order, so it might be better to put them on a list and use a
> single runner to clear the list. Also, I have no idea what happens to
> the exit code if you use this decorator more than once.
>
> BTW, should anyone be wondering, you can still use atexit inside a
> function called from atexit and any registered functions are then called
> when the first one returns.

I liked the idea, thanks. But I also wanted to be able to pass options
to the decorator, for example optparser.OptionParser instances, so
(after wrestling with nested functions for half a day!) I ended up
with the class below, FWIW.

Getting the interplay between __new__, __init__ and __call__ was
somewhat trial-and-error, and maybe I just painted myself into a
corner, but (slightly academic question) is it the case that if you
want to be able to use a decorator both with and without options, ie.
like this:

@mainmethod
def main(...)

and like this:

@mainmethod(parser=myparser)
def main(...)

then you cannot use that decorator for a function that expects or
allows a function as its first argument? Because how and when can you
decide whether that function is the decorated one or the function
parameter?

Anyway, thanks again.

[code]

class mainmethod(object):

    _parser = None
    kwargs = {}
    args =()

    def _getparser(self):
        if self._parser is None:
            self._parser = CommonOptionParser()
        return self._parser
    parser = property(_getparser)

    def __new__(cls, *args, **kw):
        obj = super(mainmethod, cls).__new__(cls, *args, **kw)
        if len(args) == 1 and inspect.isfunction(args[0]):
            #we assume that the decorator has been declared with no
arguments,
            #so go to straight to __call__, don't need __init__
            #if it's the case that the wrapped 'main' method allows or
            #expects a function as its first (and only) positional
argument
            #then you can't use this decorator
            return obj(args[0])
        else:
            return obj

    def __init__(self, *args, **kw):
        self.args = args
        self._parser = kw.pop('parser', None)
        self.kwargs = kw

    def _updatekwargs(self, dict):
        #don't want default null values of parser to overwrite
anything
        #passed to `mainmethod` itself
        for k,v in dict.iteritems():
            #can't do 'if v: ...' because empty configobj evaluates
False
            if v is None or v == '':
                continue
            self.kwargs[k] = v

    def exit(self):
        try:
            log.end()
        except:
            pass

    def run(self):
        options, args = self.parser.parse_args()
        #the following so that command line options are made available
        #to the decorated function as **kwargs
        self._updatekwargs(self.parser.values.__dict__)
        logargs = (
                self.kwargs.get(OPTLOGFILE, None),
                self.kwargs.get(OPTLOGDIR, None),
                self.kwargs.get(OPTLOGPREFIX, ''),
                )
        self.kwargs[OPTLOGFILE] = logstart(*logargs)
        log.info("SCRIPT: " + sys.argv[0])
        conf = self.kwargs.get(OPTCONFIG, None)
        if conf:
            log.info("%s: %s" % (OPTCONFIG.upper(), conf.filename))
        for k,v in self.kwargs.iteritems():
            if v and k not in COMMONOPTS:
                log.info("%s = %s" % (k, v))
        log.divider()
        return sys.exit(self.func(*self.args, **self.kwargs))

    def __call__(self, f):
        if f.func_globals['__name__'] == '__main__':
            self.func = f
            import atexit
            atexit.register(self.exit)
            atexit.register(self.run)
        return f

[/code]

Gerard



More information about the Python-list mailing list