LightTable / Python

Python language plugin for Light Table
MIT License
98 stars 51 forks source link

Implement inline eval #38

Open kenny-evitt opened 8 years ago

kenny-evitt commented 8 years ago

This covers LightTable/LightTable#760; the original comment from that issue:

When using Python to evaluate code inline, Light Table seams to evaluate the whole file upon connect. This causes the evaluator to spend a lot of time if there are time consuming functions or if there are errors in the file. (with a spinning working indicator that doesn't stop). As a workaround, I have to cut everything but the first 10 lines of code, save the file, evaluate the file, paste the cut part again, save the file again; and then I can evaluate the code inline normally. Is there a better way for this?

I have tested it with OSX Lion and Mountain Lion, Versions 0.4 and 0.5 of Light Table (0.5.2) Python 2.7.1 pyzmq==13.1.0 ipython==1.0.0

A very detailed comment in the aforementioned issue with lots of details:

Did some digging into this. The reason behind this behaviour is that in order for your code to run in the namespace of your module/file, the Light Table ipython connector code needs to pass the __module__ object corresponding to your file to ipython. In order to get a handle on that __module__ object it needs to import your file, causing the code in the whole file to run (since it is the first import of your file). There are a number of workarounds and potential avenues for fixes:

1) A simple workaround is to put your code in a

if __name__ == "__main__":
    #code here

block. This is the standard Python move and will stop that code being run on import - pretty good.

2) Another option is to change the Python eval plugin so it only evaluates in your file/module's namespace when evaluating the whole file. With the following change, when just evaluating a selection/single line it will evaluate in the __main__ namespace. I'm not certain if this is the best solution for all, but in the meantime this is my preference. You can implement this change yourself with a simple edit to ltmain.py in the python plugin (on osx it's at ~/Library/Application Support/LightTable/plugins/Python/py-src/) as follows:

Add this if to py-src/ltmain.py at line 242 and indent the try block so lines 242 to 246 look like:

if all(subsection_of_code_keys not in data[2] for subsection_of_code_keys in ["pos","meta"]):
    try:
        ltipy.setNs(data[2]['path'])
    except Exception as ex:
        jlog(ex)

Note that with this fix, evaluating the whole file (ctrl/cmd-shift-enter) will move the ipython into the module's namespace from then on. Subsequently evaluations of a selection/one line at a time will be done in the module's namespace, so you can get the current behaviour by evaluating the entire file first. This seems to be pretty good for me so far, though I've only just started testing it.

3) A third option for a fix is to automate the process of juanpr2, and copy the contents of the current file to a temp location, clear the contents of the file, save it, import it, then restore the contents of the file. This seems hackish/risky to me and running in the __main__ namespace seems to make sense to me for the most common use cases. However, someone may have a reason/use case I've not considered where not running in __main__ is required, in which case something like the above might be needed.

4) Another idea would be an option to enable/disable running single line/selection evals in the __main__ namespace (or without attempting to change namespaces), but implementing that would require a little more time than I can afford right now.

Interested to hear other people's thoughts. I'll post a gist of my ltmain.py shortly, but changing it yourself is not hard, and if that scares you, workaround 1) isn't that bad...