Add a return statement to mkdir

I’m using the pathlib lib to manage path in my Python development.
Most of th time I want to create a folder from a name which looks like:

from pathlib import Path 

tutu = Path("tutu")
toto = tutu / "toto"
toto.mkdir()

It forces me to do it in 2 lines. If I want to create a folder in one line my only option is:

(toto := tutu / "toto").mkdir()

which is not very pythonic.

Any reason why mkdir is not returning the created folder ? If it was the case, I could do:

toto = (tutu / "toto").mkdir()
2 Likes

Python made the aesthetic choice to separate statements from expressions, and there is a broad (though non-mandatory) API convention in Python that functions/methods should either be used for their value OR for side effects.

  • Returning None encourages the caller to use it as a statement, and visually looks like “It’s not assigned to anything so surely this had a side effect, or the call would be useless?” And statement order is unambiguous.
  • Whereas returning a value allows use in middle of complex expressions, where order is harder to figure out, and one tends to assume refactors like extracting and naming a sub-expression are safe.

The canonical example is list.sort() method which returns None but mutates the list in-place.
Repeated requests to make it return the result for convenience were denied, to keep it obvious to the user that there was a side effect here; instead, a sorted() function was introduced which returns a new list, leaving the original list unmodified.


Can you elaborate why exactly you feel

(toto := tutu / "toto").mkdir()

is “not very pythonic”?
I’d say that’s because it only uses := to avoid an extra line; it’s trivial to rewrite as two lines, and the price of the statement/expression dichotomy is we don’t generally shy away from using multiple lines.

However, if you prefer a single line here, go ahead and use := like that! I’d argue it’s actually clearer than toto = (tutu / "toto").mkdir() because there is no doubt what value toto got — in the latter case one would have to look up “what does mkdir() return?”

3 Likes

It’s probably because Python’s older os.mkdir doesn’t return anything, which was probably because the POSIX command mkdir doesn’t return a directory either.

I’d say one would definitely have to look that up for the case where the mkdir() call fails.

You’d get an exception, and toto would be undefined (NameError if used). That seems far more difficult to work with than a simple 2-line

toto = tutu / "toto"
toto.mkdir()
6 Likes

I hadthe same king of feeling with list.sort() actually. Thanks for explaining the rational behind these choices. It’s very reasonable and make it clearer for newcomers. Maybe I’m already too old and start having idiotic ideas.

That’s what people said when I requested it on SO I personally like it.

In most cases if I get an error during mkdir I want the code to stop because the folder won’t be there so I don’t see it as a issue but more a feature.

You still get the exception with the 2-line version, but unlike the one-liner, the variables will exist, so your error reporting can be more informative :slightly_smiling_face:

1 Like

If you want to write in one line – write it in one line:

toto = tutu / "toto"; toto.mkdir()
5 Likes

Trying to do things in one line just because of an aesthetic sense is also not very pythonic.

The rule in Python is command-query separation - if the library communicates back to you primarily by changing something (mutating one of the arguments that was passed in, or modifying the external environment), that’s a “command” which should not also return a meaningful value (as a “query” would).

As @pf_moore points out, it also makes exception handling harder. If mkdir raises an exception, then the proposed new way of doing things would also not assign toto even if the exception is handled. In the exception handler, having toto assigned would be useful, in order to report the path that failed to be created, right?

2 Likes

But also: don’t do this :wink:

API design is hard, though. If mkdir were to return an open file descriptor to the newly-created directory, that would make a lot of sense, and enable some elegant atomic operations (make a directory, then use os.open(..., dir_fd=...) to perform operations within that dir). So really, the only reason that this isn’t a good idea is that the resultant directory name (which was proposed to be the return value) can be completely calculated before the directory exists.

1 Like

the deprecated pylib had a own name for combining command and query

localpath.ensure("subdir", dir=True)

but based on the knowledge gained from dealing with it, i would rather propose having a wrapper around path objects that manages “dwim” semantics, so path itself can be “dumb” and non-magical

To jump on what was explained earlier on list.sort and here path.mkdir why does the string.strip do have return statement ?

If I follow the logic presented by @cben, there should be no return statement and a stripped function ? So if there is an exception for string object why not the others ?

A string object cannot be modified in-place from within Python; therefore, every method it provides will create and return a new string instead. It just happens that they called that one “strip” when calling it “stripped” would make more sense (because it does return). But it’s way too late to change that now. Names are hard.

3 Likes