Skip to content

Commit 2dbb2e0

Browse files
authored
GH-110109: Churn pathlib.PurePath methods (#112012)
Re-arrange `pathlib.PurePath` methods in source code. No other changes. The `PurePath` implementations of certain special methods, such as `__eq__()` and `__hash__()`, are not usually applicable to user subclasses of `_PathBase`. To facilitate their removal, another patch will split the `PurePath` class into `_PurePathBase` and `PurePath`, with the latter providing these special methods. This patch prepares the ground for splitting `PurePath`. It's similar to e8d77b0, which preceded splitting `Path`. By churning the methods here, subsequent patches will be easier to review and less likely to break things.
1 parent 7c50800 commit 2dbb2e0

File tree

2 files changed

+204
-204
lines changed

2 files changed

+204
-204
lines changed

Lib/pathlib.py

+120-120
Original file line numberDiff line numberDiff line change
@@ -246,44 +246,6 @@ class PurePath:
246246
)
247247
pathmod = os.path
248248

249-
def __new__(cls, *args, **kwargs):
250-
"""Construct a PurePath from one or several strings and or existing
251-
PurePath objects. The strings and path objects are combined so as
252-
to yield a canonicalized path, which is incorporated into the
253-
new PurePath object.
254-
"""
255-
if cls is PurePath:
256-
cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
257-
return object.__new__(cls)
258-
259-
def __reduce__(self):
260-
# Using the parts tuple helps share interned path parts
261-
# when pickling related paths.
262-
return (self.__class__, self.parts)
263-
264-
def __init__(self, *args):
265-
paths = []
266-
for arg in args:
267-
if isinstance(arg, PurePath):
268-
if arg.pathmod is ntpath and self.pathmod is posixpath:
269-
# GH-103631: Convert separators for backwards compatibility.
270-
paths.extend(path.replace('\\', '/') for path in arg._raw_paths)
271-
else:
272-
paths.extend(arg._raw_paths)
273-
else:
274-
try:
275-
path = os.fspath(arg)
276-
except TypeError:
277-
path = arg
278-
if not isinstance(path, str):
279-
raise TypeError(
280-
"argument should be a str or an os.PathLike "
281-
"object where __fspath__ returns a str, "
282-
f"not {type(path).__name__!r}")
283-
paths.append(path)
284-
self._raw_paths = paths
285-
self._resolving = False
286-
287249
def with_segments(self, *pathsegments):
288250
"""Construct a new path object from any number of path-like objects.
289251
Subclasses may override this method to customize how new path objects
@@ -351,96 +313,14 @@ def __str__(self):
351313
self._tail) or '.'
352314
return self._str
353315

354-
def __fspath__(self):
355-
return str(self)
356-
357316
def as_posix(self):
358317
"""Return the string representation of the path with forward (/)
359318
slashes."""
360319
return str(self).replace(self.pathmod.sep, '/')
361320

362-
def __bytes__(self):
363-
"""Return the bytes representation of the path. This is only
364-
recommended to use under Unix."""
365-
return os.fsencode(self)
366-
367321
def __repr__(self):
368322
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
369323

370-
def as_uri(self):
371-
"""Return the path as a URI."""
372-
if not self.is_absolute():
373-
raise ValueError("relative path can't be expressed as a file URI")
374-
375-
drive = self.drive
376-
if len(drive) == 2 and drive[1] == ':':
377-
# It's a path on a local drive => 'file:///c:/a/b'
378-
prefix = 'file:///' + drive
379-
path = self.as_posix()[2:]
380-
elif drive:
381-
# It's a path on a network drive => 'file://host/share/a/b'
382-
prefix = 'file:'
383-
path = self.as_posix()
384-
else:
385-
# It's a posix path => 'file:///etc/hosts'
386-
prefix = 'file://'
387-
path = str(self)
388-
from urllib.parse import quote_from_bytes
389-
return prefix + quote_from_bytes(os.fsencode(path))
390-
391-
@property
392-
def _str_normcase(self):
393-
# String with normalized case, for hashing and equality checks
394-
try:
395-
return self._str_normcase_cached
396-
except AttributeError:
397-
if _is_case_sensitive(self.pathmod):
398-
self._str_normcase_cached = str(self)
399-
else:
400-
self._str_normcase_cached = str(self).lower()
401-
return self._str_normcase_cached
402-
403-
@property
404-
def _parts_normcase(self):
405-
# Cached parts with normalized case, for comparisons.
406-
try:
407-
return self._parts_normcase_cached
408-
except AttributeError:
409-
self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep)
410-
return self._parts_normcase_cached
411-
412-
def __eq__(self, other):
413-
if not isinstance(other, PurePath):
414-
return NotImplemented
415-
return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod
416-
417-
def __hash__(self):
418-
try:
419-
return self._hash
420-
except AttributeError:
421-
self._hash = hash(self._str_normcase)
422-
return self._hash
423-
424-
def __lt__(self, other):
425-
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
426-
return NotImplemented
427-
return self._parts_normcase < other._parts_normcase
428-
429-
def __le__(self, other):
430-
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
431-
return NotImplemented
432-
return self._parts_normcase <= other._parts_normcase
433-
434-
def __gt__(self, other):
435-
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
436-
return NotImplemented
437-
return self._parts_normcase > other._parts_normcase
438-
439-
def __ge__(self, other):
440-
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
441-
return NotImplemented
442-
return self._parts_normcase >= other._parts_normcase
443-
444324
@property
445325
def drive(self):
446326
"""The drive prefix (letter or UNC path), if any."""
@@ -694,6 +574,126 @@ def match(self, path_pattern, *, case_sensitive=None):
694574
match = _compile_pattern(pattern_str, sep, case_sensitive)
695575
return match(str(self)) is not None
696576

577+
def __new__(cls, *args, **kwargs):
578+
"""Construct a PurePath from one or several strings and or existing
579+
PurePath objects. The strings and path objects are combined so as
580+
to yield a canonicalized path, which is incorporated into the
581+
new PurePath object.
582+
"""
583+
if cls is PurePath:
584+
cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
585+
return object.__new__(cls)
586+
587+
def __init__(self, *args):
588+
paths = []
589+
for arg in args:
590+
if isinstance(arg, PurePath):
591+
if arg.pathmod is ntpath and self.pathmod is posixpath:
592+
# GH-103631: Convert separators for backwards compatibility.
593+
paths.extend(path.replace('\\', '/') for path in arg._raw_paths)
594+
else:
595+
paths.extend(arg._raw_paths)
596+
else:
597+
try:
598+
path = os.fspath(arg)
599+
except TypeError:
600+
path = arg
601+
if not isinstance(path, str):
602+
raise TypeError(
603+
"argument should be a str or an os.PathLike "
604+
"object where __fspath__ returns a str, "
605+
f"not {type(path).__name__!r}")
606+
paths.append(path)
607+
self._raw_paths = paths
608+
self._resolving = False
609+
610+
def __reduce__(self):
611+
# Using the parts tuple helps share interned path parts
612+
# when pickling related paths.
613+
return (self.__class__, self.parts)
614+
615+
def __fspath__(self):
616+
return str(self)
617+
618+
def __bytes__(self):
619+
"""Return the bytes representation of the path. This is only
620+
recommended to use under Unix."""
621+
return os.fsencode(self)
622+
623+
@property
624+
def _str_normcase(self):
625+
# String with normalized case, for hashing and equality checks
626+
try:
627+
return self._str_normcase_cached
628+
except AttributeError:
629+
if _is_case_sensitive(self.pathmod):
630+
self._str_normcase_cached = str(self)
631+
else:
632+
self._str_normcase_cached = str(self).lower()
633+
return self._str_normcase_cached
634+
635+
def __hash__(self):
636+
try:
637+
return self._hash
638+
except AttributeError:
639+
self._hash = hash(self._str_normcase)
640+
return self._hash
641+
642+
def __eq__(self, other):
643+
if not isinstance(other, PurePath):
644+
return NotImplemented
645+
return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod
646+
647+
@property
648+
def _parts_normcase(self):
649+
# Cached parts with normalized case, for comparisons.
650+
try:
651+
return self._parts_normcase_cached
652+
except AttributeError:
653+
self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep)
654+
return self._parts_normcase_cached
655+
656+
def __lt__(self, other):
657+
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
658+
return NotImplemented
659+
return self._parts_normcase < other._parts_normcase
660+
661+
def __le__(self, other):
662+
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
663+
return NotImplemented
664+
return self._parts_normcase <= other._parts_normcase
665+
666+
def __gt__(self, other):
667+
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
668+
return NotImplemented
669+
return self._parts_normcase > other._parts_normcase
670+
671+
def __ge__(self, other):
672+
if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
673+
return NotImplemented
674+
return self._parts_normcase >= other._parts_normcase
675+
676+
def as_uri(self):
677+
"""Return the path as a URI."""
678+
if not self.is_absolute():
679+
raise ValueError("relative path can't be expressed as a file URI")
680+
681+
drive = self.drive
682+
if len(drive) == 2 and drive[1] == ':':
683+
# It's a path on a local drive => 'file:///c:/a/b'
684+
prefix = 'file:///' + drive
685+
path = self.as_posix()[2:]
686+
elif drive:
687+
# It's a path on a network drive => 'file://host/share/a/b'
688+
prefix = 'file:'
689+
path = self.as_posix()
690+
else:
691+
# It's a posix path => 'file:///etc/hosts'
692+
prefix = 'file://'
693+
path = str(self)
694+
from urllib.parse import quote_from_bytes
695+
return prefix + quote_from_bytes(os.fsencode(path))
696+
697697

698698
# Subclassing os.PathLike makes isinstance() checks slower,
699699
# which in turn makes Path construction slower. Register instead!

0 commit comments

Comments
 (0)