101

I was converting some old Python code to use pathlib instead of os.path for most path-related operations, but I ended up with the following problem: I needed to add another extension to a path that already had an extension (not replace it). With os.path, since we are merely manipulating strings, the solution was to add the extension with string operations:

newpath = path + '.res'

It doesn't work with pathlib.Path because it doesn't allow concatenation of arbitrary characters. The closest I could find was the following:

newpath = path.with_suffix(path.suffix + '.res')

It looks like a workaround because it still uses string addition in the end. And it has a new pitfall because I forgot at first to handle the case where there are already several extensions and you want to add a new one, leading to the following code to get back the old behaviour:

newpath = path.with_suffix(''.join(path.suffixes) + '.res')

Now it doesn't feel terse nor clean since it uses more and more string operations to achieve the old behaviour instead of pure path operations. The fact that Path.suffixes exists means that the library's developers considered the case where a file can have multiple extensions, yet I couldn't find a way to simply add a new extension to a path. Is there a more idiomatic way that I have missed to achieve the same behaviour?

EDIT: actually path.with_suffix(path.suffix + '.res') is enough to handle the case where there are already several file extensions, even though it wasn't immeditely obvious to me.

Morwenn
  • 21,684
  • 12
  • 93
  • 152
  • 2
    Didn't see your edit until after posting my answer... that seems like a pretty good solution too – scnerd Oct 31 '18 at 21:38
  • 3
    it seems there was no better answer than the one-liner you already had. pathlib seems to have some very annoying fatal flaws that make it more difficult to work with than plain strings -- case in point, if you wanted to add the suffix `_foo.res` with your method, you get `ValueError: Invalid suffix`, because it doesn't start with a dot! – SpinUp __ A Davis Jun 27 '19 at 14:09
  • 1
    Should probably select an answer... – Mad Physicist Nov 19 '20 at 05:23
  • @MadPhysicist I would if I considered that there was a single good answer, but as evidenced by the answer mentioning `pathlib3x` there isn't a clean solution in the standard library (and I don't want to add a new dependency for a whole library), and while several of the proposed alternative are valid, none feel satisfying. I'd rather let the mass pick their favourite answer. – Morwenn Nov 19 '20 at 09:45
  • @Morwenn. In the meantime, your question is lingering in the unanswered queue, while some unlucky person is lacking their 15 pts – Mad Physicist Nov 19 '20 at 14:10
  • @MadPhysicist I'm aware of that, unfortunately the unanswered queue and points aren't really something I care about anymore. I consider actually meaningful accept marks to bear a greater value for the overall community. – Morwenn Nov 19 '20 at 14:19
  • @spinup, I don't claim there are no flaws. But this particular behaviour seem quite correct imho: a suffix in term of pathnames are seperated by a `.`. You should do it with _name_, i.e. `path.with_name(path.name + "_foo.res")`. – doak Jan 28 '22 at 17:59
  • @doak Looking at this years after my original comment, I think I was looking at the case of changing a pathname from `/a/b/c/file.ext` to `/a/b/c/file_foo.ext`. The `with_suffix` method would seem to fit the bill perfectly to strip off the extension and add a new ending to the name, but for some reason they chose to artificially limit the method and throw an error instead. – SpinUp __ A Davis Jan 28 '22 at 21:56

7 Answers7

102

I find the following slightly more satisfying than the answers that have already been given:

new_path = path.parent / (path.name + '.suffix')
P-Gn
  • 23,115
  • 9
  • 87
  • 104
24

It doesn't seem like Path's like being modified in-place (you can't change .parts[-1] directory or change .suffixes, etc.), but that doesn't mean you need to resort to anything too unsavory. The following works just fine, even if it's not quite as elegant as I'd like:

new_path = path.with_suffix(path.suffix + new_suffix)

where path is your original Path variable, and new_suffix is the string with your new suffix/extension (including the leading ".")

scnerd
  • 5,836
  • 2
  • 21
  • 36
  • 2
    If you are going to write out both extensions, it would probably be more canonical to write as `new_path = path.with_suffix(new_suffix)` – Free Url Feb 19 '19 at 01:22
  • 4
    your answer would substitute the suffix, not add it – ImanolUr May 24 '19 at 10:21
  • 2
    @ImanolUr I assumed "new_suffix" would include everything desired. See the edited code for a more intuitive answer. – scnerd May 24 '19 at 16:37
  • 7
    But... `path.parent / path.name` is exactly equivalent to the original `path` so how is that different from OP's `path.with_suffix(path.suffix + '.res')`? – Tomerikoo Apr 04 '20 at 16:04
14

You might use pathlib3x - it offers a backport of the latest (at the date of writing this answer Python 3.11.a0) Python pathlib for Python 3.6 or newer, and a few additional functions like append_suffix

>>> python -m pip install pathlib3x

>>> import pathlib3x as pathlib

>>> pathlib.Path('some_path').append_suffix('.ext')
PosixPath('some_path.ext')
>>> pathlib.Path('some_path.ext.ext2').append_suffix('.ext3')
PosixPath('some_path.ext.ext2.ext3')


you can find it on github or PyPi


Disclaimer: I'm the author of the pathlib3x library.

bitranox
  • 1,664
  • 13
  • 21
  • Do you know how maintained that repo will be in the future? – Dash Jul 02 '20 at 18:07
  • Cool, now that's something I would like in the standard library :o – Morwenn Jul 03 '20 at 11:38
  • 3
    @Dash: it will be maintained as long as I live - then You can take over ;-) since I use it every day ... – bitranox Jul 03 '20 at 12:45
  • @Morwenn that was one of the reasons I did it - on one hand it is clear that devs dont want to clutter the standard library, on the other hand I want to add useful functions, in a way that is compatible and in line with the style of the standard library. Just to make my own code more readable and less verbose. – bitranox Jul 03 '20 at 12:49
  • That's really convenient, thanks! However when passing such a path object to joblib I got a `ValueError: Second argument should be a filename or a file-like object, ../data/processed/blabla.pkl (type ) was given.`, which does't happen with the standard pathlib objects. Is it because joblib doesn't recognizes `pathlib3x.pathlib3x.PosixPath` as a PosixPath? In the meantime I just need to add `.as_posix()`, but it would be great if we wouldn't have to. – Alexis Cllmb Jul 31 '22 at 18:17
13

You can just convert your Path to string then add new extension and convert back to Path:

from pathlib import Path
first = Path("D:/user/file.xy")
print(first)
second = Path(str(first)+".res")
print(second)
Winand
  • 2,093
  • 3
  • 28
  • 48
  • 5
    You can also monkey patch it: `Path.__add__ = lambda self, rhs: Path(str(self) + rhs)` – ofo Oct 30 '19 at 18:18
7

I think this would be better since you just want to extend the current path with an arbitrary string.

old_path = Path("/the/old/path.foo")  # "/the/old/path.foo"
new_path = Path(f"{old_path}.bar")    # "/the/old/path.foo.bar"
  • 1
    This is surprisingly the most readable code. Pathlib went too far with suffixes and forgot the most simple thing. – MarcH Oct 25 '22 at 23:43
1

if you want to append the file name, but not change the extension, this works

matfile2 = pathlib.Path.joinpath(matfile.parent, matfile.stem+' insert'+matfile.suffix)
nagordon
  • 1,307
  • 2
  • 13
  • 16
-1

The following code should do what you want it to in a very dynamic way.

from pathlib import Path
import time

p = Path('.')
p = p / '..' / 'Python' / 'Files' / 'Texts_to_read' / 'a_text_file'

new_p = str(p).split('\\')

new_suffix = '.txt'

new_p[-1] = new_p[-1] + new_suffix

p = Path('.')

for x in new_p:
    p = p / x

print(new_p)

print(p)
print(str(p))

time.sleep(5)

The fact that normal string operations can be used in this case is a good thing, as it adds a great deal of control over the file path desired without requiring a large assortment of new functions.