27

Is there an easy way to replace a substring within a pathlib.Path object in Python? The pathlib module is nicer in many ways than storing a path as a str and using os.path, glob.glob etc, which are built in to pathlib. But I often use files that follow a pattern, and often replace substrings in a path to access other files:

data/demo_img.png
data/demo_img_processed.png
data/demo_spreadsheet.csv

Previously I could do:

img_file_path = "data/demo_img.png"
proc_img_file_path = img_file_path.replace("_img.png", "_img_proc.png")
data_file_path = img_file_path.replace("_img.png", "_spreadsheet.csv")

pathlib can replace the file extension with the with_suffix() method, but only accepts extensions as valid suffixes. The workarounds are:

import pathlib
import os


img_file_path = pathlib.Path("data/demo_img.png")
proc_img_file_path = pathlib.Path(str(img_file_path).replace("_img.png", "_img_proc.png"))
# os.fspath() is available in Python 3.6+ and is apparently safer than str()
data_file_path = pathlib.Path(os.fspath(img_file_path).replace("_img.png", "_img_proc.png"))

Converting to a string to do the replacement and reconverting to a Path object seems laborious. Assume that I never have a copy of the string form of img_file_path, and have to convert the type as needed.

Hector
  • 591
  • 2
  • 6
  • 13
  • 4
    Whatever you do, beware of using `Path.replace` as as attempted substitute - not the same thing, and **can clobber existing data on filesystem**! – wim Nov 20 '18 at 19:05
  • 1
    That's right. I luckily read the documentation before trying. `replace()` will rename the current file to the target, and replace it if the file already exists. – Hector Nov 20 '18 at 20:31

3 Answers3

39

You are correct. To replace old with new in Path p, you need:

p = Path(str(p).replace(old, new))

EDIT

We turn Path p into str so we get this str method:

Help on method_descriptor:

replace(self, old, new, count=-1, /)

Return a copy with all occurrences of substring old replaced by new.

Otherwise we'd get this Path method:

Help on function replace in module pathlib:

replace(self, target)

Rename this path to the given path, clobbering the existing destination if it exists, and return a new Path instance pointing to the given path.

J_H
  • 17,926
  • 4
  • 24
  • 44
  • Nice answer. Although I don't think you need to cast to a `str`, so that may be unnecessary. – JosephTLyons Dec 02 '20 at 08:38
  • 11
    Yes, it is necessary to cast to `str`. Otherwise it will call `pathlib.Path`'s `replace` method which does not do the same thing. – rayryeng Mar 04 '21 at 14:31
  • Just a small complementation: Alternatively to `str(p)`, pathlib’s [`p.as_posix()`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.as_posix) can also be used. – mouwsy Feb 17 '23 at 10:06
2

I have recently faced a similar problem and found this thread when searching for a solution. In contrast to the accepted answer I did not convert the pathlib.Path object into a string. Instead, I used its parent and name attributes (name is a string itself), along with the joinpath() method. Here is the code:

In [2]: from pathlib import Path

In [3]: img_file_path = Path('data/demo_img.png')

In [4]: parent, name = img_file_path.parent, img_file_path.name

In [5]: proc_fn = name.replace('_img.png', '_img_proc.png')
   ...: data_fn = name.replace('_img.png', '_spreadsheet.csv')

In [6]: proc_img_file_path = Path(parent).joinpath(proc_fn)
   ...: data_img_file_path = Path(parent).joinpath(data_fn)

In [7]: proc_img_file_path
Out[7]: WindowsPath('data/demo_img_proc.png')

In [8]: data_img_file_path
Out[8]: WindowsPath('data/demo_spreadsheet.csv')

An advantage of this approach is that it avoids the risk of making unwanted replacements in the parent bit.

Tonechas
  • 13,398
  • 16
  • 46
  • 80
  • True. It's often enough I will favor `re.sub(r'\.png$', '.csv', fname)` over .replace(), for safety's sake. On this question I stuck with OP's use of .replace(). – J_H Nov 01 '21 at 17:50
1

use PurePath.with_name() or PurePath.with_stem()

ibykovsky
  • 19
  • 1