Python: Using isort with Black to improve your code base
isort is a Python package that sorts the order of your imports, and groups them into sections by type. So while a code formatter like Black will ensure the spacing and line length of your imports is correct, it will not modify their order.
So why bother ordering your imports?1
- To reduce cognitive load.
- If each file has imports listed in a different order, this is more taxing to read through, and requires active concentration. Having a consistent grouping and order to your imports helps your brain to spot patterns, often without you being consciously aware.
- To help with refactoring.
- When you are refactoring code, third party code is effectively out-of-bounds; it is a black box that you can move around, or plug into different places, but you cannot change its inner workings. If it’s unclear whether imports belong to your code base or to some third party, this makes it more difficult to spot ways you can reorganise or simplify your code.
- To discourage use of import side-effects.
- In general, import side-effects are a Bad Idea™. They make it harder to reason about what the code does, and can lead to surprising effects. While relinquishing control of your import order to isort does not prevent use of import side-effects, it does draw a line in the sand, making developers think twice before trying to manipulate the import order.2
isort configuration §
isort comes with sensible defaults, but at a minimum I prefer to add the following configuration for it to my pyproject.toml
:
The reason for each line is as follows:
profile = "black"
- I’m a strong advocate for using Black to automatically format Python code. Enabling this setting is critical to ensure that isort does not conflict with Black, since by default isort has a different opinion on the particulars of how imports should be arranged.
import_heading_* = "<blah>"
- This adds a comment heading for each import section. By default the sections have no headings. Note that there are other sections, such as
import_heading_local
for if you do afrom .local_file
style import, which I prefer to avoid. You can also define your own sections, but I have not explored that. I prefer sticking to the main three: standard library, third party, and project imports (i.e. those which are part of your project’s package(s)). known_first_party = [...]
- This is necessary for the
import_heading_firstparty
setting to work correctly when you have multiple Python packages, some of which are defined in otherpyproject.toml
files. In order for isort to be able to identify which packages are first party imports (aka “Project imports”), list each one explicitly. Without setting this, isort is smart enough to identify packages defined in the localpyproject.toml
file as being first party, but understandably it cannot determine that packages defined elsewhere are first party: they will be considered third party. If your Python project consists of just one package, then this is not necessary.
Here is an example of what using this configuration looks like:
While you’re at it, it’s also worth configuring isort to run on pre-commit.
This is all just like, my opinion, man. Usually I stick to the facts on these notes, but today I thought it would be helpful to try and explain why I think using isort is worthwhile. The following reasons are all based on anecdotal evidence (i.e. my personal experience), and not on any scientific evidence. ↩︎
If import order really does matter, then you can prevent isort from re-ordering specific imports with a strategically placed comment, since isort will preserve the order of comments. ↩︎