4

Is there a more elegant/systematic/future proof way to invoke all code in if __name__ == '__main__' that is better than copy/past?

I'm converting my bash scripts into python. Two commands we use heavily are python -m SimpleHTTPServer YOUR_PORT and python -m http.server YOUR_PORT.

Translating this in 2.x is fairly clean.

SimpleHTTPServer main:

if __name__ == '__main__':
    test()

My code to simulate main:

import SimpleHTTPServer
SimpleHTTPServer.test()

Translating this in 3.x is not clean.

http.server main:

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--cgi', action='store_true',
                       help='Run as CGI Server')
    parser.add_argument('--bind', '-b', default='', metavar='ADDRESS',
                        help='Specify alternate bind address '
                             '[default: all interfaces]')
    parser.add_argument('port', action='store',
                        default=8000, type=int,
                        nargs='?',
                        help='Specify alternate port [default: 8000]')
    args = parser.parse_args()
    if args.cgi:
        handler_class = CGIHTTPRequestHandler
    else:
        handler_class = SimpleHTTPRequestHandler
    test(HandlerClass=handler_class, port=args.port, bind=args.bind)

My code to simulate main:

import argparse
import http.server
from http.server import CGIHTTPRequestHandler, SimpleHTTPRequestHandler
parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true',
                   help='Run as CGI Server')
parser.add_argument('--bind', '-b', default='', metavar='ADDRESS',
                    help='Specify alternate bind address '
                         '[default: all interfaces]')
parser.add_argument('port', action='store',
                    default=8000, type=int,
                    nargs='?',
                    help='Specify alternate port [default: 8000]')
args = parser.parse_args()
if args.cgi:
    handler_class = CGIHTTPRequestHandler
else:
    handler_class = SimpleHTTPRequestHandler
http.server.test(HandlerClass=handler_class, port=args.port, bind=args.bind)

1 Answer 1

1

This looks horrible, and I am not sure if this is the best way to do this, but the following code appears to start the server without too much difficulty involved:

import importlib
exec(compile(importlib.util.find_spec('http.server').loader.get_source(
    'http.server'), 'server.py', 'exec'), dict(__name__='__main__'))

Update 1: The next idea for starting the http.server module probably is not much better, but it utilizes the http.server module directly instead of having to deal with the importlib module.

import http.server
exec(compile(http.server.__loader__.get_source(http.server.__name__),
             http.server.__file__, 'exec'), dict(__name__='__main__'))

Of course, that probably just means that it would be better to create a utility function that handles executing modules in whatever context we want to run them in.

import sys

nvl = lambda value, other: other if value is None else value

def exec_module(module, globals=None, locals=None):
    frame = sys._getframe(1)
    globals = nvl(globals, {})
    globals.update(frame.f_globals)
    locals = nvl(locals, {})
    locals.update(frame.f_locals)
    exec(compile(module.__loader__.get_source(module.__name__),
                 module.__file__, 'exec'), globals, locals)

# this is how you would use the code up above

import http.server

exec_module(http.server, dict(__name__='__main__'))

Update 2: The exec_module function should probably be more like the following, but for some reason that fails to come to my attention, it does not seem to be working as expected:

def exec_module(module, globals=None, locals=None):
    frame = sys._getframe(1)

    exec_globals = nvl(globals, {})
    copy_globals = exec_globals.copy()
    exec_globals.update(frame.f_globals)
    exec_globals.update(copy_globals)

    exec_locals = nvl(locals, {})
    copy_locals = exec_locals.copy()
    exec_locals.update(frame.f_locals)
    exec_locals.update(copy_locals)

    exec(compile(module.__loader__.get_source(module.__name__),
                 module.__file__, 'exec'), exec_globals, exec_locals)

The changes take into account two things. First, references to the passed in globals and locals are retained in case the caller wants to examine their state after the function runs. Second, the globals and locals from the caller cannot overwrite any values already set in the globals and locals passed in.

Sign up to request clarification or add additional context in comments.

3 Comments

oof. That's clever! But, as you imply, I'm hoping for a bit cleaner of a solution.
@StevenWexler There is an alternative solution that avoids the importlib module altogether. Furthermore, the second design was refined into a utility function that might be useful for any module written in Python.
I like exec_module!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.