os:与文件系统相关的模块

os 模块介绍

os 模块是 Python 内置的与操作系统中文件系统相关的模块,所以该模块依赖于当前的操作系统,但大部分情况下,该模块中的方法、属性在 Windows 和 UNIX 上都是可用的,其中 UNIX 包括 Linux 和 mac OS X。

我们下面默认都是在 Windows 上演示的,对于 Windows 上不支持的我们会切换到 Linux 上进行演示。

路径

用于定位一个文件或者目录的字符串被称为路径,在程序开发中通常涉及到两种路径:相对路径和绝对路径。

不过在学习相对路径之前,我们需要先了解一下什么是当前工作目录(工作区)。当前工作目录是指你在执行当前 py 文件时所在的目录,在 Python 中可以通过 os 模块提供的 getcwd 方法进行获取。

import os
print(os.getcwd())  # D:\satori

在 D:\satori 中有一个 1.py,当我在 PyCharm 中点击执行时,会打印该文件所在的目录。但是注意:工作区指的是你在执行该文件时所在目录,怎么理解呢?我们举个栗子:

我们看到,在不同的路径中执行,会打印不同的工作区。只不过我们经常使用 PyCharm,然后右键、点击 Run 执行,而此时文件所在路径和执行该文件时所在的路径是一致的。

所以相对路径依赖于当前的工作目录,假设在 D:\satori 下有一个 sample.txt:

try:
    open("sample.txt")
except FileNotFoundError:
    print("文件不存在")
else:
    print("文件存在")

当我们在执行这个 py 文件的时候,它会去哪里找这个 sample.txt 呢?没错,显然是在当前的工作区中找。

我们在 D:\satori 下执行的时候显然是没有问题的,python ../satori/1.py 等价于 python 1.py。但是当我们在 D:\ 下面执行的时候就出问题了,因为此时的工作区是 D:\,但是没有 D:\sample.txt 这个文件。

因此再次强调,工作区指的是你在执行相应的 py 文件时所在的目录,即使涉及到文件的导入也是如此。比如:在 D:\satori 中存在着 a 目录,a 目录中又存在一个 b 目录,b 目录里面存在着一个 test.py 文件,在 test.py 中我们打印工作区。然后在 1.py 中 import test.test

# D:\satori\a\b\test.py
import os 
print(os.getcwd())

# D:\satori\1.py
import a.b.test

我们在不同路径下执行 1.py:

我们看到,工作区就是你在执行当前 py 文件时所在的目录。而我们指定的路径是相对于工作区的路径,因此便称之为相对路径。

再来看看绝对路径,绝对路径很好理解,就是指文件的文件的完整路径,显然它不依赖当前的工作目录(工作区)。这里需要再提一下 PyCharm,很多人以为 Python 的 __file__ 属性指的就是当前文件的绝对路径,我们还是以 D:\satori\1.py 为例:

# 这里打印的 D:/satori/1.py 确实是绝对路径
print(__file__)  # D:/satori/1.py

但我们上面是在 PyCharm 中右键点击 Run 执行的,而 PyCharm 为我们做了什么事情呢,其实它相当于帮我们调用了 python D:/satori/1.py。但是问题来了,如果我们在命令行中执行呢?

是不是没有想到呢?并且 __file__ 和 sys.argv[0] 打印的值是一样的,所以它也不一定就是文件的完整路径。

真正获取完整路径的做法应该是,将工作区和 __file__ 组合起来。

os.environb -- 获取当前环境变量的值(字节)

environb 属性用于返回以字节表示的当前操作系统的环境变量的值,但是注意 environb 需要当 os.supports_bytes_environ 为 True 时才可以使用。

import os

print(os.supports_bytes_environ)  # False

在我当前的 Windows 上显示 False,说明不支持,我们切换到 Linux 上。

>>> import os
>>> os.supports_bytes_environ
True
>>> os.environb  # 返回值可以像字典一样操作
environ({b'XDG_SESSION_ID': b'8432', b'HOSTNAME': b'iZ2ze3ik2oh85c6hanp0hmZ', ...: ..., b'XDG_RUNTIME_DIR': b'/run/user/0', b'_': b'/usr/bin/python3'})

os.environ -- 获取当前环境变量的值(字符串)

和 environb 属性类似,只不过使用字符串表示的,Windows 和 Linux 都支持。

import os

print(os.environ)
# 省略部分输出
"""  
environ(
    {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\satori\\AppData\\Roaming', 
    'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 
    'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 
    'COMSPEC': 'C:\\WINDOWS\\system32\\cmd.exe', 
    'FPS_BROWSER_APP_PROFILE_STRING': 'Internet Explorer', 
    'FPS_BROWSER_USER_PROFILE_STRING': 'Default', 
    'GOPATH': 'C:\\Users\\satori\\go', 
    'IDEA_INITIAL_DIRECTORY': 'C:\\Users\\satori\\Desktop', 
    }
)
"""

我们可以获取指定的环境变量,也可以进行设置。

import os

print(f"主机名:{os.environ['COMPUTERNAME']}")  # 主机名:HANSER
print(f"GOPATH: {os.environ['GOPATH']}")  # GOROOT: C:\Users\satori\go
print(os.environ["PATH"])  # C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\...;.......;...

os.environ["user"] = "satori"
print(os.environ["user"])  # satori

但是注意,这种方式设置的环境变量只是临时的。如果想永久生效,那么在 Windows 上我们需要点击 "高级系统设置" 然后手动添加;在 Linux 上,则是修改 ~/.bashrc 进行添加。

os._exit 方法 -- 结束当前进程

os._exit(n) 可以结束当前进程,并同时返回状态码 n:

import os

os._exit(1000)
print(123)
"""
Process finished with exit code 1000
"""

如果你用的是 PyCharm,你会看到退出时的状态码就是我们指定的 1000,并且执行到这一句的时候解释器就退出了,下面的 print 语句是不会执行的。

但是退出进程的话,我们更常使用 sys.exit(n),它不需要考虑平台等因素的影响,一般是退出 Python 程序的首选方法。使用 sys.exit(n) 会引发一个 SystemExit 异常(唯一一个不被认为是异常的异常),如果没有捕获这个异常,程序会直接退出。

import sys

try:
    sys.exit(100)
except SystemExit:
    print("程序想要退出,被捕获了")

"""
程序想要退出,被捕获了

Process finished with exit code 0
"""

我们看到 sys.exit 是通过引发异常来实现退出的,sys.exit(n) 完全等价于 raise SystemExit(n),并且注意:SystemExit 继承自 BaseException,不是 Exception。

print(123)
raise SystemExit(-666)
"""
123

Process finished with exit code -666
"""

程序没有报错,而是直接退出了,并且状态码也是我们指定的状态码。所以说 SystemExit 是不像异常的异常,因为没有任何的错误信息出现。当然 sys.exit 和 SystemExit 如果接收一个整型则表示状态码,接收了其它类型的对则会输出到 stderr 当中、同时状态码为 1。

程序没有报错,而是直接退出了,并且状态码也是我们指定的状态码。所以说 SystemExit 是不像异常的异常,因为没有任何的错误信息出现。当然 sys.exit 和 SystemExit 如果接收一个整型则表示状态码,接收了其它类型的对则会输出到 stderr 当中、同时状态码为 1。

所以 sys.exit 是通过引发 SystemExit 异常来退出解释器的,我们可以捕获此异常做一些清理工作,甚至可以阻挠程序退出;但是 os._exit 则是直接将 Python 解释器退出,底层会调用 C 语言的 _exit 函数,相当于强制退出。因此 os._exit 一般用于子进程的退出,我们在主进程中退出的话一般使用 sys.exit。

当然我们知道还有内置的 exit 函数和 quit 函数,它们和 sys.exit 的实现机制是一样的,也会通过引发 SystemExit 退出程序,只不过用在交互式解释器当中。当然即使不在交互式当中,也是可以使用的。

os.abort 方法 -- 发送一个程序请求异常终止信号

os.abort 用于像调用该方法的进程发送一个程序请求异常终止(SIGABRT)信号,举个栗子:

>>> import os
>>> os.abort()
Aborted

以上在 Linux 中演示的,Windows 上虽然也支持,但是效果不好。

os.exec 家族方法 -- 打开新的进程

os 内部有几个以 exec 开头的方法,可以打开新的进程:

  • 方法一:os.execl(path, arg0, arg1, ...)
  • 方法二:os.execle(path, arg0, arg1, ..., env)
  • 方法三:os.execlp(file, arg0, arg1, ...)
  • 方法四:os.execlpe(file, arg0, arg1, ..., env)
  • 方法五:os.execv(path, args)
  • 方法六:os.execve(path, args, env)
  • 方法七:os.execvp(file, args)
  • 方法八:os.execvpe(file, args, env)

里面的参数含义如下:

  • path:用于指定要执行的程序的路径
  • arg0、arg1:字符串表示的路径(个人没发现该参数的具体作用)
  • args:不为空的列表或元组,其元素为字符串表示的路径
  • env:定义新处理的环境变量,一般指定 os.environ
  • file:和 path 等价
import os

# 打开系统自带的画图程序,以下几种方式均可
os.execl(r"C:\windows\system32\mspaint.exe", "/")
os.execle(r"C:\windows\system32\mspaint.exe", "/", os.environ)
os.execlp(r"C:\windows\system32\mspaint.exe", "/")
os.execlpe(r"C:\windows\system32\mspaint.exe", "/", os.environ)
os.execv(r"C:\windows\system32\mspaint.exe", ["/"])
os.execve(r"C:\windows\system32\mspaint.exe", ["/"], os.environ)
os.execvp(r"C:\windows\system32\mspaint.exe", ["/"])
os.execvpe(r"C:\windows\system32\mspaint.exe", ["/"], os.environ)

注意:execl、execle、execv、execve 不会使用环境变量中的 PATH 来定位可执行文件,我们需要指定完整的路径;但是 execlp、execlpe、execvp、execvpe 会使用环境变量中的 PATH 来定位。

import os

try:
    os.execl(r"mspaint.exe", "/")
except FileNotFoundError:
    print("找不到可执行文件")  # 找不到可执行文件

# 正常执行
os.execlp(r"mspaint.exe", "/")

os.get_exec_path 方法 -- 获取可执行文件的目录列表

get_exec_path 可以返回执行文件的目录列表,接收一个参数 env;如果为 None,则使用 os.environ。

import os

print(os.get_exec_path({}))  # ['.', 'C:\\bin']
print(os.get_exec_path())
"""
['C:\\WINDOWS\\system32', 'C:\\WINDOWS', 'C:\\WINDOWS\\System32\\Wbem', 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\', 
'C:\\WINDOWS\\System32\\OpenSSH\\', 'C:\\Program Files\\Git\\cmd', 
'C:\\Program Files\\Redis\\', 'C:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR', 
'D:\\ffmpeg\\bin', 'C:\\Users\\satori\\AppData\\Local\\Programs\\orca', 'C:\\python38', 'C:\\python38\\Scripts', 
'C:\\lua', 'C:\\MinGW\\bin', 'D:\\Graphviz\\bin', 
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Tools\\MSVC\\14.16.27023\\bin\\Hostx64\\x64', 
'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.17763.0\\x64', 
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\Common7\\IDE', 
'D:\\protoc-3.14.0-win64\\bin', 'C:\\Go\\bin', 'C:\\python38\\Scripts\\', 'C:\\python38\\', 'C:\\Users\\satori\\.cargo\\bin', 
'C:\\Program Files\\MySQL\\MySQL Shell 8.0\\bin\\', 'C:\\Users\\satori\\go\\bin', 
'C:\\Users\\satori\\AppData\\Local\\atom\\bin', 'C:\\Users\\satori\\go\\bin', 'C:\\python38\\lib\\site-packages\\numpy\\.libs']
"""
print(os.get_exec_path({"PATH": r"C:\Windows"}))  # ['C:\\Windows']

os.getenv 方法 -- 获取环境变量的值

用于获取环境变量的值:os.getenv(key, default=None),其中 key 不区分大小写,该方法实际上就是调用了 os.environ.get,我们说 os.environ 可以当成一个字典使用。

import os

print(os.getenv("GOPATH"))  # C:\Users\satori\go
print(os.getenv("GOPATH1"))  # None
print(os.getenv("GOPATH1", "不存在"))  # 不存在

os.getlogin 方法 -- 获取系统登录的用户名

你可以进入 cmd 控制台:

Users\ 后面的就是调用 getlogin 方法的返回值。

import os

print(os.getlogin())  # satori

如果你要是没有设置的话,那么返回的应该是 Administrator。

进程 id 相关

每一个进程都有一个独一无二的编号作为唯一标识,该编号便是进程的 id,并且每一个进程都有一个父进程。

  • os.getpid() 获取当前进程的 id
  • os.getppid() 获取当前进程的父进程的 id
import os

print(os.getpid())  # 13056
print(os.getppid())  # 1956

显然在执行当前 py 文件的时候,os.getpid() 就是对应 Python 解释器的进程 id,执行完毕之后解释器便退出了。所以如果你执行多次,那么 os.getppid() 的结果是会变化的,因为解释器重新启动的时候所分配到进程 id 不一定和之前一样。但是 os.getppid() 是不会变化的,因为我们是在 PyCharm 当中执行的,相当于 PyCharm 调用的 Python 解释器,而 PyCharm 我们没有关闭,所以它的 id 是不会变化的。

可以打开任务管理器查看一下,看看进程 id 为 1956 的进程是不是 PyCharm。

很明显是的,但是通过任务管理器查看比较 low,作为一个 Python 程序猿我们有更专业的办法:

import psutil
print(psutil.Process(1956).name())  # pycharm64.exe

psutil 是一个专门和操作系统打交道的第三方模块,非常的方便,可以了解一下。

在获取到进程 id 之后,显然我们就可以杀死该进程了,可以通过 os.kill 实现。

import os

pid = os.getpid()
sig = 9
# pid: 指定当前进程的 id
# sig:信号
os.kill(pid, sig)

os.startfile 方法 -- 使用关联的程序启动文件

os.startfile(path) 相当于双击 path:

import os

os.startfile("1.png")

相当于双击 1.png,会使用当前默认的看图工具打开 1.png 图片。

import os

os.startfile("1.xlsx")

双击打开 1.xlsx,会自动启动 Office。

os.strerror() 方法 -- 根据错误码返回错误信息

Python 的很多操作是直接调用操作系统提供的接口,但如果调用操作系统接口报错了,那么 Python 就会返回一个 OSError(或者继承 OSError 的异常),而常见的 FileNotFoundError 就是一个系统 error。

print(FileNotFoundError.__base__)  # <class 'OSError'>

而对于 OSError,操作系统会返回一个 "错误码" 和 一个 "错误信息"。

try:
    open("xxx")
except OSError as e:
    print(e.errno)  # 2
    print(e.strerror)  # No such file or directory

异常捕获时,如果捕获的是 OSError,那么可以通过 e.errno 获取操作系统返回的错误码、通过 e.strerror 获取操作系统返回的错误信息。

如果我们已经有一个错误码,那么可以根据 os.strerror 获取该错误码对应的错误信息。

import os

print(os.strerror(2))  # No such file or directory
# 如果是不存在的错误码,那么会返回一个 Unknown error,但这是在 Windows 系统中
# 其它平台可能会引发一个 ValueError
print(os.strerror(100))  # Unknown error

不过 Python 还内置一个模块 errno,将每一个错误码都赋值给了一个变量。

有兴趣可以自己查看一下。

os.system 方法 -- 在控制台执行相关命令

os.system(command) 等价于在控制台中输入 command 回车,比如我们想执行 pip install pkg,那么直接通过 os.system("pip install pkg") 即可。并且在 Windows 系统中,我们在控制台输入了一个文件的路径,如果是二进制文件则会自动执行;如果不是二进制文件,那么会自动尝试用相关可执行程序打开该文件。所以从这个角度上说,os.system 完全可以实现 os.startfile 的功能,只不过 os.system 还可以执行命令。

import os

# 返回值为 0 表示成功,会自动打开记事本
print(os.system("notepad.exe"))  # 0
# 为 1 表示失败,并且控制台显示的错误信息也会自动打印出来,只不过由于编码问题会显示乱码
print(os.system("xxx.png"))  # 1
# 为 2 的话表示错误

但是注意:我们首先打开了一个记事本,此时程序就阻塞在这里了,如果不把记事本关掉,那么第二个 os.system 是不会执行的;如果想不阻塞,那么只需要多加上一个 start 即可:os.system("start notepad.exe")。

不过执行系统命令,我们更推荐使用 subprocess。

os.times 方法 -- 返回当前的全局进程时间

该方法会返回一个 times_result 对象,用于记录当前的全局进程时间。

import os

print(os.times())
# nt.times_result(user=0.09375, system=0.109375, children_user=0.0, children_system=0.0, elapsed=0.0)

"""
user:用户时间
system:系统时间
children_user:所有子进程的用户时间
children_system:所有子进程的系统时间
elapsed:子过去的固定点以来经过的实际时间
"""
print(os.times().user)  # 0.09375

os.umask 方法 -- 设置用户创建文件的默认权限

该方法会设置新的权限,同时返回原来的权限。这个方法在 Windows 上也支持,但是没有太大意义,我们在 Linux 上演示。

>>> import os
>>> oct(os.umask(0o777))
'0o22'

# 返回的 0o22 表示原来的权限,也是当前系统的默认权限
# 我们也可以设置回去
>>> oct(os.umask(0o22))
'0o777'
>>> 

os.access 方法 -- 测试对路径的访问权限

我们可以通过 os.access 判断对路径具有哪些操作权限,函数参数为:

os.access(path, mode, *, dir_fd=None, effective_ids=False, follow_symlinks=True)

参数含义如下:

  • path:文件路径
  • mode:指定测试权限,os.F_OK 表示是否存在、os.X_OK 表示是否可执行、os.W_OK 表示是否可写、os.F_OK 表示是否可读
  • dir_fd:表示指定打开文件描述符的路径,Windows 上不可用,该参数一般不用
  • effective_ids:表示是否使用有效的 IDS,如果为 True,access 将使用有效的 uid/gid 、而不是真实的 uid/gid 执行访问检查
  • follow_symlinks:表示是否跟踪符号链接,是一个布尔值。如果为 False,并且要操作的路径是一个符号链接,则该方法对符号链接本身进行操作,而不是链接指向的文件

说句实话,基本上只需要关注 path 和 mode 两个参数即可,剩余三个很少会使用。

import os

print(os.access("1.png", os.F_OK))  # True
print(os.access("1.png", os.R_OK))  # True
print(os.access("1.png", os.W_OK))  # True
print(os.access("1.png", os.X_OK))  # True

os.chdir 方法 -- 改变当前工作区

这个方法该是比较有用的,我们举个栗子:

import os

# 相对路径会相对于工作区
print(os.access("1.txt", os.F_OK))  # False
print(os.access("file/1.txt", os.F_OK))  # True

# 改变工作区到当前的 file 目录中,这样可以直接访问 1.txt 了
os.chdir("file")
print(os.access("1.txt", os.F_OK))  # True

os.chmod 方法 -- 更改路径的权限

将某个路径更改为指定的权限,第一个参数为 path 表示路径,第二个参数为 mode 表示权限。比如:

os.chmod("/root/1.py", 0o777)

比较简单,没有什么难度,但是有一个模块叫 stat,给我们提供了很多的权限值,可以更方便我们指定:

  • stat.S_ISUID:设置 UID 位,允许用户访问,通常只对所有者可用的文件或目录,适用于 UNIX
  • stat.S_ISGID:设置组 ID,将进程的有效组 ID 更改为文件的组所有者,并且根据授予该组的权限授予用户的访问权限,适用于 UNIX
  • stat.S_ENFMT:V 文件锁定执行,对没有设置组执行位(stat.S_IXGRP)的文件,强制执行文件/记录锁定,适用于 UNIX
  • stat.S_ISVTX:设置粘性位,对目录设置此模式后,该目录中的文件只能由文件所有者、目录所有者对其进行重命名或删除,适用于 UNIX
  • stat.S_IREAD:读权限,同 UNIX 下的 stat.S_IRUSR,适用于 Windows
  • stat.S_IWRITE:写权限,同 UNIX 下的 stat.S_IWUSR,适用于 Windows
  • stat.S_IEXEC:可执行权限,同 UNIX 下的 stat.S_IXUSR,适用于 Windows
  • stat.S_IRWXU:屏蔽文件所有者的权限,适用于 UNIX
  • stat.S_IRUSR:所有者具有读权限,适用于 UNIX
  • stat.S_IWUSR:所有者具有写权限,适用于 UNIX
  • stat.S_IXUSR:所有具有执行权限,适用于 UNIX
  • stat.S_IRWXG:屏蔽组的权限,适用于 UNIX
  • stat.S_IRGRP:组的读权限,适用于 UNIX
  • stat.S_IWGRP:组的写权限,适用于 UNIX
  • stat.S_IXGRP:组的可执行权限,适用于 UNIX
  • stat.S_IRWXO:屏蔽不在组中的其它人的权限,适用于 UNIX
  • stat.S_IROTH:其它人的读权限,适用于 UNIX
  • stat.S_IWOTH:其它人的写权限,适用于 UNIX
  • stat.S_IXOTH:其它人的可执行权限,适用于 UNIX
import os
import stat

os.chmod("file.py", stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)

os.open 方法 -- 打开文件

打开文件可以使用高级函数 open,也可以使用低级的 os.open。

os.open(path, flags, mode=0o777, *, dir_fd=None)

参数的话我们只需要关注 path 和 flags 即可,path:表示打开的文件路径,flags 表示模式,支持的格式如下:

然后我们来看一下用法,首先平时打开文件的时候我们用的都是内置函数 open(即 io.open),那么它和 os.open 有什么关系呢?内置函数 open 实际上是对 os.open 的封装,在 os.open 基础上增加了相关访问方法,因此为了操作方便应该调用内置函数 open 进行文件操作,但如果对效率有非常高的要求则可以考虑调用 os.open;此外 open 函数返回的是一个文件对象,我们可以在此基础上进行任意操作,而 os.open 返回的是一个文件描述符(一个文件对应一个文件描述符),说白了就是一个整数。

使用 os.open 我们完全可以实现 open 函数的所有用法,下面来看看常见的读取文件的几种姿势:

# -*- coding:utf-8 -*-
# @Author: komeiji satori
import os

"""
# 只读方式打开,相当于 open 函数中的 r,要求文件必须存在
fd = os.open("1.txt", os.O_RDONLY)  

# 只写方式打开,同样要求文件必须存在。并且打开之后文件指针默认处于文件的开头,因此写入的时候会将原本的内容给覆盖掉
# 假设文件原本的内容是 "abcdefg",然后我们写入了 "123",那么文件的内容就变成了 "123defg"
fd = os.open("1.txt", os.O_WRONLY)  

# 文件不存在则创建
fd = os.open("1.txt", os.O_WRONLY | os.O_CREAT)

# 不存在则创建,存在则清空,然后写入,此时等价于 open 函数中的 w
fd = os.open("1.txt", os.O_WRONLY | os.O_CREAT | os.O_TRUNC)

# 不存在则创建,存在则追加写入(打开时文件指针处于文件末尾),此时等价于 open 函数中的 a
fd = os.open("1.txt", os.O_WRONLY | os.O_CREAT | os.O_APPEND)

# 可读可写,同样要求文件必须存在
fd = os.open("1.txt", os.O_RDWR)
"""

# 以读写模式打开文件,然后不存在则自动创建
fd = os.open("1.txt", os.O_RDWR | os.O_CREAT)
# 往该描述符对应的文件中写入内容
os.write(fd, "你好呀".encode("utf-8"))
# 然后此时指针位于末尾,我们如果想读取,需要进行移动
# os.lseek(fd, 10, 0): 从文件的开头向后移动 10 个字节
# os.lseek(fd, 10, 1): 从当前文件指针的位置向后移动 10 个字节
# os.lseek(fd, -10, 2): 从文件的末尾向前移动 10 个字节
os.lseek(fd, -3, 2)  # 这里我们将文件指针放在距离文件末尾三个字节的位置
# 显然会读取 "你好呀" 中的 "呀",读取的时候需要指定读取的字节数
print(os.read(fd, 1024).decode("utf-8"))  # 呀
# 但是我们有时不知道文件有多少内容,那么就循环读取,当读取到的内容为空时,停止读取
print(os.read(fd, 1024))  # b''

总的来说,虽然没有内置函数 open 那么方便,但是很明显可定制性更强。

打开文件之后还需要关闭,该 os.close 方法可以关闭打开的文件描述符(Linux 说法,Windows 叫文件句柄),但它关闭的必须是 os.open 或 os.pipe 返回的对象(说白了就是一个整型)。该功能适用于低级 I/O 操作,如果是 open 函数打开的文件对象,则需要调用其相应的 close 方法。

import os

# 以只读方式打开文件
fd = os.open("1.txt", os.O_RDONLY)
# 只是一个整型,因为每一个打开的文件都有一个编号
print(fd + 1)  # 4
# 指定编号关闭对应的文件
os.close(fd)
# 当然我们在关闭之前,也可以调用 os.fsync(fd) 将内容强制刷入磁盘

因此除了 close 之外,还有一个 closerange,可以关闭多个文件描述符对应的文件。

import os

# 关闭文件描述符为 1、2、3、4 的文件 
os.closerange(1, 5)

对于 open 函数而言,它返回的是一个文件对象,通过 f.fileno() 可以获取对应的描述符。

os.pipe 方法 -- 创建一个管道

os.pipe 返回一对可用于读写的文件描述符:

import os

r, w = os.pipe()
os.write(w, b"123")
print(os.read(r, 1024))  # b'123'

os.write(w, b"456")
print(os.read(r, 1024))  # b'456'

os.device_encoding 方法 -- 返回设备编码

device_encoding 方法用于在连接到终端时,返回一个与文件描述符关联的设备编码;如果没有连接到设备,则返回 None。

import os

fd = os.open("file.py", os.O_RDONLY)
print(os.device_encoding(fd))  # None
# 当然我们可以调用 os.isatty,判断是否连接到终端设备
print(os.isatty(fd))  # False

由于未连接到终端,所以返回 None。

在 Linux 系统中,可以使用 os 模块的 openpty 方法打开一个伪终端,然后连接到该伪终端获取设备编码。

>>> import os
>>> fd = os.open("1.txt", os.O_RDONLY)
>>> print(os.device_encoding(fd))
None
>>> 
>>> master, slave = os.openpty() # 打开一个伪终端,Windows 不支持该语句
>>> os.device_encoding(master)
'UTF-8'
>>> 

os.dup 方法 -- 返回一个打开的文件描述符的副本

使用 os.dup 可以返回一个打开的文件描述符的副本,并且该副本是不可继承的。

import os

fd = os.open("1.txt", os.O_RDONLY)
print(fd)  # 3
print(os.dup(fd))  # 4

然后是写入文件:

import os

fd = os.open("1.txt", os.O_RDWR | os.O_CREAT | os.O_TRUNC)
fd_cp = os.dup(fd)

# 往 fd_cp 指向的文件写入内容
os.write(fd_cp, b"12345678")
os.lseek(fd_cp, 0, 0)
print(os.read(fd_cp, 1024))  # b'12345678'
os.lseek(fd_cp, 0, 0)
print(os.read(fd, 1024))  # b'12345678'

os.closerange(fd, fd_cp)

可以看出,两者操作的是同一个文件。此外我们还可以调用 os.fdopen,根据一个描述符得到背后的文件对象。

os.listdir 方法 -- 返回该目录下的所有文件

如果想查看一个目录下有哪些文件和子目录,可以通过 os.listdir,但是注意:子目录里面的内容不会返回。

import os

# 必须传递一个目录,不能传递文件
print(os.listdir(r"C:\python38\Doc"))  # ['python387.chm']

返回一个列表,列表里面是字符串,并且字符串就只是自身的文件名或者目录名。

查看文件状态

查看文件状态,有三种方式:os.stat、os.lstat、os.fstat。

# -*- coding:utf-8 -*-
# @Author: komeiji satori
import os

print(os.stat(r"C:\python38\python.exe"))
"""
os.stat_result(
    st_mode=33279, st_ino=9570149208214770, st_dev=2156351692,
    st_nlink=1, st_uid=0, st_gid=0, st_size=101944,
    st_atime=1608966951, st_mtime=1608545262, st_ctime=1608545262
)
"""
print(os.lstat(r"C:\python38\python.exe"))

# 也可以先得到对应的文件描述符
fd = os.open(r"C:\python38\python.exe", os.O_RDONLY)
# 然后使用 os.fstat
print(os.fstat(fd))

比较简单,如果想访问哪个属性,那么直接调用即可。

  • st_mode:文件类型和权限
  • st_ino:inode 编号
  • st_dev:文件的设备 ID
  • st_nlink:硬连接数
  • st_uid:所有者的用户 id
  • st_gid:所有者的组 id
  • st_size:文件大小
  • st_atime:上次访问的时间
  • st_mtime:上次修改的时间
  • st_ctime:上次状态更改的时间

os.makedirs 方法 -- 递归创建目录

语法格式:os.makedirs(name, mode=0o777, exists_ok=False),其中的参数 exists_ok 表示目录存在是否 ok,为 False 表示不 ok、会报错;为 True,目录存在则不报错。

比较简单,然后还有一个 os.mkdir 表示创建单级目录:os.mkdir(path, mode=0o777)。因此假设使用 mkdir 创建了 D:\abc\def,那么 D:\abc 必须存在。

os.utime -- 设置文件的访问和修改时间

这个方法比较有意思,我们来看一下:

import datetime
import os

t = datetime.datetime(1981, 1, 1).timestamp()
os.utime("1.txt", (t, t))

os.urandom 方法 -- 生成随机字节串

可以生成指定大小的随机字节串,用于作为随机加密的 key 使用。

import os

print(os.urandom(6))  # b'^\xb5\xf6\x7f\xf9\x0e'
print(len(os.urandom(222)))  # 222

常规方法

os 有很多我们经常使用的方法或者属性,这里就不详细说明了,简单介绍一下。

  • os.remove:删除文件
  • os.removedirs:递归删除目录
  • os.rename:重命名
  • os.replace:重命名文件或者目录
  • os.rmdir:删除空目录
  • os.unlink:删除文件
  • os.cpu_count:获取 CPU 核心数
  • os.extsep:将文件名和扩展名分开的字符,一般是 .
  • os.linesep:操作系统分隔行的字符串
  • os.name:依赖操作系统的模块名称,Windows 为 nt、Linux 为 posix
  • os.pathsep:分隔搜索路径的字符,一般是;
  • os.sep:路径分隔符,Windows 是 \,Linux 是 /
posted @ 2019-08-28 18:03  古明地盆  阅读(1233)  评论(0编辑  收藏  举报