0

I'm working on a pet project, I'm trying to make a command-line version of Jupyter (I totally understand how stupid that sounds, "why not just use the Python shell", this is just for fun). I've been trying to think of a way to start a Python instance in the background, allowing me to plug the user's input into that shell. But I just can't find any way to do so. Is there any reasonable way to do this?

Thank you!

EDIT: I'm considering something like Jython, but I'd prefer to do this completely in Python if at all possible.

Charles Averill
  • 185
  • 2
  • 13

1 Answers1

1

There are two ways I can think off of the top of my head to do this. The first method is using exec to execute user input code.

while True:
    user_input = input("Python command to execute: ")

    try:
        exec(user_input)
    except Exception as e:
        print("Error thrown.")

This however, comes with its limitations. You have to write some custom code to catch errors, throw errors appropriately, and etc. The second method is a bit more involved, but also more generalized. You use the everything is a file method, and you treat the user input (whether it's through a shell, a website, or anything) as a file. With that file, then, you execute it. You can leave a shell open at all times checking if a file has updated before executing:

import hashlib
import runpy
import time

FILE = "./file.py"

def get_file_md5(file_name):
    with open(file_name, "rb") as f:
        return hashlib.md5(f.read()).hexdigest()

md5 = get_file_md5(FILE)
first_run = True

while True:
    current_md5 = get_file_md5(FILE)
    if md5 != current_md5 or first_run:
        first_run = False
        md5 = current_md5

        try:
            runpy.run_path(FILE)
        except Exception as e:
            print("Error", e)
    else:
        time.sleep(1)

You might find this answer that I gave to another (vaguely related) question interesting and of use.


Regarding below. Notice exec(object[, globals[, locals]]) documentation:

In all cases, if the optional parts are omitted, the code is executed in the current scope. If only globals is provided, it must be a dictionary (and not a subclass of dictionary), which will be used for both the global and the local variables.

So you can do:

exec_globals = {}
exec('a = 10; print(a)', exec_globals)

print("\na in exec_globals: ", 'a' in exec_globals)
print("exec_globals['a'] =", exec_globals['a'])

print("\na in globals(): ", 'a' in globals())
print(a)

And the above will output:

10

a in exec_globals:  True
exec_globals['a'] = 10

a in globals():  False

Traceback (most recent call last):
  File "test.py", line 7, in <module>
    print(a)
NameError: name 'a' is not defined
felipe
  • 7,324
  • 2
  • 28
  • 37
  • I was considering exec() because it's so close to what I need! The only issue is that because it's run in the same python instance, if the user uses variables with the same names as the variables I use in the interpreter, everything will get messed up. Possible solutions are to use very weird variable names, but I'm not sure if that's effective. If there's a way to execute the exec() argument on a different python instance than the one that runs the exec() command, that'll be exactly what I need. – Charles Averill May 26 '20 at 02:53
  • Made edit to the post. Docs for `exec` specifies how to deal with that. :) – felipe May 26 '20 at 04:57
  • Thank you so much! That's exactly what I needed – Charles Averill May 26 '20 at 05:19