I'm working on a development environment for
Python based on Emacs' SLIME mode for Common Lisp.
In this talk I'll demonstrate some of its
features, like an object inspector, interactive
backtrace buffer, thread and async task viewer,
and function tracer. I'll also discuss its
implementation and limitations, along with future
directions for the project.
This project aims to bring a Lisp and Smalltalk
inspired style of development to Python. You get a
faster feedback loop by developing inside a
running python process without needing to restart
your program and lose state on changes, allowing
you to immediately inspect the results of code you
write. We can also provide more advanced tooling
based on runtime introspection, as we have more
information available at runtime than is available
to traditional tools based on static analysis of
source code, mainly we have the actual values of
variables rather than just their types.
About the speaker:
Python is eating the world. Emacs is eating my computing environment. I'm
attempting to get them working together.
Transcript
Hello everyone, I'm Scottand I'll be talking about Swanky Python,which is a development environment for Pythonbased on Emacs' Slime package.So what is that and why might you find it interesting?SLIME is the Superior Lisp Interaction Mode for Emacs.It's an Emacs package for developing Common Lisp,and it's a bit different from the way we develop most languagesin that you're always connectedto a running instance of your application,and you kind of build up your application, piece by piece,modifying one expression at a timewithout ever having to restart your application.So why might you want to develop this way?One advantage is that you can get a faster feedback loop.For some kinds of software, it doesn't make a big difference.Like, if you're developing a web backendwhere all state is stored externally in a database,then you can have a file watcherthat just restarts the whole Python processwhenever you make any edit,and you're not really losing anything,because all the state is stored outside the Python processin a database. So it works great.But for other kinds of software, likelet's say you're developing an Emacs packageor a video game,then it can be a real pain to restart the applicationand recreate the state it was in beforejust to test the effect of each edit you want to make.Another advantage is the runtime introspection you have available.So since you're always connectedto a running instance of your application,you can inspect the values of variables,you can trace functions, and all sorts of other informationto help you understand your application better.And lastly, it's just a lot of fun to develop this way,or at least I find it fun developing with SLIME,so I wrote a SLIME backend for Pythonso I could have more fun when I'm coding in Python.As for the name swanky-python, within SLIME,swank is the name of the Common Lisp backendthat runs within your Common Lisp applicationand connects to Emacs. So I'm not too creative.swanky-python is just a swank implementation in Python.
So let's see it in action. So we started up with M-x slime.And what that does is it starts a Python process,starts swanky-python within it, and connects to it from Emacs.And you can configure how exactly it runs Python.Or you can start swanky python manuallywithin a Python application running on a remote serverand forward the port locallyand connect to it in Emacs, from Emacs remotely.Within the README, there's more documentationon other ways to start it.But just M-x slime is the basic way that works most of the time.So within the REPL, the first thing you'll notice is thatREPL outputs are clickable buttons,what SLIME calls presentations.So you can do things like inspect them.And for each presentation, in the Python backend,it holds on to the reference to the object.So for an int, it's not too interesting,but let's do a more complex object like a file.Then we can inspect the file.We can describe it, which will bring up documentationon that class. We can use it in further expressionslike if we copy it, it will use the actual Python objectin this expression.We can assign it to a variable.SLIME uses presentations everywherethat a Python object would be displayed.So instead of just their string representation,when you have a backtrace on an exception,or you... within the inspector or anywhere else really,anywhere that the string representationof an object would be displayed,it displays a presentation that you can go on toinspect, reuse, or send to the REPL and so on.One useful utility function is pp for print presentation.We haven't imported it yet.So when we get a name error exceptionand SLIME sees that that name is available for import somewhere,it'll give us the option of importing it.Since it's available for import from multiple modules,it'll prompt us for which one we want to import it from.We want to import it from swanky-python,not from the standard library.Then it will print a presentation of that object.Within the REPL, this is not really usefulbecause all REPL outputs are already presentations.But I use this now whenever I would use print debugging,just whenever I would use insert print statements in my programto see what's going on, I have it print a presentationbecause that way I can go back and inspect it later,copy it to the REPL and further manipulate it and so on.
Next up, let's look at the inspector more.If we go back and inspect the file object,you can write custom inspector viewsfor different kinds of objects.So far, I just have a couple. One for sequences,one for mappings, and one for every other kind of object.Like if we inspect a mapping, there's a shortcutinspect last result, which is what I normally useto open the inspector. Then we see the values,and each value in the inspector is a presentationthat we can go on to inspect, and so on.Let's go back to inspecting the file object.Again, we can inspect each of the values,we can copy them back to the REPL and so on.It just displays all the attributes for the classand their values.We can configure what attributes we want to show.There's a transient menu where we can toggleif we want to show private attributes, dunder attributes,doc strings, so on, or everything,which is a bit much to show by default.So we'll reset it to the default.In the future, I want to add graphical inspector viewsfor different kinds of objects, and also supportshowing plots in both the inspector and the REPL,but that's future work I haven't started on yet.
Let's look at the different options for evaluating Python.So we can evaluate a whole file.We can evaluate just a class.We can evaluate just the method we're working on.We can evaluate a Python statement,and it will show the result in an overlay next to the cursor.We can select some code and just evaluate the highlighted region.We can sync the REPL to the active file.So now everything we evaluate in the REPL will be in thecontext of the eval_demo module.We can also set the module that the REPL is in.We can go back to main.But let's go back to the eval_demo module for now.
One useful thing is when you update a class or a function,it updates old instances of that class or function.So right now, f.bar is foobar.But if we edit that class, it will actually edit the codefor the old instance of that class.And that's provided by code I copiedfrom IPython's autoreload extension.It helps when you're trying to develop in Pythonwithout having to restart the Python processwhenever you make a change.Auto reload in Python is a big topicthat I don't really have time to go into here,but right now it is more limitedthan what is done in Common Lisp.Like for example, if you have a data class in Pythonand you add a new field to the data class,it won't automatically update old instancesof the data class with a new field.So there's more that needs to be done with that,but I am perhaps naively optimisticthat Python's runtime is quite dynamic and flexible,and that I can fully implement autoreload in Python,but there's still work to be done,and it's a big topic to go into.Next up, let's look at the backtrace buffer.But as it is right now, autoreload is actually useful.I mostly develop in Python without having to restart the processand without running into issues from old statethat hasn't been updated properly.
So if we go on to look at the backtrace buffer,whenever we get an exception in Python...Let's go back to it.Whenever we get an exception, it will...let's change the code so that it actuallygets an exception...we will get an interactive backtrace bufferwhere we can browse the source code for the different stack framesand the local variables within the stack frames,which are all presentations that we can inspect and so on.We can also open a REPL in the context of any stack frame.Or we can, when we go to the source for a given stack frame,we can select some Python code and evaluate itwithin the context of that stack frame.One major limitation compared to SLIME for Common Lispis that in Common Lisp, you have the option torestart or resume execution from a given stack frameafter an exception happens, where in Python,what we have right now is pretty much equivalent tothe postmortem debugger.You can view the state that the call stack was inat the time of the exception,but you can't actually resume execution,which you often might want to do,because when you're coding in a dynamic language,you're going to get runtime errors.So if you're writing a script that does like some sort oflong-running computation or processes a ton of filesand gets an exception parsing one file halfway through,normally you'd have to fix the script, and then rerun itand have it process all the same files all over again,and lose a bunch of time for every bug you run intoand fix you have to make.So right now we've got a kind of mediocre workaroundwhich is you can add the restart decorator to a functionand then... where in the case of a scriptprocessing a bunch of files,you would add the restart decorator to the functionthat processes a single file.You'd add it to the functionthat represents kind of the smallest unit of workthat might fail with an exception,Then, when you get an exception,you can actually edit the function.Like, if we edit it so it doesn't throw an error,and then we can resume execution,then it will return from foo using thethe new version of baz,without having to run the script from the beginning again.So in the example of a script that processes a bunch of files,that would let you,as you run into files that cause an exception,fix your code to deal with itand resume execution without having to restart the scriptfrom the beginning.But this is obviously a pretty terrible hack,having to add the restart decorator to the function.I would like it to be able to restart from any function.without needing the decorator, as you can in Common Lisp,but I think that will require patching CPythonand I really have no idea how to do that.So if you do know anything about CPython internalsand are interested in helping, please reach out.
Another feature we have with the backtrace buffer isthere's this library called PyDumplingwhich can serialize a traceback and store it to a file.So you can use PyDumpling with your applications running inproduction to serialize a tracebackwhenever they have an exception and save it to a file.Then you can transfer the file locallyand load it into your local Emacs with slime-py-load-pydumpling.This will load the same backtrace buffer,and you see all the same local variablesat the time of the exception.You can inspect them and get a REPLin the context of the stack frame.Well, this will only work for variablesthat can be serialized with pickle.Or actually, the library uses dill,which can serialize a bit more than pickle can.But yeah so this can help you inspect and debug errorsfor applications running in production remotelythat you don't want to have SLIME connected to 24-7.
Next up, let's look at the documentation browser.We can bring up documentation for any module,and all this information is generatedfrom runtime introspection,from the doc strings for the moduleand the classes and so on.So you won't see documentation for librariesthat you don't have actually loadedinto your running Python process.Then you can go browse to classes.It'll show all the attributes, their methods, and so on.By each method to the right, it will showthe base class where the method was originally inherited from.You can also bring up a screen with all the Python packagesthat are installed, and browse that with imenu,and bring up information on any package and so on.
Next up, let's take a look at the thread view.So let's run this and then bring up the thread viewand this will show information on all running threads.You can configure it to refresh after a given interval,like every second, but I don't have that set up right now,so I have to manually refresh it.Probably the most useful thing is thatyou can bring up a backtrace for any threadwhich won't pause the thread or anything,but will just give you the call stackat the time you requested the backtrace.You can again view the stack frames, local variables,open a REPL in the context of the thread, and so on.There's also a viewer for async tasks,but I'm not going to demo that right now,because for that to work, you have to start swanky-pythonafter the async event loop has started,from within the same thread.If you go to the project readme,there's a demo of how to use the async task viewerwith a fastapi project.
Next up, let's look at tracing functions.So here we got some random error,because this is still very much a work in progress.But it looks like it executedcorrectly this time.So now let's mark the fibonacci functionfor tracing and execute it.We can see, every time the function is called,all its arguments and return values.Again, there are presentations that we can inspect and so on.But let's inspect a more complex object, like a file object.If we trace the count_lines function and run that code,then we can inspect the file it was passed, or the file object.One pitfall is that in Python, objects are mutable.So in the trace buffer, the string representationthat's printed is the string representationat the time it was passed to the function.But when we go to inspect it,we're inspecting the object as it is right now,which can be different than it was at the timethe function saw it. So for this file object, for example,it's closed now, when it was open at the timethe function used it.
Next up, let's look at AI integrations.So if you're used to SLIME with Common Lisp,Emacs actually has a built-in AI that can help with the transition.So it's just a joke, I actually really like Python.And for more serious AI integrations,I have some ideas for the futurebut I haven't implemented anything yet.I think right now, people are mostly passing source code to LLMsbut since we're embedded in the Python process at runtime,we have a lot of more information available,like maybe we can trace all calls to functions,and when we have a bug,we can feed the trace to the LLM,and the LLM can point out maybewhen this function was called with these arguments,its return value doesn't make sense,so maybe that's the root cause of your bug.If you have any ideas of potential LLM or AI integrations,let me know. I'm happy to discuss.
Next up, let's look at standard LSP-type features.So we've got completions. It's fuzzy completions right now,so it's showing everything with a PR in the name.We can bring up documentation for each one.When we start calling a method in the minibuffer at the bottomit'll show the signature.There's some refactoring available.We can extract a function or variable,or rename something,like, let's rename fib to fib2,and it will rename all the uses of it.All these features are based on Jedi,which is the Python library used by IPython.But as it is right now,if you want the most complete Python development experiencein Emacs, I'd probably recommend using LSPfor everything LSP can do, and then just using swanky-pythonfor the object inspector and backtrace buffer,and the interactive features it hasthat an LSP can't provide.
And that's it really.Shortly we'll have questions and answersas part of EmacsConf, and later on,if you have any questions, ideas, or issuesfeel free to reach out over emailor create an issue on the repository.I should probably warn you,if you want to try out the project:so far I'm probably the only user of itand I've only tested it on my own Emacs setup,so it's quite likely you'll run into issuestrying to get it installed and working.But if you do run into problems, please reach out,let me know. I'm happy to help and try and fix them.So that's it. Thanks for listening.