VS Code for python

An acceptable python IDE for very low cost

August 10, 2021 — October 2, 2024

computers are awful
faster pussycat
plain text
python
UI
workflow

VS Code support for Python is getting pretty good. Here is where I document how, and my preferred configuration.

Figure 1

1 Interaction with/execution of Python

1.1 Interactive Python

TIL that VS Code’s so-called Jupyter support includes a fully interactive Python code environment that does not require the awful Jupyter notebook file-format/interface, but operates plain Python modules.

The trick is to put the magic # %% comment in:

# %%
msg = "Hello World"
print(msg)

# %%
msg = "Hello again"
print(msg)

Now everything after that comment is one “cell” that I can run with a click. NB: that keyboard shortcut to execute the cell was not reliable for me. I updated it to Shift-Enter to avoid a possible conflict.

Plotting is supported too (opens in a side window).

Figure 2: The workflow is suspiciously easy. I have done nothing to deserve this, except to complain about Jupyter.

This is a helpful idea. For one thing, it avoids the horrible, bad, no good Jupyter file format.

Command Keyboard shortcut
Python: Go to Next Cell Ctrl+Alt+]
Python: Go to Previous Cell Ctrl+Alt+[
Python: Extend Selection by Cell Above Ctrl+Shift+Alt+[
Python: Extend Selection by Cell Below Ctrl+Shift+Alt+]
Python: Move Selected Cells Up Ctrl+; U
Python: Move Selected Cells Down Ctrl+; D
Python: Insert Cell Above Ctrl+; A
Python: Insert Cell Below Ctrl+; B
Python: Insert Cell Below Position Ctrl+; S
Python: Delete Selected Cells Ctrl+; X
Python: Change Cell to Code Ctrl+; C
Python: Change Cell to Markdown Ctrl+; M

Various things are broken. For example, sometimes the restart button doesn’t work to restart a given kernel, so I need to run some more code and abort it and then restart it. The keyboard shortcuts to execute given cells sometimes don’t work. IDK how things be.

1.2 Jupyter

Jupyter notebook support is highly evolved, in that we can run, edit and execute Jupyter notebooks inside VS Code. If it is not seamless then at least the seams are not too prominent.

I am a Jupyter skeptic but I admit VS Code does sift some of the rougher chunks out of the Jupyter grind. All in all, editing Jupyter notebooks in VS Code is much better than the Jupyter notebook browser front end, at least for my workflow, and less Jupyter is better Jupyter.

Further, heaps of other things that native Jupyter notebooks cannot even dream of just magically work in VS Code. Type inference? Yes! Autocomplete? Sure! Debugger injection? Sometimes! Search and replace across a whole project? Urgh, kinda! Style checking? Yes! Refactor assistance? Yes, and it only fails some random subset of times! Comprehensible documentation for all of these features with a single source of truth? Usually!

There are still some broken things (refactoring ignores Jupyter notebooks sometimes, session sharing broke for me in a weird way last time, search is unpredictable, keyboard shortcuts are inconsistent, scrolling to the correct bit of the notebook just fails sometimes) but since Jupyter notebooks are IMO irreducibly just a bit shit, I will take whatever incremental and patchy improvement I can get.

I imagine that Jupyter notebook extensions don’t work for VS Code but I have not tested this. Essentially all the Jupyter extensions I was installing were to make the Jupyter notebook interface impersonate VS Code, so I can pay this no mind.

Points of friction: Jupyter Notebook can interact badly with VS Code remote, losing data if there is a buggy extension installed. (See also.)

2 Debugger

VS Code supports a multi-language debugger based on an open debugger protocol which includes support for the Python-style debugging. The details are managed by an extension, microsoft/vscode-python-debugger, but users can remain oblivious to that unless doing some ornate remote debugging (for which, see below).

Install at Python Debugger Extension

Notable features include visualisation of various Python objects, including Pytorch tensors.

2.1 Interactive/Jupyter kernel debugger

It was not obvious how to use the debugger tool for post-mortem debugging in Jupyter notebooks, or Python interactive sessions, i.e. how to see what led to a crash after the fact, which is a lamented, powerful feature of the basic Python debugger unavailable to us in fancy modern Jupyter.

The manual was silent on the matter; Last time I searched online for “Python post mortem debugger Jupyter VSCode” I was directed to this very page. So I guess the answer needs to go right here:

I needed to activate the Debugger menu in the sidebar and then check the User uncaught exception option. We cannot actually get this in post mortem mode; we need to notify Python in advance by launching the cell using the Debug Cell command. Then, when the cell crashes, we get a debugger window.

Figure 3

Then those tiny buttons next to the Main thread act as step debugger commands.

AFAICT the Jupyter/interactive Python debugger is orthogonal, configuration-wise, to the normal Python debuggers; it does not seem to need a debugger configuration, nor to care about the contents of launch.json. Or am I trippin’?

There are various annoying things about the debugger in interactive mode. I cannot seem to turn off the “feature” wherein it stops at the first line of the cell and asks permission to continue. I have never ever needed to start debugging at line 1; for me a debugger is something I use to understand what is happening deep inside the code.

Annoyingly, the debugger cannot go “up” the call stack into the parent function of a given invocation in general; It seems to work sometimes, but usually I find myself in some ephemeral Python file that VS Code generated to invoke the code.

So it’s… ok. It is fine. There is a lot of repetitively clicking on things, and not quite as much flexibility as I would like, but it is less horrible than Jupyter notebooks, so I will take it.

2.2 Useful configs

A launch.json supporting local and remote debugging is this:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Remote Attach",
            "type": "debugpy",
            "request": "attach",
            "justMyCode": true,    // don’t debug libraries
            "stopOnEntry": false,  // don’t wait around for me to click "continue"
            "connect": {
                "host": "localhost",
                "port": 25698
            },
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "."
                }
            ]
        },
        {
            "name": "Python: Current File",
            "type": "debugpy",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "justMyCode": true,    // don’t debug libraries
            "stopOnEntry": false   // don’t wait around for me to click "continue"
        }
    ]
}

VS Code will not automatically construct the tunnel, so we still need to do that manually. We can do this the classic way,

ssh remote.server.com -L 25678:localhost:25678

There is a semi-automatic tunnelling tab next to the terminal window in VS Code that probably does something similar. Full walk-through here. Hard-coding the values in a launch.json might seem clunky. It is. Here are some people discussing that bulletmark/debugpy-run is a workaround providing some types of dynamic configurations.

NB: there are still some references around to ptvsd, an older version of the Python plugin which is now deprecated. Ignore ’em.

Pro-tip: code which uses threading for parallelism can hang in the usual weird ways when debugged. Some of these can be ameliorated by invoking with

env PYDEVD_UNBLOCK_THREADS_TIMEOUT=4 python my_script.py

3 Refactoring

Somewhat magical, sometimes. But I cannot work out why it only does magical refactoring assistance sometimes. TBD.

4 “Intellisense”

pylance (source) is an extension that works alongside Python in Visual Studio Code to provide performant language support. Under the hood, Pylance is powered by Pyright, Microsoft’s static type checking tool. Using Pyright, Pylance has the ability to supercharge your Python IntelliSense experience with rich type information, helping you write better code faster.

There are other language servers but I have found it advisable to use Pylance, otherwise things crash.

5 Linting

There are many linters supported in VS Code.

pylint is my preferred linter for now. flake8 is very shrill and clogs the logs with warnings about cosmetic problems marked as critical errors.

They are both slow. I’m currently using ruff which ships a Rust-based Python linter. Early indications are good; it is so fast that I do not realize it is doing things.

numpy, torch etc

Symptom: we get errors like Pylint E1101 Module 'torch' has no 'from_numpy' member. We can fix it using Pylint “generated members”. Add this to settings.json:

"pylint.args": ["--generated-members", "torch.*"]