From dda6d09e621aa5882a0a12c011e40e104cb20392 Mon Sep 17 00:00:00 2001 From: David Goodger Date: Thu, 26 Jan 2006 19:45:49 +0000 Subject: [PATCH] added PEP 355, Path - Object oriented filesystem paths, by B. Lindqvist --- pep-0000.txt | 3 + pep-0355.txt | 506 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 509 insertions(+) create mode 100644 pep-0355.txt diff --git a/pep-0000.txt b/pep-0000.txt index 0a9c5ea02..3f0c36c04 100644 --- a/pep-0000.txt +++ b/pep-0000.txt @@ -109,6 +109,7 @@ Index by Category S 352 Required Superclass for Exceptions GvR, Cannon S 353 Using ssize_t as the index type von Loewis S 354 Enumerations in Python Finney + S 355 Path - Object oriented filesystem paths Lindqvist S 754 IEEE 754 Floating Point Special Values Warnes Finished PEPs (done, implemented in Subversion) @@ -404,6 +405,7 @@ Numerical Index S 352 Required Superclass for Exceptions GvR, Cannon S 353 Using ssize_t as the index type von Loewis S 354 Enumerations in Python Finney + S 355 Path - Object oriented filesystem paths Lindqvist SR 666 Reject Foolish Indentation Creighton S 754 IEEE 754 Floating Point Special Values Warnes I 3000 Python 3.0 Plans Kuchling, Cannon @@ -474,6 +476,7 @@ Owners Kuchling, A.M. amk@amk.ca Lemburg, Marc-Andre mal@lemburg.com Lielens, Gregory gregory.lielens@fft.be + Lindqvist, Björn bjourne@gmail.com von Loewis, Martin loewis@informatik.hu-berlin.de Martelli, Alex aleax@aleax.it McClelland, Andrew eternalsquire@comcast.net diff --git a/pep-0355.txt b/pep-0355.txt new file mode 100644 index 000000000..9ecd57635 --- /dev/null +++ b/pep-0355.txt @@ -0,0 +1,506 @@ +PEP: 355 +Title: Path - Object oriented filesystem paths +Version: $Revision$ +Last-Modified: $Date$ +Author: Björn Lindqvist +Status: Draft +Type: Standards Track (library) +Created: 24-Jan-2006 +Content-Type: text/plain +Python-Version: 2.5 + + +Abstract + + This PEP describes a new class, Path, to be added to the os + module, for handling paths in an object oriented fashion. The + "weak" deprecation of various related functions is also discussed + and recommended. + + +Motivation + + Dealing with filesystem paths is a common task in any programming + language, and very common in a high-level language like Python. + Good support for this task is needed, because: + + - Almost every program uses paths to access files. It makes sense + that a task, that is so often performed, should be as intuitive + and as easy to perform as possible. + + - It makes Python an even better replacement language for + over-complicated shell scripts. + + Currently, Python has a large number of different functions + scattered over half a dozen modules for handling paths. This + makes it hard for newbies and experienced developers to to choose + the right method. + + The Path class provides the following enhancements over the + current common practice: + + - One "unified" object provides all functionality from previous + functions. + + - Subclassability - the Path object can be extended to support + paths other than filesystem paths. The programmer does not need + to learn a new API, but can reuse his or her knowledge of Path + to deal with the extended class. + + - With all related functionality in one place, the right approach + is easier to learn as one does not have to hunt through many + different modules for the right functions. + + - Python is an object oriented language. Just like files, + datetimes and sockets are objects so are paths, they are not + merely strings to be passed to functions. Path objects is + inherently a pythonic idea. + + - Path takes advantage of properties. Properties make for more + readable code. + + if imgpath.ext == 'jpg': + jpegdecode(imgpath) + + Is better than: + + if os.path.splitexit(imgpath)[1] == 'jpg': + jpegdecode(imgpath) + + +Rationale + + The following points summarize the design: + + - Path extends from string, therefore all code which expects + string pathnames need not be modified and no existing code will + break. + + - A Path object can be created either by using the classmethod + Path.cwd, by instantiating the class with a string representing + a path or by using the default constructor which is equivalent + to Path("."). + + - Path provides common pathname manipulation, pattern expansion, + pattern matching and other high-level file operations including + copying. Basically Path provides everything path-related except + the manipulation of file contents, for which file objects are + better suited. + + - Platform incompatibilites are dealt with by not instantiating + system specific methods. + + +Specification + + This class defines the following public interface (docstrings have + been extracted from the reference implementation, and shortened + for brevity; see the reference implementation for more detail): + + 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): ... [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): ... [1] + def getatime(self): ... + def getmtime(self): ... + def getctime(self): ... + def getsize(self): ... + def access(self, mode): ... [1] + def stat(self): ... + def lstat(self): ... + def statvfs(self): ... [1] + def pathconf(self, name): ... [1] + + # Filesystem properties for path. + atime, mtime, ctime, size + + # Methods for manipulating information about the filesystem + # path. + def utime(self, times) => None + def chmod(self, mode) => None + def chown(self, uid, gid) => None [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): ... [1] + def startfile(self): ... [1] + + +Replacing older functions with the Path class + + In this section, "a ==> b" means that b can be used as a + replacement for a. + + In the following examples, we assume that the Path class is + imported with "from path import Path". + + 1. Replacing os.path.join + -------------------------- + + os.path.join(os.getcwd(), "foobar") + ==> + Path(Path.cwd(), "foobar") + + os.path.join("foo", "bar", "baz") + ==> + Path("foo", "bar", "baz") + + + 2. Replacing os.path.splitext + ------------------------------ + + fname = "Python2.4.tar.gz" + os.path.splitext(fname)[1] + ==> + fname = Path("Python2.4.tar.gz") + fname.ext + + Or if you want both parts: + + fname = "Python2.4.tar.gz" + base, ext = os.path.splitext(fname) + ==> + fname = Path("Python2.4.tar.gz") + base, ext = fname.namebase, fname.extx + + + 3. Replacing glob.glob + ----------------------- + + lib_dir = "/lib" + libs = glob.glob(os.path.join(lib_dir, "*s.o")) + ==> + lib_dir = Path("/lib") + libs = lib_dir.files("*.so") + + +Deprecations + + Introducing this module to the standard library introduces a need + for the "weak" deprecation of a number of existing modules and + functions. These modules and functions are so widely used that + they cannot be truly deprecated, as in generating + DeprecationWarning. Here "weak deprecation" means notes in the + documentation only. + + The table below lists the existing functionality that should be + deprecated. + + Path method/property Deprecates function + -------------------- ------------------- + normcase() os.path.normcase() + normpath() os.path.normpath() + realpath() os.path.realpath() + expanduser() os.path.expanduser() + expandvars() os.path.expandvars() + parent os.path.dirname() + name 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() + getatime()/atime os.path.getatime() + getctime()/ctime os.path.getctime() + getmtime()/mtime os.path.getmtime() + getsize()/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() + + The Path class deprecates the whole of os.path, shutil, fnmatch + and glob. A big chunk of os is also deprecated. + + +Closed Issues + + A number contentious issues have been resolved since this PEP + first appeared on python-dev: + + * The __div__() method was removed. Overloading the / (division) + operator may be "too much magic" and make path concatenation + appear to be division. The method can always be re-added later + if the BFDL so desires. In its place, __new__() got an *args + argument that accepts both Path and string objects. The *args + are concatenated with os.path.join() which is used to construct + the Path object. These changes obsoleted the problematic + joinpath() method which was removed. + + +Open Issues + + Some functionality of Jason Orendorff's path module have been + omitted: + + * Function for opening a path - better handled by the builtin + open(). + + * Functions for reading and writing whole files - better handled + by file objects' own read() and write() methods. + + * A chdir() function may be a worthy inclusion. + + * A deprecation schedule needs to be set up. How much + functionality should Path implement? How much of existing + functionality should it deprecate and when? + + * The name obviously has to be either "path" or "Path," but where + should it live? In its own module or in os? + + * Path implements two ways to retrieve some filesystem + information. Both the properties atime, mtime, ctime and size + and the getters getatime(), getmtime(), getctime() and + getsize(). This is clearly not optimal, the information should + *either* be retrieved using properties or getters. Both methods + have advantages and disadvantages. + + The functions and modules that this new module is trying to + replace (os.path, shutil, fnmatch, glob and parts of os) are + expected to be available in future Python versions for a long + time, to preserve backwards compatibility. + + +Reference Implementation + + Currently, the Path class is implemented as a thin wrapper around + the standard library modules fnmatch, glob, os, os.path and + shutil. The intention of this PEP is to move functionality from + the aforementioned modules to Path while they are being + deprecated. + + For more detail and an implementation see: + + http://wiki.python.org/moin/PathModule + + +Examples + + In this section, "a ==> b" means that b can be used as a + replacement for a. + + 1. Make all python files in the a directory executable + ------------------------------------------------------ + + 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) + + 2. Delete emacs backup files + ---------------------------- + + 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() + + 3. Finding the relative path to a file + -------------------------------------- + + b = Path('/users/peter/') + a = Path('/users/peter/synergy/tiki.txt') + a.relpathto(b) + + 4. Splitting a path into directory and filename + ----------------------------------------------- + + os.path.split("/path/to/foo/bar.txt") + ==> + Path("/path/to/foo/bar.txt").splitpath() + + 5. List all Python scripts in the current directory tree + -------------------------------------------------------- + + list(Path().walkfiles("*.py")) + + +References and Footnotes + + [1] Method is not guaranteed to be availible on all platforms. + + Related articles & threads: + + * http://mail.python.org/pipermail/python-dev/2005-June/054439.html + + * http://mail.python.org/pipermail/python-list/2005-July/291071.html + + * http://mail.python.org/pipermail/python-list/2003-July/174289.html + + * "(idea) subclassable string: path object?", van Rossum, 2001 + http://mail.python.org/pipermail/python-dev/2001-August/016663.html + + * "path module v1.0 released", Orendorff, 2003 + http://mail.python.org/pipermail/python-announce-list/2003-January/001984.html + + * http://wiki.python.org/moin/PathClass + + * "PRE-PEP: new Path class", + http://mail.python.org/pipermail/python-list/2004-January/201672.html + + +Copyright + + This document has been placed in the public domain. + + +Local Variables: +mode: indented-text +indent-tabs-mode: nil +sentence-end-double-space: t +fill-column: 70 +coding: latin-1 +End: