
def makedirs(name, mode=0o777, exist_ok=False):
head, tail = path.split(name)
if not tail:
head, tail = path.split(head)
if head and tail and not path.exists(head):
try:
makedirs(head, exist_ok=exist_ok)
except FileExistsError:
# Defeats race condition when another thread created the path
pass
cdir = curdir
if isinstance(tail, bytes):
cdir = bytes(curdir, 'ASCII')
if tail == cdir:
return
try:
mkdir(name, mode)
except OSError:
if not exist_ok or not path.isdir(name):
raise
makedirs函数:递归目录创建函数。这是对mkdir的封装,其功能是一次性创建一个子目录和所有的中间目录。类似linux命令里面 mkdir -p(或–parents) 的功能。传入的 name 为文件路径(不能包含包含 pardir ,如 UNIX 系统中的 “..”); mode为数字权限默认0o777(基本没法改); exist_ok 当设为 False (默认值)时会直接抛出 FileExistsError 异常,设为 True 时会判断传入 name 是否为现有路径,若不是则抛出 FileExistsError 异常(总感觉这个设置的有点奇怪,也不清楚是不是我解读的问题)。
path.split()函数:这个函数的作用是将传入的路径进行分割,以最后一个“/”为分界线,分别赋值给head和tail。

当tail为空时再分割一次,考虑到传入路径结尾为”/“的情况。
path.exists()函数:该函数用于判断当前路径是否存在,若存在则返回True,否则返回False。在这里python利用异常捕获机制不断的递归调用maksdirs函数,直到文件路径不存在引发异常报错。相对于使用if…elif…else来判断,代码的执行效率更高了。
curdir函数:用于获取当前执行python文件的文件夹,注意获取的是当前执行python文件的文件夹,而不是python文件所在的文件夹。curdir会返回一个”.“(表示这个表示当前路径),所以此刻cdir变量内储存的是”.” 当前执行python文件的路径。

isinstance()函数:判断一个对象是否是一个已知的类型,类似 type(),但其会考虑到继承关系这一点与type()不相同。这里用于判断tail和bytes(字节,二进制数据流类型) 是否类型相同,是则返回True。当tail为nce()函数:判断一个对象是否是一个已知的类型,类似 type(),但其会考虑到继承关系这一点与type()不相同。这里用于判断tail和bytes(字节,二进制数据流类型) 是否类型相同,是则返回True。当tail为字节类型的时候,以”ASCII“编码方式将curdir对象重新创造一个bytes类对象。
这里一定要判断tail为字节类型的原因是,在Python3中bytes和str类型有着严格的区分。
Python 3最重要的新特性之一是对字符串和二进制数据流做了明确的区分。文本总是 Unicode,由 str 类型表示,二进制数据则由 bytes 类型表示。Python 3 不会以任意隐式的方式混用 str 和 bytes ,你不能拼接字符串和字节流,也无法在字节流里搜索字符串(反之亦然),也不能将字符串传入参数为字节流的函数(反之亦然)。
传入的文件路径既可能是 str 类型也可能是 bytes 类型。虽然bytes类型 ”D:\pythons\Jupyter“ 和str类型 ”D:\pythons\Jupyter“ 存储的是相同的路径,但是两者并不相同。为了使程序能正常return跳出递归,而不继续运行导致mkdir报错。可见开发这一块,细节决定成败。(tail始终存的是单个目录名啊,怎么可能和路径名相同。这里我想破脑壳都没想懂,以后弄明白了再来修改吧。这是我是根据前后文推断的,功能上因该没啥问题。)

mkdir()函数:数字权限创建name目录方法,可能会抛出异常 OSError (当name目录存在的时候),但是会被捕获。
当捕获OSError后,判断exist_ok属性和name是否为已存在目录(path.isdir()函数功能,若存在则返回True)。如果exist_ok为False或者name为不存在的目录,则抛出 FiloExistsError 异常(前面利用异常检测机制进行的递归可以用上了)。
raise函数:单独一个 raise。该语句引发当前上下文中捕获的异常(比如在 except 块中),或默认引发 RuntimeError 异常。在这里会抛出 FiloExistsError 异常。
下面是我绘制的一个流程图,可能因为我对python异常处理机制的不够理解,存在一定的误差,不过大体上应该是相似的。(请忽视我那智障的递归表示,赣)

def removedirs(name):
rmdir(name)
head, tail = path.split(name)
if not tail:
head, tail = path.split(head)
while head and tail:
try:
rmdir(head)
except OSError:
break
head, tail = path.split(head)
removedirs()函数:递归删除目录函数。先对传入的目录删除一次,如果删除失败会由rmdir引发报错。然后对目录进行拆分,排除传入目录最后以“/”结尾情况。然后向前删除空目录,当删除的目录不是空目录时报错退出。显然该函数只能用于向前删除空目录至非空目录为止,用到的地方大概不会多了。
rmdir(path)函数:移除(删除)目录 path。如果目录不存在或不为空,则会分别抛出 FileNotFoundError 或 OSError 异常。
def renames(old, new):
head, tail = path.split(new)
if head and tail and not path.exists(head):
makedirs(head)
rename(old, new)
head, tail = path.split(old)
if head and tail:
try:
removedirs(head)
except OSError:
pass
__all__.extend(["makedirs", "removedirs", "renames"])
renames()函数:递归重命名目录或文件函数。采用先创建后删除的方式。old :要重命名的目录,new :文件或目录的新名字。甚至可以是包含在目录中的文件,或者完整的目录树。
该函数先拆分新目录,然后调用 makedirs()函数(前面讲过的)创建目录。再调用raname()函数将 old 重命名为 new 。再拆分old,调用 removedirs() 函数(前面讲过的)移除原目录。
rename(src,dst)函数:将文件或目录 src 重命名为 dst。如果 dst 已存在,则一定情况下将会操作失败,并抛出 OSError 的子类。(这里省略了一部分,如果想要了解更多,请移步python3.9官方文档)
我认为有必要再 rename() 函数那增加一个异常捕获机制,因为这个函数本身是先调用 makedirs() 创建新目录,然后再重命名,最后再调用 removedirs() 函数删除原路径。(不太清楚这个rename的意义何在,等想通了在完整表述。)rename是会引发异常的,如果在运行过程中发生异常,虽然程序终止了,但是 makedirs() 创建的目录最为中间件却是存在了的。同时,这个函数本身并不安全,它可以在禁用 mkdir 的情况下,用来创建文件夹。

def walk(top, topdown=True, onerror=None, followlinks=False):
sys.audit("os.walk", top, topdown, onerror, followlinks)
return _walk(fspath(top), topdown, onerror, followlinks)
def _walk(top, topdown, onerror, followlinks):
dirs = []
nondirs = []
walk_dirs = []
try:
scandir_it = scandir(top)
except OSError as error:
if onerror is not None:
onerror(error)
return
with scandir_it:
while True:
try:
try:
entry = next(scandir_it)
except StopIteration:
break
except OSError as error:
if onerror is not None:
onerror(error)
return
try:
is_dir = entry.is_dir()
except OSError:
is_dir = False
if is_dir:
dirs.append(entry.name)
else:
nondirs.append(entry.name)
if not topdown and is_dir:
# Bottom-up: recurse into sub-directory, but exclude symlinks to
# directories if followlinks is False
if followlinks:
walk_into = True
else:
try:
is_symlink = entry.is_symlink()
except OSError:
is_symlink = False
walk_into = not is_symlink
if walk_into:
walk_dirs.append(entry.path)
if topdown:
yield top, dirs, nondirs
islink, join = path.islink, path.join
for dirname in dirs:
new_path = join(top, dirname)
if followlinks or not islink(new_path):
yield from _walk(new_path, topdown, onerror, followlinks)
else:
for new_path in walk_dirs:
yield from _walk(new_path, topdown, onerror, followlinks)
yield top, dirs, nondirs
__all__.append("walk")
python3.9的 os.walk() 函数实现与前面python版本存在些许不同,3.9中将大量内容写到了另一个os._walk() 的函数中,进行了一次重写。
os.walk(top, topdown=True, onerror=None, followlinks=False)函数:生成目录树中的文件名,方式是按上->下或下->上顺序浏览目录树。对于以 top 为根的目录树中的每个目录(包括 top 本身),它都会生成一个三元组 (dirpath, dirnames, filenames)。
top:需要遍历的目录的地址,返回一个三元组(dirpath, dirnames, filenames)。
dirpath:指当前遍历的这个文件夹本身的地址,字符串
dirnames:一个list,内容是top文件子目录名称组成的列表。
filennames:一个list,是该文件中非目录文件名称组成的列表。
topdown:默认为True,可选。当为True时优先遍历根目录,否则先遍历子目录(感觉像深度遍历)。无论 topdown 为何值,在生成目录及其子目录的元组之前,都将检索全部子目录列表。


onerror:需要一个callable对象,可选。当walk需要异常时调用。默认将忽略 scandir() 调用中的错误。
followlinks:walk() 默认不会递归进指向目录的符号链接。可以在支持符号链接的系统上将followlinks 设置为 True,以访问符号链接指向的目录。也就是当其值为True时会遍历目录下的快捷方式(linux 下是软连接 symbolic link )实际所指的目录(默认关闭),如果为 False,则优先遍历 top 的子目录。
sys.audit():引发一个审计事件并触发任何激活的审计钩子。(暂时不明白这个地方的作用,不过这个应该和walk重构有很大原因)
fspath()函数:返回路径的文件系统表示。确保传入的是 str 或 bytes 类型的字符串。如果不是将抛出 TypeError 异常。
walk() 先借助 sys.audit() 触发监听,以实现多次调用 walk()。然后再返回 _walk() 函数的结果。
_walk()函数:传入参数与walk相同。是对先前版本 walk() 函数的大部分功能的重新整合。
scandir(path)函数:返回一个 os.DirEntry 对象的迭代器,它们对应于由 path 指定目录中的条目。在这里使用它获取top的DirEntry迭代器对象,储存在scandir_it中。它非常轻巧方便。例如用scandir函数遍历当前目录。

在Python 3.5版本中,新添加了os.scandir()方法,它是一个目录迭代方法。你可以在PEP 471中找到关于它的一些内容。在Python 3.5中,os.walk是使用os.scandir来实现的,根据Python 3.5宣称,它在POSIX系统中,执行速度提高了3到5倍;在Windows系统中,执行速度提高了7到20倍。它返回的是一个DirEntry对象,用它的name属性获取到文件名,以及is_dir方法判断是文件还是目录。
程序通过is_dir()等方法判断归类到不同的列表中,然后再根据topdown参数的值决定是否递归到子目录,如果followlinks为False则将符号链接排除。注意符号链接并不是快捷方式,两者功能类似,但并不相同。
符号链接与快捷方式:快捷方式(shortcut)是一种功能上类似符号链接的文件对象,但与符号链接有本质的不同。快捷方式是普通的文件(拥有扩展名 .lnk),而非符号,因此,快捷方式可以被复制、移动、更改(某些特殊的快捷方式无法更改所有信息)或删除。快捷方式可以指向文件、文件夹或其他任何系统中合法的位置(包括控制面板、桌面等)。
当 scandir() 函数触发异常后,判断 onerror 是否为空,如果非空(有callable对象),就将异常作为参数传给 callable 对象进行处理。
由于scandir_it是一个迭代器,所以使用with语句进行上下文管理。(for的执行时间太长了,不如with简洁)。其中 entry.is_dir() 表示是否为文件夹,返回 True 或者 False ;entry.name 返回当前对象名称;entry.is_symlink() 表示对象是否为符号链接,返回 True 或者 False ;entry.path 返回当前对象的地址。

_walk函数中对迭代对象处理的一部分程序的流程图(排列有点乱)。可以供大家理清一下思路。
再判断_walk是否优先遍历top目录,这一段的作用就是以递归的方式对根或者子目录进行访问。到这里我们可以很明显的看出 walk() 函数其实是对 scandir() 函数的一个封装,牺牲了其的效率以换取更大的便利性。我认为,当在开发中使用时,应直接使用 scandir() 函数,毕竟它是用C写的。
再说回来,回到上文中提到以递归的方式对根或者子目录进行访问。首先是判断是否优先遍历根目录,若是则先调用 yield 产生三个generator。
(简单地讲,yield 的作用就是把一个函数变成一个 generator (生成器),带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator。也就是将 top、dirs、nondirs 转化成generator。)
islink, join = path.islink, path.join :表示将 path.islink(path)(判断目录path是否存在)保存到 islink 中,将 path.join(path,new)(将path与new连接起来)保存到join中。
然后再从 dirs 列表中取出目录名,将其和它们的根目录连接起来,形成新的链接。再确定运行访问符号链接或者不是已存在路径,调用 yields from 语句迭代调用处理 _walk()。
若是优先访问子目录,就从 walk_dirs 列表中取出子目录路径, 调用 yields from 语句迭代调用处理 _walk()。调用完后再调用 yields 将 top、dirs、nondirs 标记为生成器对象。
关于 yields from 语句的解读,大家可以去看这篇文章:深入理解Python的yield from语法 。
if {open, stat} <= supports_dir_fd and {scandir, stat} <= supports_fd:
def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=None):
sys.audit("os.fwalk", top, topdown, onerror, follow_symlinks, dir_fd)
if not isinstance(top, int) or not hasattr(top, '__index__'):
top = fspath(top)
if not follow_symlinks:
orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
topfd = open(top, O_RDONLY, dir_fd=dir_fd)
try:
if (follow_symlinks or (st.S_ISDIR(orig_st.st_mode) and
path.samestat(orig_st, stat(topfd)))):
yield from _fwalk(topfd, top, isinstance(top, bytes),
topdown, onerror, follow_symlinks)
finally:
close(topfd)
def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks):
scandir_it = scandir(topfd)
dirs = []
nondirs = []
entries = None if topdown or follow_symlinks else []
for entry in scandir_it:
name = entry.name
if isbytes:
name = fsencode(name)
try:
if entry.is_dir():
dirs.append(name)
if entries is not None:
entries.append(entry)
else:
nondirs.append(name)
except OSError:
try:
if entry.is_symlink():
nondirs.append(name)
except OSError:
pass
if topdown:
yield toppath, dirs, nondirs, topfd
for name in dirs if entries is None else zip(dirs, entries):
try:
if not follow_symlinks:
if topdown:
orig_st = stat(name, dir_fd=topfd, follow_symlinks=False)
else:
assert entries is not None
name, entry = name
orig_st = entry.stat(follow_symlinks=False)
dirfd = open(name, O_RDONLY, dir_fd=topfd)
except OSError as err:
if onerror is not None:
onerror(err)
continue
try:
if follow_symlinks or path.samestat(orig_st, stat(dirfd)):
dirpath = path.join(toppath, name)
yield from _fwalk(dirfd, dirpath, isbytes,
topdown, onerror, follow_symlinks)
finally:
close(dirfd)
if not topdown:
yield toppath, dirs, nondirs, topfd
__all__.append("fwalk")
接下来的 fwalk() 函数与上面的 walk() 函数行为完全一样,除了它产生的是 4 元组 (dirpath, dirnames, filenames, dirfd),并且它支持 dir_fd(也就是是指向目录 dirpath 的文件描述符)。我就不多加以解释了,即使存在的一部分不同,也是很小的。
值得一提的是在 .nt (也就是Windows) 平台下没有 fwalk() 函数,但是当我用pycharm进行编辑的时候是有提示 fwalk() 函数的,这也算是一个小坑吧。jupyter就没有这个补全。
def execl(file, *args):
execv(file, args)
def execle(file, *args):
env = args[-1]
execve(file, args[:-1], env)
def execlp(file, *args):
execvp(file, args)
def execlpe(file, *args):
env = args[-1]
execvpe(file, args[:-1], env)
def execvp(file, args):
_execvpe(file, args)
def execvpe(file, args, env):
_execvpe(file, args, env)
__all__.extend(["execl","execle","execlp","execlpe","execvp","execvpe"])
def _execvpe(file, args, env=None):
if env is not None:
exec_func = execve
argrest = (args, env)
else:
exec_func = execv
argrest = (args,)
env = environ
if path.dirname(file):
exec_func(file, *argrest)
return
saved_exc = None
path_list = get_exec_path(env)
if name != 'nt':
file = fsencode(file)
path_list = map(fsencode, path_list)
for dir in path_list:
fullname = path.join(dir, file)
try:
exec_func(fullname, *argrest)
except (FileNotFoundError, NotADirectoryError) as e:
last_exc = e
except OSError as e:
last_exc = e
if saved_exc is None:
saved_exc = e
if saved_exc is not None:
raise saved_exc
raise last_exc
os.exec*() :这几个函数就不一一具体说明了。这些函数都是对应的C API的python实现。
这些函数都将执行一个新程序,以替换当前进程。它们没有返回值。在 Unix 上,新程序会加载到当前进程中,且进程号与调用者相同。过程中的错误会被报告为 OSError 异常。 当前进程会被立即替换。打开的文件对象和描述符都不会刷新,因此如果这些文件上可能缓冲了数据,则应在调用 exec* 函数之前使用 sys.stdout.flush() 或 os.fsync() 刷新它们。具体解释请移步官方文档。我这就不多加赘述了。
os.exec*()都只是posix系统的直接映射,所以os.execl的第一个参数 “/usr/bin/python “是程序的可执行文件,而其他的都是program argument,就是c中int main(int argc,char** argv)中的argv。
而python的sys.argv应是c中argv的[1:],所以os.execl中的第二个参数 “python “对于python程序test.py不可见而且没有用。 实际上os.execl的第二个参数也就是int main(int argc,char** argv)中的argv[0]可以是任意的,它本质上是提供给c程序作为main()函数的第一个参数使用。
————————————————————————————————————————————————————————————————————————————————————————
总结:好累,真不明白当初大佬是怎么读完的,当初还想三篇博客写完,做梦去吧。(文章中摘录了部分大佬的博客内容,希望大佬不要介意,大佬的博客。)后面的内容就要自己一个个推断了,希望能够更新完吧。每一次阅读源代码就增长了很多知识,这个学习方法是满不错的。