253

Is there a built-in function in Python that would replace (or remove, whatever) the extension of a filename (if it has one)?

Example:

print replace_extension('/home/user/somefile.txt', '.jpg')

In my example: /home/user/somefile.txt would become /home/user/somefile.jpg

I don't know if it matters, but I need this for a SCons module I'm writing. (So perhaps there is some SCons specific function I can use ?)

I'd like something clean. Doing a simple string replacement of all occurrences of .txt within the string is obviously not clean. (This would fail if my filename is somefile.txt.txt.txt)

Zeitounator
  • 38,476
  • 7
  • 53
  • 66
ereOn
  • 53,676
  • 39
  • 161
  • 238
  • 2
    possible duplicate of [Extracting extension from filename in Python](http://stackoverflow.com/questions/541390/extracting-extension-from-filename-in-python) – S.Lott Aug 23 '10 at 15:55
  • SCons allows getting at the filebase in an action string. Can you post your scons specific logic that needs this? Is this for the action, emitter, scanner? – bdbaddog Nov 01 '15 at 03:00
  • some of this doesn't seem to work any more as path returns a PosixPath not a string :p – shigeta Oct 10 '19 at 18:04
  • 12
    Python 3.9 will allow `path.removesuffix('.txt') + '.jpg'`, which will likely be the easiest way going forward https://www.python.org/dev/peps/pep-0616/ – panofsteel Oct 01 '20 at 00:20
  • @panofsteel What import is needed for that? – not2qubit Nov 21 '22 at 16:49
  • @not2qubit None, `removesuffix` is a string method: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix edit: so to clarify, in panofsteel's example, `path` is just a string – vthomas2007 Aug 04 '23 at 18:21

8 Answers8

297

Try os.path.splitext it should do what you want.

import os
print os.path.splitext('/home/user/somefile.txt')[0]+'.jpg'  # /home/user/somefile.jpg
os.path.splitext('/home/user/somefile.txt')  # returns ('/home/user/somefile', '.txt')
Gulzar
  • 23,452
  • 27
  • 113
  • 201
jethro
  • 18,177
  • 7
  • 46
  • 42
  • 1
    Only put the new name together with os.path.join to look clean. – Tony Veijalainen Aug 23 '10 at 17:51
  • 8
    @Tony Veijalainen: You shouldn't use os.path.join because that is for joining path components with the OS-specific path separator. For example, `print os.path.join(os.path.splitext('/home/user/somefile.txt')[0], '.jpg')` will return `/home/user/somefile/.jpg`, which is not desirable. – scottclowe Jan 07 '16 at 00:37
  • 4
    Explict is better than implicit. If there is zero or one suffix then: `pathlib.Path('/home/user/somefile.txt').with_suffix('.jpg')` – FredrikHedman May 05 '21 at 20:40
  • 1
    Won't this break if there is more than one dot in the file name, e.g. if the file is called `some.file.txt`? Can anyone provide solution for this case? – Garnagar Jan 05 '22 at 14:07
  • @Garnagar filename = filename.rsplit( ".", 2 )[ 0 ] + ".txt" – eramm Mar 16 '22 at 10:29
231

Expanding on AnaPana's answer, how to remove an extension using pathlib (Python >= 3.4):

>>> from pathlib import Path

>>> filename = Path('/some/path/somefile.txt')

>>> filename_wo_ext = filename.with_suffix('')

>>> filename_replace_ext = filename.with_suffix('.jpg')

>>> print(filename)
/some/path/somefile.ext    

>>> print(filename_wo_ext)
/some/path/somefile

>>> print(filename_replace_ext)
/some/path/somefile.jpg
JS.
  • 14,781
  • 13
  • 63
  • 75
  • 1
    Real Python has a good write-up of example use cases of the pathlib module: https://realpython.com/python-pathlib/ – Steven C. Howell Oct 16 '18 at 13:24
  • 3
    This answer is my typical approach, but it seems to fail when you have multiple file extensions. For example, `pth = Path('data/foo.tar.gz'); print(pth.with_suffix('.jpg'))` will output `'data/foo.tar.jpg'`. I suppose you can do `pth.with_suffix('').with_suffix('.jpg')`, but it's clunky, and you would need to add an arbitrarily long chain of `.with_suffix('')` calls in order to deal with an arbitrary number of dots `.` in a file extension (admittedly, more than 2 is an exotic edge case). – tel May 28 '19 at 02:50
  • 1
    @tel You could use a `while` loop to solve that: `pth = Path('data/foo.tar.gz'); while pth != pth.with_suffix(''): pth = pth.with_suffix(''); pth = pth.with_suffix('.jpg')` – dericke May 20 '20 at 22:15
  • See [my answer below](https://stackoverflow.com/a/56807917/5299417) for a solution to the multiple extensions problem. – Michael Hall Jun 08 '20 at 02:05
  • 1
    @tel: Note that your `pth.with_suffix('').with_suffix('.jpg')` handles both the double suffix *and* the single suffix case, in one line. – djvg Feb 14 '22 at 21:17
60

As @jethro said, splitext is the neat way to do it. But in this case, it's pretty easy to split it yourself, since the extension must be the part of the filename coming after the final period:

filename = '/home/user/somefile.txt'
print( filename.rsplit( ".", 1 )[ 0 ] )
# '/home/user/somefile'

The rsplit tells Python to perform the string splits starting from the right of the string, and the 1 says to perform at most one split (so that e.g. 'foo.bar.baz' -> [ 'foo.bar', 'baz' ]). Since rsplit will always return a non-empty array, we may safely index 0 into it to get the filename minus the extension.

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • 15
    Note that using `rsplit` will result in different results for files which start with a dot and have no other extension (like hidden files on Linux, e.g. `.bashrc`). `os.path.splitext` returns an empty extension for these, but using `rsplit` will treat the whole filename as an extension. – Florian Brucker Jan 24 '12 at 11:11
  • 9
    This will also give unexpected results for the filename `/home/john.johnson/somefile` – Will Manley Nov 13 '16 at 10:26
  • not to mention cases where you have filename.original.xml – tonysepia Aug 22 '23 at 12:00
21

I prefer the following one-liner approach using str.rsplit():

my_filename.rsplit('.', 1)[0] + '.jpg'

Example:

>>> my_filename = '/home/user/somefile.txt'
>>> my_filename.rsplit('.', 1)
>>> ['/home/user/somefile', 'txt']
IvanD
  • 7,971
  • 4
  • 35
  • 33
15

Handling multiple extensions

In the case where you have multiple extensions using pathlib and str.replace works a treat:

Remove/strip extensions

>>> from pathlib import Path
>>> p = Path("/path/to/myfile.tar.gz")
>>> extensions = "".join(p.suffixes)

# any python version
>>> str(p).replace(extensions, "")
'/path/to/myfile'

# python>=3.9
>>> str(p).removesuffix(extensions)
'/path/to/myfile'

Replace extensions

>>> p = Path("/path/to/myfile.tar.gz")
>>> extensions = "".join(p.suffixes)
>>> new_ext = ".jpg"
>>> str(p).replace(extensions, new_ext)
'/path/to/myfile.jpg'

If you also want a pathlib object output then you can obviously wrap the line in Path()

>>> Path(str(p).replace("".join(p.suffixes), ""))
PosixPath('/path/to/myfile')

Wrapping it all up in a function

from pathlib import Path
from typing import Union

PathLike = Union[str, Path]


def replace_ext(path: PathLike, new_ext: str = "") -> Path:
    extensions = "".join(Path(path).suffixes)
    return Path(str(p).replace(extensions, new_ext))


p = Path("/path/to/myfile.tar.gz")
new_ext = ".jpg"

assert replace_ext(p, new_ext) == Path("/path/to/myfile.jpg")
assert replace_ext(str(p), new_ext) == Path("/path/to/myfile.jpg")
assert replace_ext(p) == Path("/path/to/myfile")
    
Michael Hall
  • 2,834
  • 1
  • 22
  • 40
  • 4
    pathlib has a shortcut for this: Path().with_suffix("") will remove an extension and Path.with_suffix(".txt") will replace it. – Levi Jun 07 '20 at 19:51
  • 5
    Correct. But it only removes the first extension. So in the above example, using `with_suffix` instead of `replace` would only remove `.gz` instead of `.tar.gz` My answer was intended to be "general", but if you only expect a single extension, `with_suffix` would be a cleaner solution. – Michael Hall Jun 08 '20 at 02:00
  • 3
    Fittingly, from Python 3.9 onward, you can use `removesuffix` over `replace`. This is perhaps safer, e.g. on Linux some *directories* might have a `.d` suffix: `"/home/config.d/file.d".replace(".d", "")` -> `'/home/config/file'` versus `"/home/config.d/file.d".removesuffix(".d")` -> `'/home/config.d/file'`. So, also saves the `""` function argument. – Alex Povel Nov 29 '20 at 11:21
  • 1
    Thanks for the heads up @AlexPovel, I have added an example using `removesuffix` for python 3.9 – Michael Hall Dec 01 '20 at 01:52
9

TLDR: Best way to replace all extensions, in my opinion, is the following.

import pathlib
p = pathlib.Path('/path/to.my/file.foo.bar.baz.quz')
print(p.with_name(p.name.split('.')[0]).with_suffix('.jpg'))

Longer Answer: The best way to do this will depend on your version of python and how many extensions you need to handle. That said, I'm surprised nobody has mentioned pathlib's with_name. I'm also concerned that some answers here don't handle a . in the parent directories. Here are several ways to accomplish extension replacement.

Using Path Objects

Replace Up to One Extension

import pathlib
p = pathlib.Path('/path/to.my/file.foo')
print(p.with_suffix('.jpg'))

Replace Up to Two Extensions

import pathlib
p = pathlib.Path('/path/to.my/file.foo.bar')
print(p.with_name(p.stem).with_suffix('.jpg'))

Replace All Extensions

Using pathlibs with_name (best solution, in my opinion):

import pathlib
p = pathlib.Path('/path/to.my/file.foo.bar.baz.quz')
print(p.with_name(p.name.split('.')[0]).with_suffix('.jpg'))

Using functools.reduce and pathlib's with_suffix:

import pathlib
import functools
p = pathlib.Path('/path/to.my/file.foo.bar.baz.quz')
print(functools.reduce(lambda v, _: v.with_suffix(''), p.suffixes, p).with_suffix('.jpg'))
print(functools.reduce(lambda v, e: v.with_suffix(e), ['' for _ in p.suffixes] + ['.jpg'], p))

Python 3.9+ Using pathlib and str.removesuffix:

import pathlib
p = pathlib.Path('/path/to.my/file.foo.bar.baz.quz')
print(pathlib.Path(str(p).removesuffix(''.join(p.suffixes))).with_suffix('.jpg'))

Without Using Path Objects (Strings Only)

In general, I think solutions using pathlib are cleaner, but not everybody can do that. If you're still using python 2, I'm sorry. If you don't have the pathlib package for python2, I'm really sorry.

Replace All Extensions

Python 2.7 compatible using os.path:

import os
ps = '/path/to.my/file.foo.bar.baz.quz'
print(os.path.join(os.path.dirname(ps), os.path.basename(ps).split('.')[0] + '.jpg'))

Python 3.9+ Using removesuffix and os.path (if you have python3.9, why aren't you using pathlib?):

import os
ps = '/path/to.my/file.foo.bar.baz.quz'
print(ps.removesuffix(os.path.splitext(ps)[-1].split('.', 1)[-1]) + 'jpg')
jisrael18
  • 719
  • 3
  • 10
  • This is what I used, as it seems to be the best answer here that doesn't need python 3.9 – JeffCharter Jul 27 '21 at 17:11
  • Thanks. You caused me review my answer, fix my original answer to handle more than two extensions, and add several alternatives, including python3.9 answers. I'm not sure that 3.9 actually makes things any simpler. Am I missing something? – jisrael18 Jul 27 '21 at 21:37
  • 1
    Your last 2.7 version example seems broken, as "ps.removesuffix" as a string method was only introduced with 3.9 version, and both answers are exactly equal. Edit error? – Welsige Sep 08 '21 at 13:11
  • @Welsige Thanks. You're right, I just copied over the wrong example from my test file. Should work now. – jisrael18 Sep 08 '21 at 17:23
  • Please don't forget to also show the output from each of your suggested solutions. – not2qubit Nov 21 '22 at 16:39
7

Another way to do is to use the str.rpartition(sep) method.

For example:

filename = '/home/user/somefile.txt'
(prefix, sep, suffix) = filename.rpartition('.')

new_filename = prefix + '.jpg'

print new_filename
user2802945
  • 159
  • 1
  • 6
7

For Python >= 3.4:

from pathlib import Path

filename = '/home/user/somefile.txt'

p = Path(filename)
new_filename = p.parent.joinpath(p.stem + '.jpg') # PosixPath('/home/user/somefile.jpg')
new_filename_str = str(new_filename) # '/home/user/somefile.jpg'
AnaPana
  • 1,958
  • 19
  • 18