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)
asked Apr 3, 2015 at 15:09

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.

answered Apr 3, 2015 at 16:18
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

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.