Using Python in Emacs
Introduction
I will keep this note up to date with details on how I use emacs for Python development.
Setting up a Python project
There are a few things I want when setting up a Python project:
- a virtual environment
- a language server (for use via eglot)
- an interpreter that works well with emacs
To that end, when setting up a project, I follow these steps:
- Create a new directory for my project, or navigate to an existing project directory.
- Create a new virtual environment in that directory, with e.g.
uv venv -p 3.13 myenv
(I tend to use uv for python package/environment management these days). - Activate the virtual environment. I use pyvenv and activate environments via
pyvenv-activate
. Sometimes, I still need to explicitly callsource ./myenv/bin/activate
in e.g. an open vterm buffer. In general, it's a good idea to make sure the environment is active and running as expected before doing anything. - Set up eglot—in your virtual environment, install
python-lsp-ruff
orruff-lsp
or whatever Python language server provides the functionality you want. It's also worth installingisort
if you want to sort your imports with e.g.python-sort-imports
. - Install and configure
gnureadline
for native completions (if you get theWarning (python): Your ‘python-shell-interpreter’ doesn’t seem to support readline, yet ‘python-shell-completion-native’ was t and "python3" is not part of the ‘python-shell-completion-native-disabled-interpreters’ list. Native completions have been disabled locally.
warning). See here for instructions. - Create/open a python buffer and start eglot with
M-x eglot
.
Using multiple language servers with eglot
For some reason, I recall this working without additional configuration before. But it's now, so I might as well document how to get it working. My best guess is that I overrode the defaults as the eglot docs note that "Eglot comes with a fairly complete set of associations of major-modes to popular language servers predefined."
Suppose you have installed two different options for a Python LSP. Follow the instructions here.
(with-eval-after-load 'eglot (add-to-list 'eglot-server-programs `(python-mode . ,(eglot-alternatives '(("ruff" "server") ("basedpyright-langserver" "--stdio"))))))
IN PROGRESS Writing and Executing Python Code
After setting up the Python project as described above, I tend to follow one of two approaches:
Writing + Code with Org Babel
I often write and execute code blocks in org mode using babel. A lot of my coding work occurs in the context of writing examples and demos (I am a developer advocate) so demonstrating and writing about code segments is often more important to me than working on projects/source files.
This isn't a guide to using org babel, so please consult the docs if you are unfamiliar with it. You can find this inside emacs! Use C-h R (info-display-manual)
and search for org
to find the org mode manual, then navigate to the "Working with Source Code" section.
When working with org babel, I tend to do the following for convenience:
Set header args in properties: I will usually set at least a session via properties on the parent org mode header. Multiple babel blocks with the same session share an execution context, much like multiple code blocks in a Jupyter notebook. So variables defined in one block will persist to the next block that has the same session name.
Here's what that looks like:
*Headline 1 :PROPERTIES: :header-args:python: :session session_name :END: #+begin_src python x = 42 #+end_src : None #+begin_src python x #+end_src : 42
Now, as you can see, all newly-created Python source blocks under
Headline 1
will automatically be associated with the sessionsession_name
and you won't need to manually specify the session in each block.Define snippets to quickly create source blocks: I use YASnippet to make it quicker and easier to define new source blocks in my chosen language, without needing to type out
C-c , s python
each time I need a new source block.Here is an example snippet:
# -*- mode: snippet -*- # name: python babel source block # key: p # contributor: Daniel Liden # -- #+begin_src python :results ${1:verbatim} $0 #+end_src
With this snippet, you can simply type p
and then yas-expand
(I have this set to TAB
). It will create the source block with your cursor set to the results type, which will default to verbatim
. Hitting tab again will bring you to the body of the source block (where $0
is), and you can start writing your code. This saves a few keystrokes.
- Displaying graphics/tables/plots: I have never found this straightforward and usually need to do some troubleshooting whenever I try it. In general, I find it easier to write the graphic to a file and then, separately, display the file in org mode rather than trying to get the source block to yield and display the file directly. That said, this is a good resource to consult for the various options for displaying graphics.
IN PROGRESS Source files + REPL approach
When working on a Python project, I use a source file + REPL approach. Setting this up in a basic way is simple. First, make sure you have the appropriate Python virtual environment set up (with e.g. pyvenv-activate
). Then, with a .py
file open, call M-x run-python
to start a Python REPL. When you want to run a code snippet, or all the code in a given file, you can use one of the various python-shell-send-*
commands to run the code from your source file in the REPL. Some of my most used ones are:
python-shell-send-buffer (C-c C-c)
: evaluate the whole bufferpython-shell-send-region (C-c C-r)
: evaluate a region (highlighted/selected section of code)python-shell-send-statement (C-c C-e)
: evaluate the statement at the current point. If you have a marked region, this will act the same aspython-shell-send-region
. Otherwise, this essentially just sends the current line, which is often what I want.
I will say that the various send commands did not behave exactly the way I expected them to, so it is worthwhile to experiment. Make a python file with some classes, functions, and unencapsulated code snippets and see how these commands behave. Marking and then sending a region is the most reliable and precise approach.
Here are some additional tips for using this workflow (will fill these out in the future):
- Use a Language Server: I use the eglot language server protocol (LSP) client for emacs, with the basedpyright and/or ruff server language server(s).
- Use magit for git workflows: Magit is an excellent TUI for git that provides a quick and intuitive way to handle all of your version control tasks.
- Use projectile for navigating within a project: Projectile has a variety of tools for navigating your project structure, searching and browsing project files, etc.