TL;DR
from pathlib import Path
fp = Path()
# 以下の違いとは?
fp.resolve()
fp.absolute()
結論として、
- resolve: 絶対パスを作成する
- absolute: 絶対パスを取得する
的な違いがあるようです。
Introduction
DjangoなどのFWを使うにしても、機械学習系の開発をするにしても、ディレクトリのプロジェクトルートを取得して、必要なPathをある程度先に定義して使うというケースは多いと思います。
たとえば、
import os
from pathlib import Path
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
PROJECT_ROOT = Path(__file__).resolve().parent
BATCH_DIR = PROJECT_ROOT.joinpath('batch')
APP_DIR = PROJECT_ROOT.joinpath('app')
...
os.pathを書くよりも便利なPathlibですが、絶対パスの取得でabsolute, resolveどっちを使ったらええねんという疑問がありました。
ちなみに、absoluteは実装されているんですが、pathlibのドキュメントには記載がないです。(なんでだろう...?)
ということで、中身を読みます。
class Path(PurePath):
"""PurePath subclass that can make system calls.
Path represents a filesystem path but unlike PurePath, also offers
methods to do system calls on path objects. Depending on your system,
instantiating a Path will return either a PosixPath or a WindowsPath
object. You can also instantiate a PosixPath or WindowsPath directly,
but cannot instantiate a WindowsPath on a POSIX system or vice versa.
"""
__slots__ = (
'_accessor',
'_closed',
)
...
def absolute(self):
"""Return an absolute version of this path. This function works
even if the path doesn't point to anything.
No normalization is done, i.e. all '.' and '..' will be kept along.
Use resolve() to get the canonical path to a file.
"""
# XXX untested yet!
if self._closed:
self._raise_closed()
if self.is_absolute():
return self
# FIXME this must defer to the specific flavour (and, under Windows,
# use nt._getfullpathname())
obj = self._from_parts([os.getcwd()] + self._parts, init=False)
obj._init(template=self)
return obj
def resolve(self, strict=False):
"""
Make the path absolute, resolving all symlinks on the way and also
normalizing it (for example turning slashes into backslashes under
Windows).
"""
if self._closed:
self._raise_closed()
s = self._flavour.resolve(self, strict=strict)
if s is None:
# No symlink resolution => for consistency, raise an error if
# the path doesn't exist or is forbidden
self.stat()
s = str(self.absolute())
# Now we have no symlinks in the path, it's safe to normalize it.
normed = self._flavour.pathmod.normpath(s)
obj = self._from_parts((normed,), init=False)
obj._init(template=self)
return obj
まずは、Docstringからみます。
Docstringの違い
<absolute>
Return an absolute version of this path.
This function works even if the path doesn't point to anything.
No normalization is done, i.e. all '.' and '..' will be kept along.
Use resolve() to get the canonical path to a file.
<resolve>
Make the path absolute, resolving all symlinks on the way and also normalizing it
(for example turning slashes into backslashes under Windows).
absoluteの方は、絶対パスを返す模様です。
パスが何もさしていなくても動くようで、'..'
みたいな記載をしたら、それがそのままPosixPath('..')
になります。
resolveは、絶対パスを作成するようです。
シンボリックリンクを解消して、'..'
みたいな記載をしても標準パスを返してくれます。
例を見ます。僕のマシンはMacなので、出力の想定はそれに合わせます。
fp = Path('../')
fp.absolute()
-> PosixPath('/Users/username/..')
fp.resolve()
-> PosixPath('/Users')
確かに。resolveの方がPathとしての見通しはよいかもしれません。
実際に、コマンドラインで打つときは、absoluteの方が馴染みがありますね。
コードからみると
def absolute(self):
if self._closed:
self._raise_closed()
if self.is_absolute():
return self
# FIXME this must defer to the specific flavour (and, under Windows,
# use nt._getfullpathname())
obj = self._from_parts([os.getcwd()] + self._parts, init=False)
obj._init(template=self)
return obj
全体的なイメージとしては、カレントディレクトリに、Path('xxx')のxxxをはっつけて、初期化すると言う感じ
def resolve(self):
if self._closed:
self._raise_closed()
s = self._flavour.resolve(self, strict=strict)
if s is None:
# No symlink resolution => for consistency, raise an error if
# the path doesn't exist or is forbidden
self.stat()
s = str(self.absolute())
# Now we have no symlinks in the path, it's safe to normalize it.
normed = self._flavour.pathmod.normpath(s)
obj = self._from_parts((normed,), init=False)
obj._init(template=self)
return obj
self._flavour.resolve(self, strict=strict)が肝な模様。
class _PosixFlavour(_Flavour):
sep = '/'
altsep = ''
has_drv = False
pathmod = posixpath
is_supported = (os.name != 'nt')
def resolve(self, path, strict=False):
sep = self.sep
accessor = path._accessor
seen = {}
def _resolve(path, rest):
if rest.startswith(sep):
path = ''
for name in rest.split(sep):
if not name or name == '.':
# current dir
continue
if name == '..':
# parent dir
path, _, _ = path.rpartition(sep)
continue
newpath = path + sep + name
if newpath in seen:
# Already seen this path
path = seen[newpath]
if path is not None:
# use cached value
continue
# The symlink is not resolved, so we must have a symlink loop.
raise RuntimeError("Symlink loop from %r" % newpath)
# Resolve the symbolic link
try:
target = accessor.readlink(newpath)
except OSError as e:
if e.errno != EINVAL and strict:
raise
# Not a symlink, or non-strict mode. We just leave the path
# untouched.
path = newpath
else:
seen[newpath] = None # not resolved symlink
path = _resolve(path, target)
seen[newpath] = path # resolved symlink
return path
# NOTE: according to POSIX, getcwd() cannot contain path components
# which are symlinks.
base = '' if path.is_absolute() else os.getcwd()
return _resolve(base, str(path)) or sep
全部追うのは頭が疲れちゃうのでやめました笑
シンボリックリンクを色々解消してくれるメソッドっぽいですね。
そこで作成されたパスを使って、標準化してパスを作成してくれるメソッドの模様です。
おわりに
これが気になって、今朝目が覚めてしまったので解消されてスッキリしています。
結構適当に書いちゃったので、読み手には難しい文章になっているかもしれませんがご容赦ください。
おわり