updated on January 8
While Python will never have a type system you might know from languages like Java, C++ or even Haskell, it is definitely an incredible addition to the language. It enables static type analysis, enhances the code completion in your editor of choice and makes documenting the types of your parameters very elegant. Therefore, it is a no-brainer for me whether I should use type annotation for new projects or not.
Python is not very strict about the types. In the end, there it is a dynamically typed language. Therefore, the biggest advantage of Python types is documentation. Therefore, you should pay attention that your type annotations carray as much semantic meaning as possible.
For a recent project of mine, I brainstormed and played a little bit around and wrote a function that looked roughly like this:
def write(path: str, tag: str): pass
You might argue that there is nothing wrong with the types. And you’re correct. In the past, paths were treated as strings that should have a common format. They are a bunch of blocks connected by the chosen path seperator of your operating system (slash on *nix systems backslash on Windows).
And that’s the problem. Strings do not enforce any structure. They are just lists of possibly randomly chosen characters. However, as mentioned above, string have to follow a very specific structure, otherwise they are invalid. For example,
hello world and
/some\random/folder are perfectly fine strings but invalid paths. Brett Cannon, a python core developer that I respect very much, made a very good point on this.
To make matters worse, functionality for working with paths is split between many modules from the standard library.
glob is used to find all files matching a given pattern.
os.path provides all basic manipulations on a path. And finally,
shutil provides high level operations on files, like moving.
Imagine a scenario where you want to find all
import glob import shutil import os archive = os.path.expanduser('~/archive') for filename in glob.glob('*.pdf'): destination = os.path.join(archive, filename) shutil.move(filename, destination)
PEP 428 addressed all issues mentioned above by introducing a new module,
Pathlib. It describes paths on your filesystem in an object-oriented manner. And in my opinion, a beautiful example of excellent craftmanship, done by somebody who knows the Python Zen.
If your in the nice position that you have left Python2 finally behind you, you can just do
from pathlib import Path and you are good to go.
Rewritten to using
pathlib, the example from above would look like this.
from pathlib import Path destination = Path('~/archive').expanduser() for file in Path('.').glob('*.pdf'): file.rename(destination / file)
While I could go over everything you could do in
Pathlib and give examples for everything, I would like to refer the official documentation and only show the parts that I find extremly cool and which made me rewrite my code used the
os module for handling paths.
for file, template in self.templates.items(): content = template.render(**self.parameters) with (self.output_path / file).open('w+') as f: f.write(content)
The coolest feature is actually on the second last line. Here I had to concatenate my the base output path with the name of the file that I am currently processing. With the os module, you would have to write
While it is not necessarily bad to do path concetation this way, it is not the most python way. I expected that using
Pathlib, I could just add two paths objects. However I was wrong. Adding two path objects is not defined. But the division is.
concrete_path = output_path / file
Look how nice it is 😍 It might not seem like a big deal to many people, but this small feature ensured me again that the developer of the standard library know what they are doing and really focus on desigin human-friendly interfaces.