os.pathモジュールが提供するabspath関数で特定のパスの絶対パスを取得する方法や、その際に注意する点、pathlib.Path.absoluteメソッドとの振る舞いの違いなどを紹介する。
from os import getcwd, chdir
from os.path import abspath, join, normpath, realpath, islink
# 現在の作業ディレクトリを基点とした絶対パスを取得
cwd = getcwd()
print(cwd)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code
# Windows:C:\tmp\pytips\pytips_0198\code
p = abspath('foo.txt')
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/foo.txt
# Windows:C:\tmp\pytips\pytips_0198\code\foo.txt
tmp = normpath(join(getcwd(), 'foo.txt'))
print(tmp)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/foo.txt
# Windows:C:\tmp\pytips\pytips_0198\code\foo.txt
print(p == tmp) # True
p = abspath('../../pytips_0197/pytips_0197.txt')
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0197/pytips_0197.txt
# Windows:C:\tmp\pytips\pytips_0197\pytips_0197.txt
# シンボリックリンクを含むパスの絶対パスを取得
print(islink('dir1')) # True
chdir('dir1')
print(getcwd())
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/dir0
# Windows:C:\tmp\pytips\pytips_0198\code\dir1
p = abspath('../dir1/cdir')
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/dir1/cdir
# Windows:C:\tmp\pytips\pytips_0198\code\dir1\cdir
# pathlibモジュールのPath.absoluteメソッドとは挙動が異なることがある
from pathlib import Path
path = Path('../dir1/cdir')
p = path.absolute()
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/dir0/../dir1/cdir
# Windows:C:\tmp\pytips\pytips_0198\code\dir1\..\dir1\cdir
# シンボリックリンクを含むパスから正規の絶対パスを取得
chdir(cwd)
p = realpath('dir1/cdir')
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/dir0/cdir
# Windows:C:\tmp\pytips\pytips_0198\code\dir0\cdir
os.pathモジュールが提供するabspath関数を使うと、その関数に指定したパスの絶対パスを取得できる。ここではmacOSとWindowsを例として、その使い方を見ていく。
macOSでは次のようなディレクトリ階層を作成した。
このうち、dir1ディレクトリはdir0ディレクトリに対するシンボリックリンクになっている。Windowsでも同様なディレクトリ階層を作成している。
こちらでもdir1ディレクトリはdir0ディレクトリに対するシンボリックリンクになっている(管理者権限でコマンドプロンプトを実行し、「mklink /D」コマンドで作成)。
また、osモジュールおよびos.pathモジュールから以下の関数をインポートした。
from os import getcwd, chdir
from os.path import abspath, join, normpath, realpath, islink
Pythonのスクリプトを実行する作業ディレクトリはpytips_0198/codeディレクトリとする(後でディレクトリを移動する)。
cwd = getcwd()
print(cwd)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code
# Windows:C:\tmp\pytips\pytips_0198\code
macOSの出力結果は「/private/tmp/pytips/pytips_0198/code」となっているが、これは「/tmp/……」と読み替えてほしい(/tmpディレクトリが/private/tmpディレクトリへのシンボリックリンクになっているため)。
既に述べたが、abspath関数は渡したパスの絶対パスを返す。簡単な例を以下に示す。
p = abspath('foo.txt')
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/foo.txt
# Windows:C:\tmp\pytips\pytips_0198\code\foo.txt
ここではpytips_0198/codeディレクトリに存在しないファイルのパス「foo.txt」を渡しているが問題はない。この場合は、現在の作業ディレクトリ(os.getcwd関数で得られるパス)と、abspath関数渡したパスを連結したものが絶対パスとして戻される。
os.path.abspath関数のドキュメントにもあるが、これはnormpath(join(getcwd(), 'foo.txt'))を呼び出したのと同じ結果になる。
tmp = normpath(join(getcwd(), 'foo.txt'))
print(tmp)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/foo.txt
# Windows:C:\tmp\pytips\pytips_0198\code\foo.txt
print(p == tmp) # True
ここで使っているnormpath関数とjoin関数についても簡単に見ておこう。join関数は1つ以上のパスを連結した結果を返す関数だ。ただし、渡した引数の中でルートとなる要素があった場合、その要素よりも前の要素については捨てられる点には注意しよう。以下に例を示す。
p = join('foo', 'bar')
print(p)
# 出力結果:
# macOS:foo/bar
# Windows:foo\bar
p = join('foo', '/bar', 'baz')
print(p)
# 出力結果:
# macOS:/bar/baz
# Windows:/bar\baz
Windows環境では異なるドライブ文字を含んだ場合も同様にそれ以前の要素は無視される(以下の例では先頭の'foo'はCドライブのカレントディレクトリにある'foo'というパスを意味するが、次の要素が'd:bar'なので無視される。なお、このd:barはDドライブのカレントディレクトリにあるbarというパスを表すだけで、Dドライブのルートディレクトリにあるbarというパスを表しているわけではないことにも注意)。
p = join('foo', 'd:bar', 'baz')
print(p) # d:bar\baz
normpath関数はパスを正規化する関数だ。ここでいう「正規化」とはカレントディレクトリを表すドット「.」や親ディレクトリを表す2つのドット「..」などを解決したり、連続するスラッシュを単一のスラッシュとしたりする処理だと考えればよい。
p = normpath('foo//bar/../baz.txt')
print(p)
# 出力結果:
# macOS:foo/baz.txt
# Windows:foo\baz.txt
この例ではfooとbarの間のダブルスラッシュが単一のスラッシュに変換されるとともに、親ディレクトリを意味する「..」が解決された結果、fooディレクトリの下にあるbaz.txtファイルを意味するパスが返された。
normpath関数による正規化が行われるので、abspath関数に「..」を含むパスを与えるとそれが正規化されたものが返される。以下に例を示す。
p = abspath('../../pytips_0197/pytips_0197.txt')
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0197/pytips_0197.txt
# Windows:C:\tmp\pytips\pytips_0197\pytips_0197.txt
現在の作業ディレクトリは「/tmp/pytips/pytips_0198/code」ディレクトリなので、abspath関数に渡した「../../pytips_0197/pytips_0197.txt」というパスはcodeディレクトリの上の上のディレクトリ(pytipsディレクトリ)にあるpytips_0197ディレクトリにあるpytips_0197.txtファイルを意味し、実際にそのパスが得られている。
パスの一部(ディレクトリ)にシンボリックリンクが含まれている場合には注意が必要になる。冒頭で述べたようにdir1ディレクトリはdir0ディレクトリに対するシンボリックリンクとなっている。これを例に見てみよう。
print(islink('dir1')) # True
chdir('dir1')
print(getcwd())
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/dir0
# Windows:C:\tmp\pytips\pytips_0198\code\dir1
この例ではos.path.islink関数でdir1ディレクトリがシンボリックリンクであることを確認し、そこに作業ディレクトリを移動している。macOSではその結果、リンク先であるdir0ディレクトリに移動したことが分かる(Windowsではdir1ディレクトリが得られている。OSによる挙動の異なりであり、シンボリックリンクを扱う場合には注意が必要になるかもしれない)。
ここで親ディレクトリにあるdir1ディレクトリ(とは現在の作業ディレクトリのことだ)にあるcdirというパスの絶対パスを得てみよう。
p = abspath('../dir1/cdir')
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/dir1/cdir
# Windows:C:\tmp\pytips\pytips_0198\code\dir1\cdir
「..」を含むパスが正規化され、ここではdir0という要素が出てくることなく、dir1を含むパスが得られた。これでも問題はないだろうが、シンボリックリンクを解決したパスが必要なときには以下で紹介するos.path.realpath関数を使う必要がある。また、正規化を含む振る舞いは、pathlibモジュールのPathクラスで同様な処理を行うPath.absoluteメソッドとは異なる点にも注意すること。以下は同じことをPath.absoluteメソッドで試した結果だ。
from pathlib import Path
path = Path('../dir1/cdir')
p = path.absolute()
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/dir0/../dir1/cdir
# Windows:C:\tmp\pytips\pytips_0198\code\dir1\..\dir1\cdir
absoluteメソッドでは「..」が解決されずに、それを含んだままの絶対パスが戻されている。こちらの方が望ましい情報を含んでいるという場合には、abspath関数ではなく、Path.absoluteメソッドを使うのがよいだろう。
最後にシンボリックリンクを含むパスから、それを解決した正規の絶対パスをrealpath関数で取得する例も示しておこう。
chdir(cwd)
p = realpath('dir1/cdir')
print(p)
# 出力結果:
# macOS:/private/tmp/pytips/pytips_0198/code/dir0/cdir
# Windows:C:\tmp\pytips\pytips_0198\code\dir0\cdir
Copyright© Digital Advantage Corp. All Rights Reserved.