@@ -204,11 +204,10 @@ def _select_from(self, parent_path, scandir):
204204class _PathParents (Sequence ):
205205 """This object provides sequence-like access to the logical ancestors
206206 of a path. Don't try to construct it yourself."""
207- __slots__ = ('_pathcls ' , '_drv' , '_root' , '_tail' )
207+ __slots__ = ('_path ' , '_drv' , '_root' , '_tail' )
208208
209209 def __init__ (self , path ):
210- # We don't store the instance to avoid reference cycles
211- self ._pathcls = type (path )
210+ self ._path = path
212211 self ._drv = path .drive
213212 self ._root = path .root
214213 self ._tail = path ._tail
@@ -224,11 +223,11 @@ def __getitem__(self, idx):
224223 raise IndexError (idx )
225224 if idx < 0 :
226225 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 ])
229228
230229 def __repr__ (self ):
231- return "<{}.parents>" .format (self ._pathcls .__name__ )
230+ return "<{}.parents>" .format (type ( self ._path ) .__name__ )
232231
233232
234233class PurePath (object ):
@@ -316,6 +315,13 @@ def __init__(self, *args):
316315 else :
317316 self ._raw_path = self ._flavour .join (* paths )
318317
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+
319325 @classmethod
320326 def _parse_path (cls , path ):
321327 if not path :
@@ -342,15 +348,14 @@ def _load_parts(self):
342348 self ._root = root
343349 self ._tail_cached = tail
344350
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
354359
355360 @classmethod
356361 def _format_parsed_parts (cls , drv , root , tail ):
@@ -584,8 +589,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False):
584589 "scheduled for removal in Python {remove}" )
585590 warnings ._deprecated ("pathlib.PurePath.relative_to(*args)" , msg ,
586591 remove = (3 , 14 ))
587- path_cls = type (self )
588- other = path_cls (other , * _deprecated )
592+ other = self .with_segments (other , * _deprecated )
589593 for step , path in enumerate ([other ] + list (other .parents )):
590594 if self .is_relative_to (path ):
591595 break
@@ -594,7 +598,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False):
594598 if step and not walk_up :
595599 raise ValueError (f"{ str (self )!r} is not in the subpath of { str (other )!r} " )
596600 parts = ['..' ] * step + self ._tail [len (path ._tail ):]
597- return path_cls (* parts )
601+ return self . with_segments (* parts )
598602
599603 def is_relative_to (self , other , / , * _deprecated ):
600604 """Return True if the path is relative to another path or False.
@@ -605,7 +609,7 @@ def is_relative_to(self, other, /, *_deprecated):
605609 "scheduled for removal in Python {remove}" )
606610 warnings ._deprecated ("pathlib.PurePath.is_relative_to(*args)" ,
607611 msg , remove = (3 , 14 ))
608- other = type ( self ) (other , * _deprecated )
612+ other = self . with_segments (other , * _deprecated )
609613 return other == self or other in self .parents
610614
611615 @property
@@ -617,13 +621,13 @@ def parts(self):
617621 else :
618622 return tuple (self ._tail )
619623
620- def joinpath (self , * args ):
624+ def joinpath (self , * pathsegments ):
621625 """Combine this path with one or several arguments, and return a
622626 new path representing either a subpath (if all arguments are relative
623627 paths) or a totally different path (if one of the arguments is
624628 anchored).
625629 """
626- return self .__class__ (self , * args )
630+ return self .with_segments (self , * pathsegments )
627631
628632 def __truediv__ (self , key ):
629633 try :
@@ -633,7 +637,7 @@ def __truediv__(self, key):
633637
634638 def __rtruediv__ (self , key ):
635639 try :
636- return type ( self ) (key , self )
640+ return self . with_segments (key , self )
637641 except TypeError :
638642 return NotImplemented
639643
@@ -650,6 +654,8 @@ def parent(self):
650654 @property
651655 def parents (self ):
652656 """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.
653659 return _PathParents (self )
654660
655661 def is_absolute (self ):
@@ -680,7 +686,7 @@ def match(self, path_pattern):
680686 """
681687 Return True if this path matches the given pattern.
682688 """
683- pat = type ( self ) (path_pattern )
689+ pat = self . with_segments (path_pattern )
684690 if not pat .parts :
685691 raise ValueError ("empty pattern" )
686692 pat_parts = pat ._parts_normcase
@@ -755,7 +761,7 @@ def _make_child_relpath(self, name):
755761 path_str = f'{ path_str } { name } '
756762 else :
757763 path_str = name
758- path = type ( self ) (path_str )
764+ path = self . with_segments (path_str )
759765 path ._str = path_str
760766 path ._drv = self .drive
761767 path ._root = self .root
@@ -805,7 +811,7 @@ def samefile(self, other_path):
805811 try :
806812 other_st = other_path .stat ()
807813 except AttributeError :
808- other_st = self .__class__ (other_path ).stat ()
814+ other_st = self .with_segments (other_path ).stat ()
809815 return self ._flavour .samestat (st , other_st )
810816
811817 def iterdir (self ):
@@ -867,7 +873,7 @@ def absolute(self):
867873 cwd = self ._flavour .abspath (self .drive )
868874 else :
869875 cwd = os .getcwd ()
870- return type ( self ) (cwd , self )
876+ return self . with_segments (cwd , self )
871877
872878 def resolve (self , strict = False ):
873879 """
@@ -885,7 +891,7 @@ def check_eloop(e):
885891 except OSError as e :
886892 check_eloop (e )
887893 raise
888- p = type ( self ) (s )
894+ p = self . with_segments (s )
889895
890896 # In non-strict mode, realpath() doesn't raise on symlink loops.
891897 # Ensure we get an exception by calling stat()
@@ -975,7 +981,7 @@ def readlink(self):
975981 """
976982 if not hasattr (os , "readlink" ):
977983 raise NotImplementedError ("os.readlink() not available on this system" )
978- return type ( self ) (os .readlink (self ))
984+ return self . with_segments (os .readlink (self ))
979985
980986 def touch (self , mode = 0o666 , exist_ok = True ):
981987 """
@@ -1064,7 +1070,7 @@ def rename(self, target):
10641070 Returns the new Path instance pointing to the target path.
10651071 """
10661072 os .rename (self , target )
1067- return self .__class__ (target )
1073+ return self .with_segments (target )
10681074
10691075 def replace (self , target ):
10701076 """
@@ -1077,7 +1083,7 @@ def replace(self, target):
10771083 Returns the new Path instance pointing to the target path.
10781084 """
10791085 os .replace (self , target )
1080- return self .__class__ (target )
1086+ return self .with_segments (target )
10811087
10821088 def symlink_to (self , target , target_is_directory = False ):
10831089 """
0 commit comments