如何在Python中替换(或删除)文件名的扩展名?

259

Python中是否有内置函数可以替换(或删除)文件名的扩展名(如果有的话)?

例如:

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

在我的示例中:/home/user/somefile.txt将变成/home/user/somefile.jpg

我不知道这是否重要,但我需要用于编写SCons模块。 (所以也许有一些SCons特定的函数可以使用?)

我想要一个干净的解决方案。简单地替换字符串中所有.txt的出现显然不够干净。(如果我的文件名是somefile.txt.txt.txt,这种方法会失败)


2
可能是从Python中的文件名提取扩展名的重复问题。 - S.Lott
SCons 允许在 action 字符串中获取文件基础名称。你能否发布需要此功能的 SCons 特定逻辑?这是针对 Action、Emitter 还是 Scanner? - bdbaddog
一些代码似乎已经失效了,因为路径返回的是PosixPath对象而不是字符串。 :p - shigeta
13
Python 3.9将允许path.removesuffix('.txt') + '.jpg',这很可能是未来最简单的方法。请参考https://www.python.org/dev/peps/pep-0616/。 - panofsteel
@panofsteel 需要导入哪个模块? - not2qubit
@not2qubit 没有,removesuffix 是一个字符串方法:https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix 编辑:所以要澄清的是,在 panofsteel 的例子中,path 只是一个字符串。 - vthomas2007
8个回答

303

尝试使用os.path.splitext,它应该能够实现你想要的功能。

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')

1
只需将新名称与os.path.join组合起来,以使其看起来整洁。 - Tony Veijalainen
8
@Tony Veijalainen: 不应使用os.path.join,因为它用于使用特定于操作系统的路径分隔符连接路径组件。例如, print os.path.join(os.path.splitext('/home/user/somefile.txt')[0], '.jpg') 将返回 /home/user/somefile/.jpg,这是不期望的。 - scottclowe
4
显式优于隐式。如果后缀为零个或一个,则使用以下方法更改文件后缀名:pathlib.Path('/home/user/somefile.txt').with_suffix('.jpg') - FredrikHedman
1
如果文件名中有多个点,例如文件名为some.file.txt,这段代码不会出错吗?有没有人能提供解决方案? - Garnagar
@Garnagar 文件名 = filename.rsplit( ".", 2 )[ 0 ] + ".txt" - eramm

239

在AnaPana的回答基础上,如何使用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

2
Real Python有一个很好的关于pathlib模块示例用法的介绍:https://realpython.com/python-pathlib/ - Steven C. Howell
3
这是我的典型方法,但当你有多个文件扩展名时,它似乎会失败。例如,pth = Path('data/foo.tar.gz'); print(pth.with_suffix('.jpg')) 将输出 'data/foo.tar.jpg'。我想你可以使用 pth.with_suffix('').with_suffix('.jpg'),但这很笨拙,并且你需要添加任意长的 .with_suffix('') 链来处理文件扩展名中的任意数量的点号.(尽管超过2个点的情况是一种少见的特例)。 - tel
2
你可以使用 while 循环来解决这个问题: pth = Path('data/foo.tar.gz'); while pth != pth.with_suffix(''): pth = pth.with_suffix(''); pth = pth.with_suffix('.jpg') - dericke
请参考我的答案,解决多个扩展名的问题。 - Michael Hall
1
请注意,您的 pth.with_suffix('').with_suffix('.jpg') 可以在一行中处理双后缀和单后缀情况。 - djvg

62

正如 @jethro 所说,splitext 是一个很好的方法。但在这种情况下,你可以很容易地自己分割它,因为扩展名 必须是 文件名中最后一个点号后面的部分:

filename = '/home/user/somefile.txt'
print( filename.rsplit( ".", 1 )[ 0 ] )
# '/home/user/somefile'
rsplit告诉Python从字符串右侧开始拆分,1表示最多执行一次拆分(例如将'foo.bar.baz'拆分为[ 'foo.bar', 'baz' ])。由于rsplit总是返回非空数组,我们可以安全地将索引0用于获取没有扩展名的文件名。

15
请注意,使用rsplit会导致以点开头且没有其他扩展名的文件(例如Linux上的隐藏文件,如.bashrc)产生不同的结果。对于这些文件,os.path.splitext返回一个空扩展名,但使用rsplit将把整个文件名视为扩展名。 - Florian Brucker
9
这也会对文件名/home/john.johnson/somefile产生意想不到的结果。 - Will Manley
更不用说还有那些文件名为filename.original.xml的情况了。 - tonysepia

23

我更喜欢使用str.rsplit()来实现单行代码:

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

示例:

>>> my_filename = '/home/user/somefile.txt'
>>> my_filename.rsplit('.', 1)
>>> ['/home/user/somefile', 'txt']

5
如果 somefile 没有扩展名并且用户是 'john.doe',则此操作失败。 - Marek Jedliński
2
那么它们都会失败吗? - eatmeimadanish

16

处理多个扩展名

如果您有多个扩展使用pathlib,则str.replace非常有效:

删除/剥离扩展名

>>> 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'

替换文件扩展名

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

如果你也想要一个 pathlib 对象输出,那么你可以显然地将这一行用 Path() 包裹起来。

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

将所有内容封装到一个函数中

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")
    

4
pathlib 有一个快捷方式:Path().with_suffix("") 可以删除文件扩展名,而 Path.with_suffix(".txt") 则可以将其替换。 - Levi
6
正确。但是它只会移除第一个扩展名。所以在上面的例子中,使用 with_suffix 而不是 replace 只会移除 .gz 而不是 .tar.gz。我的答案旨在是“通用的”,但如果你只期望有一个扩展名,那么 with_suffix 将是一个更清晰的解决方案。 - Michael Hall
3
恰当地,从Python 3.9开始,您可以使用removesuffix替代replace。这样可能会更加安全,例如在Linux上一些目录可能具有.d后缀:"/home/config.d/file.d".replace(".d","") -> '/home/config/file',而"/home/config.d/file.d".removesuffix(".d") -> '/home/config.d/file'。因此,也可以省略""函数参数。 - Alex Povel
1
谢谢提醒,@AlexPovel。我已经添加了一个使用Python 3.9的removesuffix的示例。 - Michael Hall

9

TLDR: 我认为最好的替换所有文件扩展名的方法如下。

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

更详细的回答: 最佳方法取决于您使用的Python版本以及需要处理的扩展数量。话虽如此,我很惊讶没有人提到过pathlib的with_name。我也担心这里的某些答案不能处理父目录中的.。以下是实现扩展名替换的几种方法。

使用路径对象

替换一个扩展名

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

替换最多两个扩展名

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

替换所有扩展名

使用pathlib中的with_name方法(我认为是最好的解决方案):

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

使用functools.reduce和pathlib的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+ 使用pathlib和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'))

不使用路径对象(仅限字符串)

一般而言,我认为使用pathlib的解决方案更加简洁,但并非每个人都可以这么做。如果您仍在使用Python 2,很抱歉。如果您没有python2的pathlib包,我真的很抱歉。

替换所有扩展名

兼容Python 2.7,使用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+ 使用 removesuffixos.path(如果你有 Python 3.9,为什么不使用 pathlib?):

import os
ps = '/path/to.my/file.foo.bar.baz.quz'
print(ps.removesuffix(os.path.splitext(ps)[-1].split('.', 1)[-1]) + 'jpg')

这就是我使用的,因为它似乎是这里最好的答案,而且不需要 Python 3.9。 - JeffCharter
谢谢。你让我重新审视我的答案,修复原始答案以处理超过两个扩展名,并添加了几个替代方案,包括Python3.9的答案。我不确定3.9是否真的会使事情变得更简单。我有什么遗漏吗? - jisrael18
1
你的最后一个2.7版本示例似乎有问题,因为“ps.removesuffix”作为字符串方法仅在3.9版本中引入,并且两个答案完全相等。编辑错误? - Welsige
@Welsige 谢谢。你说得对,我刚才从我的测试文件中复制了错误的示例。现在应该可以工作了。 - jisrael18
请不要忘记展示每个建议解决方案的输出。 - not2qubit

7

另一种方法是使用 str.rpartition(sep) 方法。

例如:

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

new_filename = prefix + '.jpg'

print new_filename

7

对于 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'

4
我认为 JS 建议的 pathlib 方法更简单。 - h0b0
同意.stem是最简单的答案。 - M__

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接