[解決!Python]splitext関数やPath.stem/Path.suffix属性を使ってパスを拡張子とそれ以外の部分に分割するには解決!Python

パスを、そこに含まれる拡張子とそれ以外の部分に分割したいことはよくある。これを行うにはsplitext関数やPath.stem/Path.suffix/Path.parent/Path.suffixes属性が使える。

» 2022年11月15日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「解決!Python」のインデックス

連載目次

# パスを拡張子とそれ以外の部分に分割する
from os.path import splitext

path = '/dir0/dir1/somefile.txt'

root, ext = splitext(path)
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile, ext: .txt

# Pathオブジェクトではparent属性とstem属性を組み合わせる
from pathlib import Path

path = Path(path)
print(f'path.stem: {path.stem}'# path.stem: somefile
print(f'path.suffix: {path.suffix}'# path.suffix: .txt
print(f'path.parent: {path.parent}'# path.parent: /dir0/dir1

root = Path(f'{path.parent}/{path.stem}')
print(f'root: {root}, ext: {path.suffix}'# root: /dir0/dir1/somefile, ext: .txt

# 拡張子が連続している場合にそれらを拡張子として分割するには
path = '/dir0/dir1/somefile.tar.gz'
root, ext = splitext(path)
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile.tar, ext: .gz

def mysplitext(path):
    root, ext = splitext(path)
    exts = ext
    while ext != '':
        root, ext = splitext(root)
        exts = ext + exts
    return root, exts

root, ext = mysplitext(path)
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile, ext: .tar.gz

path = Path(path)  # root: /dir0/dir1/somefile, ext: .tar.gz
root = Path(f'{path.parent}/{path.stem}')
ext = path.suffix
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile.tar, ext: .gz

print(path.suffixes)  # ['.tar', '.gz']

def mysplitext(path):
    parent = str(path.parent)
    parent = '' if parent == '.' else parent
    tmp = path.stem
    while True:
        stem = Path(tmp).stem
        if stem == tmp:
            break
        tmp = stem
    suffixes = ''.join(path.suffixes)
    root = f'{parent}/{stem}' if parent else stem
    return root, suffixes

root, ext = mysplitext(path)
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile, ext: .tar.gz


os.pathモジュールのsplitext関数

 os.pathモジュールには渡されたパスを、拡張子とそれ以外の部分に分割するsplitext関数がある(引数には文字列、Pathオブジェクトなどを指定できる)。splitext関数はパスを与えるとそれを「(拡張子以外, 拡張子)」というタプルに含めて返送する。

 以下に例を示す。

from os.path import splitext

path = '/dir0/dir1/somefile.txt'

root, ext = splitext(path)
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile, ext: .txt


 この例ではパスとして「'/dir0/dir1/somefile.txt'」をsplitext関数に渡している。そのため、拡張子とそれ以外に分割された「('/dir0/dir1/somefile', '.txt')」というタプルが返送されていることが分かる。

pathlibモジュールのPath.stem/Path.suffix属性

 一方、pathlibモジュールのPathクラスにはstem属性とsuffix属性がある。stem属性はパスを構成する最終要素の拡張子よりも前の部分を表し、suffix属性は拡張子を表す。そのため、単にこれら2つを結合するだけではsplitext関数とは異なる結果になることがある。

 以下に例を示す。

from pathlib import Path

path = Path(path)  # '/dir0/dir1/somefile.txt'
print(f'path.stem: {path.stem}'# path.stem: somefile
print(f'path.suffix: {path.suffix}'# path.suffix: .txt


 変数pathには先ほどの内容('/dir0/dir1/somefile.txt')をPathクラスのオブジェクトにしたものが代入されている。そして、そのstem属性はパスを構成する最終要素(somefile.txt)から拡張子を取り除いたものになっている。これに対して、suffix属性は拡張子になっている。

 splitext関数では「パスの構成要素全てから拡張子を除いたもの」と「拡張子」が得られていたので、Path.stem/Path.suffix属性とsplitext関数の戻り値が完全に対応しているわけではないことには注意しよう。同様な結果を得るには、パスを構成する要素のうち、最終要素よりも前の部分を表すPath.parent属性を使える。

print(f'path.parent: {path.parent}'# path.parent: /dir0/dir1


 よって、splitext関数と同様な結果を得るには以下のようにすればよい。

root = Path(f'{path.parent}/{path.stem}')
print(f'root: {root}, ext: {path.suffix}'# root: /dir0/dir1/somefile, ext: .txt


拡張子が複数ある場合

 拡張子が複数ある場合にもsplitext関数やPath.parent/Path.stem/Path.suffix属性を使うだけで、複数の拡張子とそれ以外の部分に分割できるかというとそうではない。

 以下はsplitext関数を使った場合の例だ。パスには「'/dir0/dir1/somefile.tar.gz'」を指定している。

path = '/dir0/dir1/somefile.tar.gz'
root, ext = splitext(path)
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile.tar, ext: .gz


 このときには拡張子として認識されるのは最後の「.gz」だけだ。これはPathクラスのsuffix属性についても同様だ。

path = Path(path)
root = Path(f'{path.parent}/{path.stem}')
ext = path.suffix
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile.tar, ext: .gz


 このパスを「.tar.gz」とそれ以外の部分に分割したいとしよう。

 以下はsplitext関数を使ってこれを行う関数の例だ。

def mysplitext(path):
    root, ext = splitext(path)
    exts = ext
    while ext != '':
        root, ext = splitext(root)
        exts = ext + exts
    return root, exts


 基本的な考え方は、拡張子とそれ以外の部分に分割できる間はsplitext関数を呼び続けて、取り出した拡張子を結合していき、最後にその他の部分と結合された拡張子を返送するというものだ。

 実行結果を以下に示す。

root, ext = mysplitext(path)
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile, ext: .tar.gz


 2つある拡張子(.tar.gz)とそれ以外に分割できたことが確認できる。

 一方、Pathクラスを使用する場合には、suffixes属性という便利な属性がある。

print(path.suffixes)  # ['.tar', '.gz']


 この属性はパスを構成する最終要素に含まれる拡張子を要素とするリストを表す。これを「''.join(path.suffixes)」のようにすれば拡張子('.tar.gz')を得られる。このこととstem属性が(末尾の)拡張子よりも前の部分を表すことを組み合わせると、以下のようなコードが考えられる。

def mysplitext(path):
    parent = str(path.parent)
    parent = '' if parent == '.' else parent
    tmp = path.stem
    while True:
        stem = Path(tmp).stem
        if stem == tmp:
            break
        tmp = stem
    suffixes = ''.join(path.suffixes)
    root = f'{parent}/{stem}' if parent else stem
    return root, suffixes


 基本的な考え方は、stem属性を何度か呼び出して「somefile.tar.gz」→「somefile.tar」→「somefile」のように最終的に拡張子以外の部分を得て、それと「''.join(path.suffixes)」の結果を結合しようというものだ(関数の前後にある細々とした部分はsplitext関数で得られる結果と同じ結果が得られるようにするためのもの)。もっと別な実装もあるだろうが、ここではこのようにしている(例えば、suffix属性が空になるまでループをしていく方法)。

 実行結果を以下に示す。

root, ext = mysplitext(path)
print(f'root: {root}, ext: {ext}'# root: /dir0/dir1/somefile, ext: .tar.gz


 こちらでも2つの拡張子とそれ以外の部分に分割できることが確認できた。

「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。