Emacs & Python: My Setup 2018

Posted: 2018-11-27 | Updated: 2019-11-08

Earlier this year I did an expansion to my entries on MPD, sort of a state of my setup piece, and I've decided to do the same thing for my Emacs and Python setups as well. If you write Python code and use Emacs, or are interested in either, read on!

Personally, I don't love installing python packages I need for development through the system package manager. It's definitely appropriate for libraries needed by other system packages and things like that, but for my local dev environment I do something else.

For my login account, "hristos", I've added $HOME/.local/bin to my $PATH. Additionally, when I want to install a package via pip, I invoke it like this:

This --user flag installs the package to $HOME/.local so now all I need to do is configure my editor to look there for Python libraries. A nice benefit of doing things this way is you don't need superuser access, and you aren't messing with the system python in any way.

Anyways, in the manner seen above, you would install at minimum these packages: pyflakes, flake8, and jedi.

Completion is enabled with two primary packages: Company mode and company-jedi. Company mode itself comes with Emacs, but follow the link to see an example of installing company-jedi with the excellent use-package.

The above function is ran as a python-mode hook and enables several things:

If a Jedi server is running, it's stopped (via another function)

Check commands are specified

A path to a virtualenv is specified (this is required by Jedi, even if you don't use it.)

I tell Jedi to include my local Python library paths under $HOME/.local/lib in it's list of python paths so it will autocomplete and check those.

Finally, if it's not already there, the Jedi server is installed to it's virtualenv.

You may notice that absolute paths are not used for the various binaries I've set, and that's deliberately. The idea is, let $PATH do it's job and find the right thing. This lets me use locally installed binaries ahead of anything that may be installed at the system-level.

It's often useful to go to the definition of a particular symbol to learn more about it and whatnot. The below function is mapped to Shift+left click to make this easy:

(defungoto-definition-at-point(event)"Move the point to the clicked position and jedi:goto-definition the thing at point."(interactive"e")(let((es(event-start event)))(select-window(posn-window es))(goto-char(posn-point es))(jedi:goto-definition)))

In the spirit of empowering Emacs to give me everything I want, the below function, bound to shift+right click, will open a buffer with the PyDoc for the given symbol:

(defunquick-pydoc(event)"Move the point to the clicked position and pydoc the thing at point."(interactive"e")(let((es(event-start event)))(select-window(posn-window es))(goto-char(posn-point es))(pydoc-at-point)))

I won't go into much detail here, see this entry for more, but the final part of my setup involves an Emacs daemon runit service.

In my runit service file for Emacs, I set a few environment variables to ensure Emacs has the same $PATH as my user, and other things. Anything specific about one's environment needs to be set here since runit doesn't pass much of an environment to services (see here for more information on that.) Below is what my service file looks like:

There's many more awesome tidbits that come along with this setup -- to see things like python-specific keybindings: with a python-mode buffer open, run M-x RET describe-mode RET and Emacs will show you some of those details and much more.

The tools described above help me be a more productive Python programmer. It's not a comprehensive writeup of my entire workflow, just the main parts that make writing Python more awesome.

One could take this a step further and utilize packages like python-django.el which provide much deeper project integration. I personally use a different pattern for this but it's a nice way to manage a project when you want to keep everything within Emacs.

In the future, I may do a more in depth entry on my Django workflow, or other more specific things. Until then, happy hacking!