LoginSignup
11
8

More than 5 years have passed since last update.

Pathlibのabsoluteとresolve

Last updated at Posted at 2018-08-14

TL;DR

from pathlib import Path

fp = Path()

# 以下の違いとは?
fp.resolve()
fp.absolute()

結論として、
* resolve: 絶対パスを作成する
* absolute: 絶対パスを取得する

的な違いがあるようです。

Introduction

DjangoなどのFWを使うにしても、機械学習系の開発をするにしても、ディレクトリのプロジェクトルートを取得して、必要なPathをある程度先に定義して使うというケースは多いと思います。
たとえば、

settings.py
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のドキュメントには記載がないです。(なんでだろう...?)

ということで、中身を読みます。

pathlib.py
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なので、出力の想定はそれに合わせます。

pathlib
fp = Path('../')

fp.absolute() 
-> PosixPath('/Users/username/..')

fp.resolve() 
-> PosixPath('/Users')

確かに。resolveの方がPathとしての見通しはよいかもしれません。
実際に、コマンドラインで打つときは、absoluteの方が馴染みがありますね。

コードからみると

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をはっつけて、初期化すると言う感じ

resolve
    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

全部追うのは頭が疲れちゃうのでやめました笑
シンボリックリンクを色々解消してくれるメソッドっぽいですね。
そこで作成されたパスを使って、標準化してパスを作成してくれるメソッドの模様です。

おわりに

これが気になって、今朝目が覚めてしまったので解消されてスッキリしています。
結構適当に書いちゃったので、読み手には難しい文章になっているかもしれませんがご容赦ください。

おわり

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8