Python 3.7 (original) (raw)

Python 3.7 has been out for a while. In fact, it’s the oldest version of Python still receiving support when this was written. I’d still like to write a “what’s new”, targeting users who are upgrading to a Python 3.7+ only codebase, and want to know what to take advantage of!



Future annotations

If you add the following line to a Python module:

from __future__ import annotations

all of your annotations will remain unevaluated strings. This means you can use the latest Python typing syntax in your code (which is much more readable) and still support Python 3.7+! There are two downsides: you can’t use the new syntax_outside_ of annotations (obviously), and you can’t access the annotations at runtime. So this import is highly recommended unless you are using something using runtime annotations (like cattrs, pydantic, typer, or even functools.singledispatch).

Module access

This is something you could already be using, but now it can be included in your design. Two new module level functions were added: __dir__() -> List[str] and__getattr__(name: str) -> ANy. Most modules that might be imported in a REPL should already include this function:

def dir() -> List[str]:
    return __all__

This will allow tab completion to skip anything not in your __all__, like theannotations that you imported from __future__ along with all of your other imports.

The __getattr__ is really exciting, because it enables you to do all sorts of dynamic attributes on modules. You could makefrom plubmum.cmd import <any shell command> work in much less code. You could catch a common misspelling (like hist.axes instead of hist.axis), print a warning, and then make it work anyway. I would probably not mix with difflib to produce correction suggestions, because Python 3.10 does this for you anyway, and REPL users should upgrade. But you could (just limit it to <3.10, using the standard feature is better there).

Core support for typing on classes

Two new special methods were added. __class_getitem__ allowsSomeClass[thing] to be supported. __mro_entries__ is called during subclassing to get the bases if you subclass something that is not a class; if it is called, __orig_bases__ contains the original bases, and __bases__contains the thing this triggers. It is used because List[int] does not actually return a class, but an instance - that instance knows how to get the proper bases (list, object) because that’s what mro_entries returns. So you can now use instances instead of classes when subclassing, and those instances can replace themselves with classes during the process.

Other changes

Other standard library things

Other developer changes

Library developers may need to be aware of the following changes:

Final words

This was a fantastic release - and is the last “large” (18 month) release of Python ever. Python moved to a 12 month release cycle after 3.7. As of 2022, this is the oldest officially supported version of Python, and already has been dropped by the data science libraries following NEP 29 / SPEC 0.

Sources

This blog post was written well after the release, so the primary source was the docs & experience. Here are a few possibly useful links:

Bonus: Automatic upgrades

I will assume you have already run pyupgrade with --py37plus, ideally added it to pre-commit. This will clean up a lot of things automatically. Unique to the 3.7+ upgrade is from future import annotations; adding this to all your files will enable pyupgrade to also clean up your type annotations. You can use isort to inject this automatically. All told, this is what your pre-commit file should look like:

- repo: https://github.com/PyCQA/isort
  rev: "5.10.1"
  hooks:
  - id: isort
     args: ["-a", "from __future__ import annotations"]

- repo: https://github.com/asottile/pyupgrade
  rev: "v2.31.0"
  hooks:
  - id: pyupgrade
    args: ["--py37-plus"]

This will at least fix:

More might be added in future versions.