"""Common operations on Posix pathnames.Instead of importing this module directly, import os and refer tothis module as os.path. The "os.path" name is an alias for thismodule on Posix systems; on other systems (e.g. Mac, Windows),os.path provides the same operations in a manner specific to thatplatform, and is an alias to another module (e.g. macpath, ntpath).Some of this can actually be useful on non-Posix systems too, e.g.for manipulation of the pathname component of URLs."""# Strings representing various path-related bits and pieces.# These are primarily for export; internally, they are hardcoded.# Should be set before imports for resolving cyclic dependency.curdir='.'pardir='..'extsep='.'sep='/'pathsep=':'defpath=':/bin:/usr/bin'altsep=Nonedevnull='/dev/null'importosimportsysimportstatimportgenericpathfromgenericpathimport*__all__=["normcase","isabs","join","splitdrive","split","splitext","basename","dirname","commonprefix","getsize","getmtime","getatime","getctime","islink","exists","lexists","isdir","isfile","ismount","expanduser","expandvars","normpath","abspath","samefile","sameopenfile","samestat","curdir","pardir","sep","pathsep","defpath","altsep","extsep","devnull","realpath","supports_unicode_filenames","relpath","commonpath"]def_get_sep(path):ifisinstance(path,bytes):returnb'/'else:return'/'# Normalize the case of a pathname. Trivial in Posix, string.lower on Mac.# On MS-DOS this may also turn slashes into backslashes; however, other# normalizations (such as optimizing '../' away) are not allowed# (another function should be defined to do that).defnormcase(s):"""Normalize case of pathname. Has no effect under Posix"""s=os.fspath(s)ifnotisinstance(s,(bytes,str)):raiseTypeError("normcase() argument must be str or bytes, ""not '{}'".format(s.__class__.__name__))returns# Return whether a path is absolute.# Trivial in Posix, harder on the Mac or MS-DOS.defisabs(s):"""Test whether a path is absolute"""s=os.fspath(s)sep=_get_sep(s)returns.startswith(sep)# Join pathnames.# Ignore the previous parts if a part is absolute.# Insert a '/' unless the first part is empty or already ends in '/'.defjoin(a,*p):"""Join two or more pathname components, inserting '/' as needed. If any component is an absolute path, all previous path components will be discarded. An empty last part will result in a path that ends with a separator."""a=os.fspath(a)sep=_get_sep(a)path=atry:ifnotp:path[:0]+sep#23780: Ensure compatible data type even if p is null.forbinmap(os.fspath,p):ifb.startswith(sep):path=belifnotpathorpath.endswith(sep):path+=belse:path+=sep+bexcept(TypeError,AttributeError,BytesWarning):genericpath._check_arg_types('join',a,*p)raisereturnpath# Split a path in head (everything up to the last '/') and tail (the# rest). If the path ends in '/', tail will be empty. If there is no# '/' in the path, head will be empty.# Trailing '/'es are stripped from head unless it is the root.defsplit(p):"""Split a pathname. Returns tuple "(head, tail)" where "tail" is everything after the final slash. Either part may be empty."""p=os.fspath(p)sep=_get_sep(p)i=p.rfind(sep)+1head,tail=p[:i],p[i:]ifheadandhead!=sep*len(head):head=head.rstrip(sep)returnhead,tail# Split a path in root and extension.# The extension is everything starting at the last dot in the last# pathname component; the root is everything before that.# It is always true that root + ext == p.defsplitext(p):p=os.fspath(p)ifisinstance(p,bytes):sep=b'/'extsep=b'.'else:sep='/'extsep='.'returngenericpath._splitext(p,sep,None,extsep)splitext.__doc__=genericpath._splitext.__doc__# Split a pathname into a drive specification and the rest of the# path. Useful on DOS/Windows/NT; on Unix, the drive is always empty.defsplitdrive(p):"""Split a pathname into drive and path. On Posix, drive is always empty."""p=os.fspath(p)returnp[:0],p# Return the tail (basename) part of a path, same as split(path)[1].defbasename(p):"""Returns the final component of a pathname"""p=os.fspath(p)sep=_get_sep(p)i=p.rfind(sep)+1returnp[i:]# Return the head (dirname) part of a path, same as split(path)[0].
[docs]defdirname(p):"""Returns the directory component of a pathname"""p=os.fspath(p)sep=_get_sep(p)i=p.rfind(sep)+1head=p[:i]ifheadandhead!=sep*len(head):head=head.rstrip(sep)returnhead
# Is a path a symbolic link?# This will always return false on systems where os.lstat doesn't exist.defislink(path):"""Test whether a path is a symbolic link"""try:st=os.lstat(path)except(OSError,AttributeError):returnFalsereturnstat.S_ISLNK(st.st_mode)# Being true for dangling symbolic links is also useful.deflexists(path):"""Test whether a path exists. Returns True for broken symbolic links"""try:os.lstat(path)exceptOSError:returnFalsereturnTrue# Is a path a mount point?# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)defismount(path):"""Test whether a path is a mount point"""try:s1=os.lstat(path)exceptOSError:# It doesn't exist -- so not a mount point. :-)returnFalseelse:# A symlink can never be a mount pointifstat.S_ISLNK(s1.st_mode):returnFalseifisinstance(path,bytes):parent=join(path,b'..')else:parent=join(path,'..')parent=realpath(parent)try:s2=os.lstat(parent)exceptOSError:returnFalsedev1=s1.st_devdev2=s2.st_devifdev1!=dev2:returnTrue# path/.. on a different device as pathino1=s1.st_inoino2=s2.st_inoifino1==ino2:returnTrue# path/.. is the same i-node as pathreturnFalse# Expand paths beginning with '~' or '~user'.# '~' means $HOME; '~user' means that user's home directory.# If the path doesn't begin with '~', or if the user or $HOME is unknown,# the path is returned unchanged (leaving error reporting to whatever# function is called with the expanded path as argument).# See also module 'glob' for expansion of *, ? and [...] in pathnames.# (A function should also be defined to do full *sh-style environment# variable expansion.)defexpanduser(path):"""Expand ~ and ~user constructions. If user or $HOME is unknown, do nothing."""path=os.fspath(path)ifisinstance(path,bytes):tilde=b'~'else:tilde='~'ifnotpath.startswith(tilde):returnpathsep=_get_sep(path)i=path.find(sep,1)ifi<0:i=len(path)ifi==1:if'HOME'notinos.environ:importpwduserhome=pwd.getpwuid(os.getuid()).pw_direlse:userhome=os.environ['HOME']else:importpwdname=path[1:i]ifisinstance(name,bytes):name=str(name,'ASCII')try:pwent=pwd.getpwnam(name)exceptKeyError:returnpathuserhome=pwent.pw_dirifisinstance(path,bytes):userhome=os.fsencode(userhome)root=b'/'else:root='/'userhome=userhome.rstrip(root)return(userhome+path[i:])orroot# Expand paths containing shell variable substitutions.# This expands the forms $variable and ${variable} only.# Non-existent variables are left unchanged._varprog=None_varprogb=Nonedefexpandvars(path):"""Expand shell variables of form $var and ${var}. Unknown variables are left unchanged."""path=os.fspath(path)global_varprog,_varprogbifisinstance(path,bytes):ifb'$'notinpath:returnpathifnot_varprogb:importre_varprogb=re.compile(br'\$(\w+|\{[^}]*\})',re.ASCII)search=_varprogb.searchstart=b'{'end=b'}'environ=getattr(os,'environb',None)else:if'$'notinpath:returnpathifnot_varprog:importre_varprog=re.compile(r'\$(\w+|\{[^}]*\})',re.ASCII)search=_varprog.searchstart='{'end='}'environ=os.environi=0whileTrue:m=search(path,i)ifnotm:breaki,j=m.span(0)name=m.group(1)ifname.startswith(start)andname.endswith(end):name=name[1:-1]try:ifenvironisNone:value=os.fsencode(os.environ[os.fsdecode(name)])else:value=environ[name]exceptKeyError:i=jelse:tail=path[j:]path=path[:i]+valuei=len(path)path+=tailreturnpath# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.# It should be understood that this may change the meaning of the path# if it contains symbolic links!defnormpath(path):"""Normalize path, eliminating double slashes, etc."""path=os.fspath(path)ifisinstance(path,bytes):sep=b'/'empty=b''dot=b'.'dotdot=b'..'else:sep='/'empty=''dot='.'dotdot='..'ifpath==empty:returndotinitial_slashes=path.startswith(sep)# POSIX allows one or two initial slashes, but treats three or more# as single slash.if(initial_slashesandpath.startswith(sep*2)andnotpath.startswith(sep*3)):initial_slashes=2comps=path.split(sep)new_comps=[]forcompincomps:ifcompin(empty,dot):continueif(comp!=dotdotor(notinitial_slashesandnotnew_comps)or(new_compsandnew_comps[-1]==dotdot)):new_comps.append(comp)elifnew_comps:new_comps.pop()comps=new_compspath=sep.join(comps)ifinitial_slashes:path=sep*initial_slashes+pathreturnpathordotdefabspath(path):"""Return an absolute path."""path=os.fspath(path)ifnotisabs(path):ifisinstance(path,bytes):cwd=os.getcwdb()else:cwd=os.getcwd()path=join(cwd,path)returnnormpath(path)# Return a canonical path (i.e. the absolute location of a file on the# filesystem).defrealpath(filename):"""Return the canonical path of the specified filename, eliminating anysymbolic links encountered in the path."""filename=os.fspath(filename)path,ok=_joinrealpath(filename[:0],filename,{})returnabspath(path)# Join two paths, normalizing and eliminating any symbolic links# encountered in the second path.def_joinrealpath(path,rest,seen):ifisinstance(path,bytes):sep=b'/'curdir=b'.'pardir=b'..'else:sep='/'curdir='.'pardir='..'ifisabs(rest):rest=rest[1:]path=sepwhilerest:name,_,rest=rest.partition(sep)ifnotnameorname==curdir:# current dircontinueifname==pardir:# parent dirifpath:path,name=split(path)ifname==pardir:path=join(path,pardir,pardir)else:path=pardircontinuenewpath=join(path,name)ifnotislink(newpath):path=newpathcontinue# Resolve the symbolic linkifnewpathinseen:# Already seen this pathpath=seen[newpath]ifpathisnotNone:# use cached valuecontinue# The symlink is not resolved, so we must have a symlink loop.# Return already resolved part + rest of the path unchanged.returnjoin(newpath,rest),Falseseen[newpath]=None# not resolved symlinkpath,ok=_joinrealpath(path,os.readlink(newpath),seen)ifnotok:returnjoin(path,rest),Falseseen[newpath]=path# resolved symlinkreturnpath,Truesupports_unicode_filenames=(sys.platform=='darwin')defrelpath(path,start=None):"""Return a relative version of a path"""ifnotpath:raiseValueError("no path specified")path=os.fspath(path)ifisinstance(path,bytes):curdir=b'.'sep=b'/'pardir=b'..'else:curdir='.'sep='/'pardir='..'ifstartisNone:start=curdirelse:start=os.fspath(start)try:start_list=[xforxinabspath(start).split(sep)ifx]path_list=[xforxinabspath(path).split(sep)ifx]# Work out how much of the filepath is shared by start and path.i=len(commonprefix([start_list,path_list]))rel_list=[pardir]*(len(start_list)-i)+path_list[i:]ifnotrel_list:returncurdirreturnjoin(*rel_list)except(TypeError,AttributeError,BytesWarning,DeprecationWarning):genericpath._check_arg_types('relpath',path,start)raise# Return the longest common sub-path of the sequence of paths given as input.# The paths are not normalized before comparing them (this is the# responsibility of the caller). Any trailing separator is stripped from the# returned path.defcommonpath(paths):"""Given a sequence of path names, returns the longest common sub-path."""ifnotpaths:raiseValueError('commonpath() arg is an empty sequence')paths=tuple(map(os.fspath,paths))ifisinstance(paths[0],bytes):sep=b'/'curdir=b'.'else:sep='/'curdir='.'try:split_paths=[path.split(sep)forpathinpaths]try:isabs,=set(p[:1]==sepforpinpaths)exceptValueError:raiseValueError("Can't mix absolute and relative paths")fromNonesplit_paths=[[cforcinsifcandc!=curdir]forsinsplit_paths]s1=min(split_paths)s2=max(split_paths)common=s1fori,cinenumerate(s1):ifc!=s2[i]:common=s1[:i]breakprefix=sepifisabselsesep[:0]returnprefix+sep.join(common)except(TypeError,AttributeError):genericpath._check_arg_types('commonpath',*paths)raise