diff -r 97ab0ccac893 Lib/pathlib.py --- a/Lib/pathlib.py Wed Jan 06 11:37:52 2016 -0800 +++ b/Lib/pathlib.py Wed Jan 06 14:26:47 2016 -0800 @@ -28,7 +28,7 @@ __all__ = [ "PurePath", "PurePosixPath", "PureWindowsPath", - "Path", "PosixPath", "WindowsPath", + "Path", "PosixPath", "WindowsPath", "StatCache", ] # @@ -425,6 +425,35 @@ _normal_accessor = _NormalAccessor() +class StatCache(_NormalAccessor): + """An alternative accessor that caches [l]stat() results.""" + + def __init__(self): + self._stat_cache = {} + self._lstat_cache = {} + + # NOTE: Overriding static methods [l]stat() with instance methods. + + def stat(self, pathobj): + self.lstat(pathobj) # Populate one or both caches + path = str(pathobj) + st = self._stat_cache.get(path) + if st is None: + st = os.stat(path) + self._stat_cache[path] = st + return st + + def lstat(self, pathobj): + path = str(pathobj) + st = self._lstat_cache.get(path) + if st is None: + st = os.lstat(path) + self._lstat_cache[path] = st + if not S_ISLNK(st.st_mode): + self._stat_cache[path] = st + return st + + # # Globbing helpers # @@ -571,11 +600,12 @@ class _PathParents(Sequence): """This object provides sequence-like access to the logical ancestors of a path. Don't try to construct it yourself.""" - __slots__ = ('_pathcls', '_drv', '_root', '_parts') + __slots__ = ('_pathcls', '_drv', '_root', '_parts', '_accessor') - def __init__(self, path): + def __init__(self, path, accessor): # We don't store the instance to avoid reference cycles self._pathcls = type(path) + self._accessor = accessor self._drv = path._drv self._root = path._root self._parts = path._parts @@ -589,8 +619,10 @@ def __getitem__(self, idx): if idx < 0 or idx >= len(self): raise IndexError(idx) - return self._pathcls._from_parsed_parts(self._drv, self._root, - self._parts[:-idx - 1]) + item = self._pathcls._from_parsed_parts(self._drv, self._root, + self._parts[:-idx - 1], init=False) + item._init(accessor=self._accessor) + return item def __repr__(self): return "<{}.parents>".format(self._pathcls.__name__) @@ -607,6 +639,7 @@ '_drv', '_root', '_parts', '_str', '_hash', '_pparts', '_cached_cparts', ) + _accessor = None # Set in non-pure Path instances def __new__(cls, *args): """Construct a PurePath from one or several strings and or existing @@ -670,7 +703,7 @@ else: return cls._flavour.join(parts) - def _init(self): + def _init(self, template=None, accessor=None): # Overriden in concrete Path pass @@ -678,7 +711,9 @@ drv, root, parts = self._parse_args(args) drv, root, parts = self._flavour.join_parsed_parts( self._drv, self._root, self._parts, drv, root, parts) - return self._from_parsed_parts(drv, root, parts) + child = self._from_parsed_parts(drv, root, parts, init=False) + child._init(template=self) + return child def __str__(self): """Return the string representation of the path, suitable for @@ -815,8 +850,10 @@ if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep] or drv or root or len(parts) != 1): raise ValueError("Invalid name %r" % (name)) - return self._from_parsed_parts(self._drv, self._root, - self._parts[:-1] + [name]) + new = self._from_parsed_parts(self._drv, self._root, + self._parts[:-1] + [name], init=False) + new._init(template=self) + return new def with_suffix(self, suffix): """Return a new path with the file suffix changed (or added, if none).""" @@ -834,8 +871,10 @@ name = name + suffix else: name = name[:-len(old_suffix)] + suffix - return self._from_parsed_parts(self._drv, self._root, - self._parts[:-1] + [name]) + new = self._from_parsed_parts(self._drv, self._root, + self._parts[:-1] + [name], init=False) + new._init(template=self) + return new def relative_to(self, *other): """Return the relative path to another path identified by the passed @@ -866,8 +905,10 @@ formatted = self._format_parsed_parts(to_drv, to_root, to_parts) raise ValueError("{!r} does not start with {!r}" .format(str(self), str(formatted))) - return self._from_parsed_parts('', root if n == 1 else '', - abs_parts[n:]) + new = self._from_parsed_parts('', root if n == 1 else '', + abs_parts[n:], init=False) + new._init(template=self) + return new @property def parts(self): @@ -893,7 +934,9 @@ return self._make_child((key,)) def __rtruediv__(self, key): - return self._from_parts([key] + self._parts) + new = self._from_parts([key] + self._parts, init=False) + new._init(template=self) + return new @property def parent(self): @@ -903,12 +946,14 @@ parts = self._parts if len(parts) == 1 and (drv or root): return self - return self._from_parsed_parts(drv, root, parts[:-1]) + parent = self._from_parsed_parts(drv, root, parts[:-1], init=False) + parent._init(template=self) + return parent @property def parents(self): """A sequence of this path's logical parents.""" - return _PathParents(self) + return _PathParents(self, self._accessor) def is_absolute(self): """True if the path is absolute (has both a root and, if applicable, @@ -974,15 +1019,24 @@ if not self._flavour.is_supported: raise NotImplementedError("cannot instantiate %r on your system" % (cls.__name__,)) - self._init() + stat_cache = kwargs.get('stat_cache') + if stat_cache is not None: + if not isinstance(stat_cache, StatCache): + raise TypeError('stat_cache must be StatCache, not %s' % + stat_cache.__class__.__name__) + self._init(accessor=stat_cache) return self def _init(self, - # Private non-constructor arguments + # Private non-constructor arguments (pass at most one) template=None, + accessor=None, ): self._closed = False - if template is not None: + if accessor is not None: + assert template is None + self._accessor = accessor + elif template is not None: self._accessor = template._accessor else: self._accessor = _normal_accessor @@ -991,7 +1045,9 @@ # This is an optimization used for dir walking. `part` must be # a single part relative to this path. parts = self._parts + [part] - return self._from_parsed_parts(self._drv, self._root, parts) + child = self._from_parsed_parts(self._drv, self._root, parts, init=False) + child._init(template=self) + return child def __enter__(self): if self._closed: @@ -1410,7 +1466,9 @@ if (not (self._drv or self._root) and self._parts and self._parts[0][:1] == '~'): homedir = self._flavour.gethomedir(self._parts[0][1:]) - return self._from_parts([homedir] + self._parts[1:]) + new = self._from_parts([homedir] + self._parts[1:], init=False) + new._init(template=self) + return new return self