PEP 355 – Path - 面向对象的文件系统路径
- 作者:
- Björn Lindqvist <bjourne at gmail.com>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2006年1月24日
- Python 版本:
- 2.5
- 发布历史:
拒绝通知
此PEP(以这种形式)已被拒绝。提议的路径类是最终的“大杂烩”;但认为将所有使用路径的功能都作为单个类的方法来实现的观点是一种反模式。(例如,为什么不包括open()?或者execfile()?)从str继承是一个特别糟糕的想法;许多字符串操作应用于路径时毫无意义。此PEP已搁置,尽管讨论时不时地爆发,但现在是时候结束此PEP的痛苦了。一个不那么牵强的提议可能会更容易被接受。
摘要
此PEP描述了一个新类Path,将添加到os模块中,以面向对象的方式处理路径。还讨论并建议了各种相关函数的“弱”弃用。
背景
此PEP中表达的观点并非最近才提出,而是在Python社区中争论多年。许多人认为os.path模块中提供的文件路径操作API不足。Just van Rossum于2001年在python-dev上首次提出了Path对象的建议[2]。2003年,Jason Orendorff发布了“path module”的1.0版,这是第一个使用对象表示路径的公开实现[3]。
path模块很快变得非常流行,并多次尝试将其纳入Python标准库;[4]、[5]、[6]、[7]。
此PEP总结了人们对path模块提出的想法和建议,并提议将修改后的版本纳入标准库。
动机
处理文件系统路径是任何编程语言中的常见任务,在像Python这样的高级语言中尤其常见。需要对此任务提供良好的支持,因为:
- 几乎每个程序都使用路径来访问文件。因此,如此频繁执行的任务应该尽可能直观和易于执行。
- 它使Python成为替代过于复杂的shell脚本的更好语言。
目前,Python有大量不同的函数分散在六个模块中用于处理路径。这使得新手和有经验的开发人员都难以选择正确的方法。
Path类提供了以下对当前常见实践的增强:
- 一个“统一”对象提供以前所有函数的功能。
- 可子类化 -
Path对象可以扩展以支持文件系统路径以外的路径。程序员无需学习新的API,但可以重用他们对Path的知识来处理扩展类。 - 将所有相关功能集中在一个地方,学习正确的方法变得更容易,因为不必在许多不同的模块中寻找正确的函数。
- Python是一种面向对象的语言。就像文件、日期时间(datetimes)和套接字(sockets)是对象一样,路径也是对象,它们不仅仅是传递给函数的字符串。
Path对象本质上是一个Pythonic的想法。 Path利用属性(properties)。属性使得代码更具可读性:if imgpath.ext == 'jpg': jpegdecode(imgpath)
比以下更好:
if os.path.splitexit(imgpath)[1] == 'jpg': jpegdecode(imgpath)
基本原理
以下几点总结了设计:
Path继承自字符串,因此所有期望字符串路径名的代码无需修改,也没有现有代码会被破坏。- 可以通过使用类方法
Path.cwd,通过使用表示路径的字符串实例化类,或者使用等同于Path(".")的默认构造函数来创建Path对象。 Path提供常见的路径名操作、模式扩展、模式匹配以及包括复制在内的其他高级文件操作。基本上,Path提供所有与路径相关的功能,除了文件内容的操作,文件对象更适合此用途。- 通过不实例化系统特定方法来处理平台不兼容性。
规范
此类别定义了以下公共接口(文档字符串已从参考实现中提取并为简洁而缩短;有关更多详细信息,请参阅参考实现):
class Path(str):
# Special Python methods:
def __new__(cls, *args) => Path
"""
Creates a new path object concatenating the *args. *args
may only contain Path objects or strings. If *args is
empty, Path(os.curdir) is created.
"""
def __repr__(self): ...
def __add__(self, more): ...
def __radd__(self, other): ...
# Alternative constructor.
def cwd(cls): ...
# Operations on path strings:
def abspath(self) => Path
"""Returns the absolute path of self as a new Path object."""
def normcase(self): ...
def normpath(self): ...
def realpath(self): ...
def expanduser(self): ...
def expandvars(self): ...
def basename(self): ...
def expand(self): ...
def splitpath(self) => (Path, str)
"""p.splitpath() -> Return (p.parent, p.name)."""
def stripext(self) => Path
"""p.stripext() -> Remove one file extension from the path."""
def splitunc(self): ... # See footnote [1]
def splitall(self): ...
def relpath(self): ...
def relpathto(self, dest): ...
# Properties about the path:
parent => Path
"""This Path's parent directory as a new path object."""
name => str
"""The name of this file or directory without the full path."""
ext => str
"""
The file extension or an empty string if Path refers to a
file without an extension or a directory.
"""
drive => str
"""
The drive specifier. Always empty on systems that don't
use drive specifiers.
"""
namebase => str
"""
The same as path.name, but with one file extension
stripped off.
"""
uncshare[1]
# Operations that return lists of paths:
def listdir(self, pattern = None): ...
def dirs(self, pattern = None): ...
def files(self, pattern = None): ...
def walk(self, pattern = None): ...
def walkdirs(self, pattern = None): ...
def walkfiles(self, pattern = None): ...
def match(self, pattern) => bool
"""Returns True if self.name matches the given pattern."""
def matchcase(self, pattern) => bool
"""
Like match() but is guaranteed to be case sensitive even
on platforms with case insensitive filesystems.
"""
def glob(self, pattern):
# Methods for retrieving information about the filesystem
# path:
def exists(self): ...
def isabs(self): ...
def isdir(self): ...
def isfile(self): ...
def islink(self): ...
def ismount(self): ...
def samefile(self, other): ... # See footnote [1]
def atime(self): ...
"""Last access time of the file."""
def mtime(self): ...
"""Last-modified time of the file."""
def ctime(self): ...
"""
Return the system's ctime which, on some systems (like
Unix) is the time of the last change, and, on others (like
Windows), is the creation time for path.
"""
def size(self): ...
def access(self, mode): ... # See footnote [1]
def stat(self): ...
def lstat(self): ...
def statvfs(self): ... # See footnote [1]
def pathconf(self, name): ... # See footnote [1]
# Methods for manipulating information about the filesystem
# path.
def utime(self, times) => None
def chmod(self, mode) => None
def chown(self, uid, gid) => None # See footnote [1]
def rename(self, new) => None
def renames(self, new) => None
# Create/delete operations on directories
def mkdir(self, mode = 0777): ...
def makedirs(self, mode = 0777): ...
def rmdir(self): ...
def removedirs(self): ...
# Modifying operations on files
def touch(self): ...
def remove(self): ...
def unlink(self): ...
# Modifying operations on links
def link(self, newpath): ...
def symlink(self, newlink): ...
def readlink(self): ...
def readlinkabs(self): ...
# High-level functions from shutil
def copyfile(self, dst): ...
def copymode(self, dst): ...
def copystat(self, dst): ...
def copy(self, dst): ...
def copy2(self, dst): ...
def copytree(self, dst, symlinks = True): ...
def move(self, dst): ...
def rmtree(self, ignore_errors = False, onerror = None): ...
# Special stuff from os
def chroot(self): ... # See footnote [1]
def startfile(self): ... # See footnote [1]
使用Path类替换旧有函数
在本节中,“a ==> b”表示b可以作为a的替代。
在以下示例中,我们假设Path类已通过from path import Path导入。
- 替换
os.path.joinos.path.join(os.getcwd(), "foobar") ==> Path(Path.cwd(), "foobar") os.path.join("foo", "bar", "baz") ==> Path("foo", "bar", "baz")
- 替换
os.path.splitextfname = "Python2.4.tar.gz" os.path.splitext(fname)[1] ==> fname = Path("Python2.4.tar.gz") fname.ext
或者如果你想要两部分
fname = "Python2.4.tar.gz" base, ext = os.path.splitext(fname) ==> fname = Path("Python2.4.tar.gz") base, ext = fname.namebase, fname.extx
- 替换
glob.globlib_dir = "/lib" libs = glob.glob(os.path.join(lib_dir, "*s.o")) ==> lib_dir = Path("/lib") libs = lib_dir.files("*.so")
弃用
将此模块引入标准库,意味着需要“弱”弃用一些现有模块和函数。这些模块和函数使用广泛,无法真正弃用,即无法生成DeprecationWarning。此处“弱弃用”仅指文档中的注释。
下表列出了应弃用的现有功能。
| Path 方法/属性 | 弃用函数 |
|---|---|
| normcase() | os.path.normcase() |
| normpath() | os.path.normpath() |
| realpath() | os.path.realpath() |
| expanduser() | os.path.expanduser() |
| expandvars() | os.path.expandvars() |
| parent | os.path.dirname() |
| 名称 | os.path.basename() |
| splitpath() | os.path.split() |
| drive | os.path.splitdrive() |
| ext | os.path.splitext() |
| splitunc() | os.path.splitunc() |
| __new__() | os.path.join(), os.curdir |
| listdir() | os.listdir() [fnmatch.filter()] |
| match() | fnmatch.fnmatch() |
| matchcase() | fnmatch.fnmatchcase() |
| glob() | glob.glob() |
| exists() | os.path.exists() |
| isabs() | os.path.isabs() |
| isdir() | os.path.isdir() |
| isfile() | os.path.isfile() |
| islink() | os.path.islink() |
| ismount() | os.path.ismount() |
| samefile() | os.path.samefile() |
| atime() | os.path.getatime() |
| ctime() | os.path.getctime() |
| mtime() | os.path.getmtime() |
| size() | os.path.getsize() |
| cwd() | os.getcwd() |
| access() | os.access() |
| stat() | os.stat() |
| lstat() | os.lstat() |
| statvfs() | os.statvfs() |
| pathconf() | os.pathconf() |
| utime() | os.utime() |
| chmod() | os.chmod() |
| chown() | os.chown() |
| rename() | os.rename() |
| renames() | os.renames() |
| mkdir() | os.mkdir() |
| makedirs() | os.makedirs() |
| rmdir() | os.rmdir() |
| removedirs() | os.removedirs() |
| remove() | os.remove() |
| unlink() | os.unlink() |
| link() | os.link() |
| symlink() | os.symlink() |
| readlink() | os.readlink() |
| chroot() | os.chroot() |
| startfile() | os.startfile() |
| copyfile() | shutil.copyfile() |
| copymode() | shutil.copymode() |
| copystat() | shutil.copystat() |
| copy() | shutil.copy() |
| copy2() | shutil.copy2() |
| copytree() | shutil.copytree() |
| move() | shutil.move() |
| rmtree() | shutil.rmtree() |
Path类弃用了整个os.path、shutil、fnmatch和glob。大部分os也被弃用。
已关闭的问题
自从此PEP首次出现在python-dev上以来,一些有争议的问题已得到解决:
__div__()方法被移除。重载/(除法)运算符可能“过于神奇”,并使路径连接看起来像除法。如果BDFL(仁慈的独裁者)愿意,该方法总是可以在以后重新添加。取而代之的是,__new__()获得了一个*args参数,该参数接受Path和字符串对象。*args通过os.path.join()连接,用于构建Path对象。这些更改废弃了有问题的joinpath()方法,该方法已被移除。- 方法和属性
getatime()/atime、getctime()/ctime、getmtime()/mtime和getsize()/size相互重复。这些方法和属性已合并为atime()、ctime()、mtime()和size()。它们之所以不是属性,是因为它们可能会意外改变。以下示例不保证总是通过断言:p = Path("foobar") s = p.size() assert p.size() == s
未解决的问题
Jason Orendorff的path模块的一些功能已被省略:
- 打开路径的功能 - 最好由内置的
open()处理。 - 读写整个文件的功能 - 最好由文件对象自己的
read()和write()方法处理。 - 一个
chdir()函数可能是一个有价值的补充。 - 需要制定一个弃用计划。
Path应该实现多少功能?它应该弃用多少现有功能以及何时弃用? - 名称显然必须是“path”或“Path”,但它应该存在于何处?在自己的模块中还是在
os中? - 由于
Path继承自str或unicode,以下非魔术、公共方法在Path对象上可用:capitalize(), center(), count(), decode(), encode(), endswith(), expandtabs(), find(), index(), isalnum(), isalpha(), isdigit(), islower(), isspace(), istitle(), isupper(), join(), ljust(), lower(), lstrip(), replace(), rfind(), rindex(), rjust(), rsplit(), rstrip(), split(), splitlines(), startswith(), strip(), swapcase(), title(), translate(), upper(), zfill()
在python-dev上,关于这种继承是否合理存在争议。大多数参与讨论的人表示,在文件系统路径的上下文中,大多数字符串方法没有意义——它们只是无用的负担。另一种立场,同样在python-dev上提出,是继承自字符串非常方便,因为它允许代码“正常工作”与
Path对象,而无需进行调整。其中一个问题是,在Python层面,无法使对象“足够像字符串”,以便它可以传递给内置函数
open()(以及其他期望字符串或缓冲区的内置函数),除非该对象继承自str或unicode。因此,不继承自字符串需要修改CPython的核心。
为了保持向后兼容性,这个新模块试图替换的函数和模块(os.path、shutil、fnmatch、glob以及os的部分)预计将在未来的Python版本中长期可用。
参考实现
目前,Path类实现为标准库模块fnmatch、glob、os、os.path和shutil的一个薄包装器。此PEP的目的是在这些模块被弃用时,将功能从上述模块移至Path。
更多详情和实现请参阅
示例
在本节中,“a ==> b”表示b可以作为a的替代。
- 使目录中所有python文件可执行
DIR = '/usr/home/guido/bin' for f in os.listdir(DIR): if f.endswith('.py'): path = os.path.join(DIR, f) os.chmod(path, 0755) ==> for f in Path('/usr/home/guido/bin').files("*.py"): f.chmod(0755)
- 删除emacs备份文件
def delete_backups(arg, dirname, names): for name in names: if name.endswith('~'): os.remove(os.path.join(dirname, name)) os.path.walk(os.environ['HOME'], delete_backups, None) ==> d = Path(os.environ['HOME']) for f in d.walkfiles('*~'): f.remove()
- 查找文件的相对路径
b = Path('/users/peter/') a = Path('/users/peter/synergy/tiki.txt') a.relpathto(b)
- 将路径分割成目录和文件名
os.path.split("/path/to/foo/bar.txt") ==> Path("/path/to/foo/bar.txt").splitpath()
- 列出当前目录树中所有的Python脚本
list(Path().walkfiles("*.py"))
参考文献和脚注
[1] 该方法不保证在所有平台上都可用。
版权
本文档已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0355.rst
上次修改: 2025-02-01 08:59:27 GMT