@@ -204,11 +204,10 @@ def _select_from(self, parent_path, scandir):
204
204
class _PathParents (Sequence ):
205
205
"""This object provides sequence-like access to the logical ancestors
206
206
of a path. Don't try to construct it yourself."""
207
- __slots__ = ('_pathcls ' , '_drv' , '_root' , '_tail' )
207
+ __slots__ = ('_path ' , '_drv' , '_root' , '_tail' )
208
208
209
209
def __init__ (self , path ):
210
- # We don't store the instance to avoid reference cycles
211
- self ._pathcls = type (path )
210
+ self ._path = path
212
211
self ._drv = path .drive
213
212
self ._root = path .root
214
213
self ._tail = path ._tail
@@ -224,11 +223,11 @@ def __getitem__(self, idx):
224
223
raise IndexError (idx )
225
224
if idx < 0 :
226
225
idx += len (self )
227
- return self ._pathcls ._from_parsed_parts (self ._drv , self ._root ,
228
- self ._tail [:- idx - 1 ])
226
+ return self ._path ._from_parsed_parts (self ._drv , self ._root ,
227
+ self ._tail [:- idx - 1 ])
229
228
230
229
def __repr__ (self ):
231
- return "<{}.parents>" .format (self ._pathcls .__name__ )
230
+ return "<{}.parents>" .format (type ( self ._path ) .__name__ )
232
231
233
232
234
233
class PurePath (object ):
@@ -316,6 +315,13 @@ def __init__(self, *args):
316
315
else :
317
316
self ._raw_path = self ._flavour .join (* paths )
318
317
318
+ def with_segments (self , * pathsegments ):
319
+ """Construct a new path object from any number of path-like objects.
320
+ Subclasses may override this method to customize how new path objects
321
+ are created from methods like `iterdir()`.
322
+ """
323
+ return type (self )(* pathsegments )
324
+
319
325
@classmethod
320
326
def _parse_path (cls , path ):
321
327
if not path :
@@ -342,15 +348,14 @@ def _load_parts(self):
342
348
self ._root = root
343
349
self ._tail_cached = tail
344
350
345
- @classmethod
346
- def _from_parsed_parts (cls , drv , root , tail ):
347
- path = cls ._format_parsed_parts (drv , root , tail )
348
- self = cls (path )
349
- self ._str = path or '.'
350
- self ._drv = drv
351
- self ._root = root
352
- self ._tail_cached = tail
353
- return self
351
+ def _from_parsed_parts (self , drv , root , tail ):
352
+ path_str = self ._format_parsed_parts (drv , root , tail )
353
+ path = self .with_segments (path_str )
354
+ path ._str = path_str or '.'
355
+ path ._drv = drv
356
+ path ._root = root
357
+ path ._tail_cached = tail
358
+ return path
354
359
355
360
@classmethod
356
361
def _format_parsed_parts (cls , drv , root , tail ):
@@ -584,8 +589,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False):
584
589
"scheduled for removal in Python {remove}" )
585
590
warnings ._deprecated ("pathlib.PurePath.relative_to(*args)" , msg ,
586
591
remove = (3 , 14 ))
587
- path_cls = type (self )
588
- other = path_cls (other , * _deprecated )
592
+ other = self .with_segments (other , * _deprecated )
589
593
for step , path in enumerate ([other ] + list (other .parents )):
590
594
if self .is_relative_to (path ):
591
595
break
@@ -594,7 +598,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False):
594
598
if step and not walk_up :
595
599
raise ValueError (f"{ str (self )!r} is not in the subpath of { str (other )!r} " )
596
600
parts = ['..' ] * step + self ._tail [len (path ._tail ):]
597
- return path_cls (* parts )
601
+ return self . with_segments (* parts )
598
602
599
603
def is_relative_to (self , other , / , * _deprecated ):
600
604
"""Return True if the path is relative to another path or False.
@@ -605,7 +609,7 @@ def is_relative_to(self, other, /, *_deprecated):
605
609
"scheduled for removal in Python {remove}" )
606
610
warnings ._deprecated ("pathlib.PurePath.is_relative_to(*args)" ,
607
611
msg , remove = (3 , 14 ))
608
- other = type ( self ) (other , * _deprecated )
612
+ other = self . with_segments (other , * _deprecated )
609
613
return other == self or other in self .parents
610
614
611
615
@property
@@ -617,13 +621,13 @@ def parts(self):
617
621
else :
618
622
return tuple (self ._tail )
619
623
620
- def joinpath (self , * args ):
624
+ def joinpath (self , * pathsegments ):
621
625
"""Combine this path with one or several arguments, and return a
622
626
new path representing either a subpath (if all arguments are relative
623
627
paths) or a totally different path (if one of the arguments is
624
628
anchored).
625
629
"""
626
- return self .__class__ (self , * args )
630
+ return self .with_segments (self , * pathsegments )
627
631
628
632
def __truediv__ (self , key ):
629
633
try :
@@ -633,7 +637,7 @@ def __truediv__(self, key):
633
637
634
638
def __rtruediv__ (self , key ):
635
639
try :
636
- return type ( self ) (key , self )
640
+ return self . with_segments (key , self )
637
641
except TypeError :
638
642
return NotImplemented
639
643
@@ -650,6 +654,8 @@ def parent(self):
650
654
@property
651
655
def parents (self ):
652
656
"""A sequence of this path's logical parents."""
657
+ # The value of this property should not be cached on the path object,
658
+ # as doing so would introduce a reference cycle.
653
659
return _PathParents (self )
654
660
655
661
def is_absolute (self ):
@@ -680,7 +686,7 @@ def match(self, path_pattern):
680
686
"""
681
687
Return True if this path matches the given pattern.
682
688
"""
683
- pat = type ( self ) (path_pattern )
689
+ pat = self . with_segments (path_pattern )
684
690
if not pat .parts :
685
691
raise ValueError ("empty pattern" )
686
692
pat_parts = pat ._parts_normcase
@@ -755,7 +761,7 @@ def _make_child_relpath(self, name):
755
761
path_str = f'{ path_str } { name } '
756
762
else :
757
763
path_str = name
758
- path = type ( self ) (path_str )
764
+ path = self . with_segments (path_str )
759
765
path ._str = path_str
760
766
path ._drv = self .drive
761
767
path ._root = self .root
@@ -805,7 +811,7 @@ def samefile(self, other_path):
805
811
try :
806
812
other_st = other_path .stat ()
807
813
except AttributeError :
808
- other_st = self .__class__ (other_path ).stat ()
814
+ other_st = self .with_segments (other_path ).stat ()
809
815
return self ._flavour .samestat (st , other_st )
810
816
811
817
def iterdir (self ):
@@ -867,7 +873,7 @@ def absolute(self):
867
873
cwd = self ._flavour .abspath (self .drive )
868
874
else :
869
875
cwd = os .getcwd ()
870
- return type ( self ) (cwd , self )
876
+ return self . with_segments (cwd , self )
871
877
872
878
def resolve (self , strict = False ):
873
879
"""
@@ -885,7 +891,7 @@ def check_eloop(e):
885
891
except OSError as e :
886
892
check_eloop (e )
887
893
raise
888
- p = type ( self ) (s )
894
+ p = self . with_segments (s )
889
895
890
896
# In non-strict mode, realpath() doesn't raise on symlink loops.
891
897
# Ensure we get an exception by calling stat()
@@ -975,7 +981,7 @@ def readlink(self):
975
981
"""
976
982
if not hasattr (os , "readlink" ):
977
983
raise NotImplementedError ("os.readlink() not available on this system" )
978
- return type ( self ) (os .readlink (self ))
984
+ return self . with_segments (os .readlink (self ))
979
985
980
986
def touch (self , mode = 0o666 , exist_ok = True ):
981
987
"""
@@ -1064,7 +1070,7 @@ def rename(self, target):
1064
1070
Returns the new Path instance pointing to the target path.
1065
1071
"""
1066
1072
os .rename (self , target )
1067
- return self .__class__ (target )
1073
+ return self .with_segments (target )
1068
1074
1069
1075
def replace (self , target ):
1070
1076
"""
@@ -1077,7 +1083,7 @@ def replace(self, target):
1077
1083
Returns the new Path instance pointing to the target path.
1078
1084
"""
1079
1085
os .replace (self , target )
1080
- return self .__class__ (target )
1086
+ return self .with_segments (target )
1081
1087
1082
1088
def symlink_to (self , target , target_is_directory = False ):
1083
1089
"""
0 commit comments