PEP 428: poll about the joining syntax

Hello, Since there has been some controversy about the joining syntax used in PEP 428 (filesystem path objects), I would like to run an informal poll about it. Please answer with +1/+0/-0/-1 for each proposal: - `p[q]` joins path q to path p - `p + q` joins path q to path p - `p / q` joins path q to path p - `p.join(q)` joins path q to path p (you can include a rationale if you want, but don't forget to vote :-)) Thank you Antoine. -- Software development and contracting: http://pro.pitrou.net

On Tue, Oct 9, 2012 at 12:24 AM, Guido van Rossum <guido@python.org> wrote:
I don't like any of those; I'd vote for another regular method, maybe p.pathjoin(q).
My own current preference is to take "p.joinpath(q)" straight from path.py (https://github.com/jaraco/path.py/blob/master/path.py#L236). My rationale for disliking all of the poll options (clarified during the previous discussions, so I can summarise it better now): "p[q]", "p + q", "p / q": A method API is desirable *anyway* (for better integration with all the tools that deal with callables in general), and no compelling justification has been provided for offering two ways to do it (mere brevity when writing doesn't cut it, when the result is something that is more cryptic when reading and learning). "p + q", "p.join(q)": passing strings where path objects are needed is expected to be a common error mode, especially for people just starting to use the new API. It is desirable that such errors produce an exception rather than silently producing an incorrect string. I don't *love* joinpath as a name, I just don't actively dislike it the way I do the four presented options (and it has the virtue of the path.py precedent). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Reducing to numeric votes: p[q]: -1 (confusing w.r.t to indexing/slicing, not convinced it is needed) p + q : -1 (confusing w.r.t to strings, not convinced it is needed) p / q : -0 (not convinced it is needed) p.join(q): -0 (confusing w.r.t strings) p.joinpath(q): +1 (avoids confusion, path.py precedent, need a method API anyway) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

p[q]: -1 p + q : +1 p / q : -1 p.join(q): -1 p.joinpath(q): -0 Looks like I am a minority. :-( Rationale: A directory structure with symbolic links is a graph. A path is an ordered set of links. A path can start anywhere in the graph and can end up anywhere. Links are represented by folder names. To me this means a natural representation of a Path as a list of strings which can be serialized in a OS-specific path. In fact today we all do, already: path.split(os.path.sep) and then manipulate the resulting list. Representing the Path with an object that has the same API as a list of strings (add, radd, append, insert, get item, get slice) and a few extra ones, will make it easier for new users to understand it and remember the APIs. I do not like p[q] and p/q because they fire the wrong neurons in my brain. p[q] picks an element in a set, p/q picks a subset of p. I also do not like p.join because p is not a string and it may be confusing. I am not opposed to q.joinpath(q) but it would require that users learn a new API. They cannot just guess it. they would have to look it up. That gives aways the main reason I use Python: it is intuitive. Moreover p.joinpath(q) seems to indicate that q is a path but q could be a string not a Path. Massimo On Oct 8, 2012, at 2:29 PM, Nick Coghlan wrote:

Am 08.10.2012 21:15, schrieb Nick Coghlan:
I dislike + and [] because I find the result too surprising. If I'd be forced to choose between +, / and [] then I would go for / as it looks kinda like a path. +1 for p.joinpath(*args). It's really a must have feature. The name is debatable, though. +0 for p / sub -1 for p + sub and p[sub] Christian

On Tue, 9 Oct 2012 00:45:41 +0530 Nick Coghlan <ncoghlan@gmail.com> wrote:
[...]
How about one_path.to(other_path) ? Regards Antoine. -- Software development and contracting: http://pro.pitrou.net

On 11/10/12 06:07, Antoine Pitrou wrote:
-1 "To" implies to me either: * one_path is mutated to become other_path; or * you supply the end points, and the method finds a path between them neither of which is remotely relevant. It certainly is not a synonym for add/join/combine/concat paths. Brevity is not more important than clarity. -- Steven

Antoine Pitrou writes:
+1
How about one_path.to(other_path) ?
TOOWDTI, yes, but to me what it obviously does is Path("/usr/local/bin").to(Path("/usr/bin")) => Path("../bin") Ie, to me it's another spelling for .relative_to(), except that the operands have reversed. FWIW M€2% YMMV etc. Some random thoughts follow. If you think that is out of keeping with the progress of this thread<wink/>, stop reading now. I just don't think this problem (of convenient and object-oriented paths) is going to get solved. Basically what most of the people who are posting about this seem to want is a subclass of string that DWIMs. The problem is that "DWIM" varies substantially across programmers, and seems to be nondeterministic for some (me, for one). If path "objects" "should" behave like strings with specialized convenience methods, how can you improve on os.path? I haven't seen any answers to that, only "WIBNI Paths looked like strings representing paths?" And only piece by piece at that, no coherent overview of what Paths-like-str might look like from a space station. If we're going to have an object-oriented path module, why can't it be object-oriented? Paths are sequences of path components. They are not sequences of characters. Sorry! Path components are strings (or subclasses thereof), but they have additional syntax (extensions, Windows devices, Windows remote paths). Maybe that, we can do something with! Antoine says that Paths need to be immutable. Makes sense, but does that preclude having MutablePath? Then `mp[-1] += ".ext"` is a natural notation, no? How about `mp[-1] %= ".tex"; mp[-1] += .pdf"`? Then just my_path = MutablePath(arg_path) mutate(my_path) return Path(my_path) does the work safely. As has been noted several times, all paths have syntax resembling URL syntax. Even the semantics are similar, except (of course you are in no way surprised by this) on Windows, where the syntactic role of "scheme" has semantics "device", and there is the issue of the different path separator. Maybe it would be reasonable to forget object-oriented Paths restricted to filesystems and use URLs when you want object-oriented behavior. Under the hood URL methods working with file URLs would be manipulating paths via os.path, perhaps. I realize that this would impose an asymmetric burden on developers on Windows. On the other hand, these days who isn't familiar with URL syntax and passing familiar with its minor differences from file system path semantics? Perhaps the benefits of working with a well-defined object model would outweight the costs, at least when developing new code. In ordinary maintenance or major refactoring, the developer would have the option of continuing to use os.path or homebrew functions to manipulate paths. Steve

On Mon, Oct 08, 2012 at 11:54:06AM -0700, Guido van Rossum wrote:
I don't like any of those; I'd vote for another regular method, maybe p.pathjoin(q).
Path.pathjoin? Like list.listappend and dict.dictupdate perhaps? :-) I'm never going to remember whether it is pathjoin or joinpath. -1 on method names that repeat the type name. -- Steven

I'm a Python developer rather than a developer of Python, but I'd like to ask a question about this option (and implicitly vote against it, I suppose); if you specialize a method name, such as .pathjoin, aren't you implying that methods must be unambiguous even across types and classes? This seems negative. Even if .join is already used for strings, it also makes sense for this use case. Of course, the proposed syntactic sugar options (operator overloading) seems more pathological than either of the method-based options, so I suppose you could consider my votes as -1 to everything else, +.5 to .pathjoin, and +1 to .join. On Mon, Oct 8, 2012 at 2:54 PM, Guido van Rossum <guido@python.org> wrote:

Am 12.10.2012 14:45, schrieb Blake Hyde:
Of course different classes can have methods of the same name. The issue here is that due to the similarity (and interchangeability) of path objects and strings it is likely that people get them mixed up every now and then, and if .join() works on both objects the failure mode (strange result from str.join when you expected path.join) is horribly confusing. It's the same argument against the "+" operator. (Apart from the other downside that it will act differently depending on *two* objects, i.e. both operands.) In contrast, the "/" operator is not defined on strings, but on numbers, and the both the confusion likelihood and failure mode of mixing numbers and strings are much less severe. It's really kind of the same reason why integer floor division was awkward with "/", and has been changed to "//" in Python 3. Georg

Georg Brandl wrote:
I don't understand the "horribly confusing" part. Sure, when I got them mixed up and ended up with a plain ol' string instead of a really cool Path it took a moment to figure out where I had made the error, but the traceback of "AttributeError: 'str' object has no attribute 'path'" left absolutely no room for confusion as to what the problem was. ~Ethan~

On Sat, Oct 13, 2012 at 2:27 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
Now, instead of retrieving an attribute, call str() and send the path name over a pipe or socket, or save it to a file. Instead of an immediate error, you'll get a bad path somewhere *else*, and have to track down where the data corruption came from (which not even be in the current process, or in a process that was even running on the current machine). Making "+" and "Path.join" mean something different from what they mean when called on strings is, in the specific case of a path representation, far too likely to lead to data corruption bugs for us to be happy with allowing it in the standard library. This is one I think Jason Orendorff's original path.py got right, which is why my current preference is "just copy path.py and use / and Path.joinpath". Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sat, 13 Oct 2012 04:26:44 +1000 Nick Coghlan <ncoghlan@gmail.com> wrote:
This is my current preference too. I don't like joinpath(), but as long as there is an operator to do the same thing I don't care :-) Regards Antoine. -- Software development and contracting: http://pro.pitrou.net

On 12 October 2012 20:39, Georg Brandl <g.brandl@gmx.net> wrote:
I don't know about you, but I found that so horribly confusing I had to check the output. I'm just not used to thinking of str.join(str) as sensible, and I could not for the life of me see where the output 'l/usri/usrb' came from. Where was "lib"? I might just have been an idiot for a minute, but it'll just get harder in real code. And I'm not the worst for stupid mistakes: we wan't newbies to be able (and want) to use the built in path modules. When they come back wondering why
homepath.join("joshua").join(".config")
returned
'.j/homeo/homes/homeh/homeu/homeacj/homeo/homes/homeh/homeu/homeaoj/homeo/homes/homeh/homeu/homeanj/homeo/homes/homeh/homeu/homeafj/homeo/homes/homeh/homeu/homeaij/homeo/homes/homeh/homeu/homeag'
we are going to have a problem. So I agree with you [Georg Brandl] here. I would even rather .pathjoin/.joinpath than .join despite the utterly painful name*. * As others have stated, if you like it why not .strjoin and .dictupdate and .listappend?

`p[q]` -1 `p + q` -1 ('+' should just tack on to the filename field) `p / q` +1 `p.join(q)` +0

On Mon, Oct 08, 2012 at 08:47:07PM +0200, Antoine Pitrou <solipsis@pitrou.net> wrote:
- `p[q]` joins path q to path p
-1. Confusing with p[-2]
- `p + q` joins path q to path p
-0. What is "path addition"? Concatenation? Joining? Puzzled...
- `p / q` joins path q to path p
+0. Again, "path division" is a bit strange but at least I understand '/' is the separation symbol.
- `p.join(q)` joins path q to path p
+1. That one I love best, even with the name "join". I used to use os.path.join() quite extensively so there is no chance I confuse that with str.join(). Oleg. -- Oleg Broytman http://phdru.name/ phd@phdru.name Programmers don't die, they just GOSUB without RETURN.

On 2012-10-08 19:47, Antoine Pitrou wrote:
-1. I would much prefer subscripting to be used to slice paths, e.g. p[-1] == os.path.basename(p).
- `p + q` joins path q to path p
+0. I would prefer that to mean "join without directory separator", e.g. Path("/foo/bar") + ".txt" == Path("/foo/bar.txt").
- `p / q` joins path q to path p
+1. Join with directory separator, e.g. Path("/foo") / "bar" == Path("/foo/bar").
- `p.join(q)` joins path q to path p
+0
(you can include a rationale if you want, but don't forget to vote :-))

On Mon, Oct 08, 2012 at 08:47:07PM +0200, Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1
- `p + q` joins path q to path p
-1 (or +0 if q is forbidden from being a string)
- `p / q` joins path q to path p
-1
- `p.join(q)` joins path q to path p
+1 -- Andrew McNabb http://www.mcnabbs.org/andrew/ PGP Fingerprint: 8A17 B57C 6879 1863 DE55 8012 AB4D 6098 8826 6868

On 2012-10-08, at 20:47 , Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1
- `p + q` joins path q to path p
-1
- `p / q` joins path q to path p
+0, looks like a unix path although others will have issues
- `p.join(q)` joins path q to path p
+1, especially if `p.join(*q)`, strongly reminiscent of os.path.join (which I often import "bare" in path-heavy code), I don't think the common naming with str.join is an issue anymore than it is for threading.Thread.join.
- `p.joinpath(q)` joins path q to path p
same as `join`, although more of a +0.9 as it's longer without benefits.

On Monday, 8 October 2012, Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1 it isn't really indexing
- `p + q` joins path q to path p
-1 risk of ambiguity (string concatenation, e.g. it's too easy to assume you can add an extension with p + '.txt')
- `p / q` joins path q to path p
-0 best of the operator options
- `p.join(q)` joins path q to path p
+0 would like it except for the risk of silent errors if p is a string p.joinpath(q) +1 I wish there was a better name, but I doubt one will appear :-( Paul

On 2012-10-08 23:17, Richard Oudkerk wrote:
I like the short 'add'. A small problem I see with 'add' (and with 'append') is that the outcome of adding (or appending) an absolute path is too surprising, unlike with the 'join' or 'joinpath' names. Also, How would we add an extension to a path (without turning it into a str first)? Will there be a method called addext() or addsuffix() as the .ext/.suffix property is immutable? The suggestions I saw in the thread so far targeted substituting the extension, not adding. Regarding '/', I would like to mention Scapy [1], the packet manipulation program. From its documentation: "The / operator has been used as a composition operator between two layers". The '/' feels natural to use with Scapy. An example from the docs:
Let’s say I want a broadcast MAC address, and IP payload to ketchup.com and to mayo.com, TTL value from 1 to 9, and an UDP payload:
Ether(dst="ff:ff:ff:ff:ff:ff")/IP(dst=["ketchup.com","mayo.com"],ttl=(1,9))/UDP()
Regards, TB [1] http://www.secdev.org/projects/scapy/

On Tue, Oct 09, 2012 at 01:02:05AM +0200, "T.B." <bauertomer@gmail.com> wrote:
Except that layers are divided (pun intended) in wrong order. It seems that Ether is at the top where the traditional stack order from Ether to UDP is from bottom to top. Oleg. -- Oleg Broytman http://phdru.name/ phd@phdru.name Programmers don't die, they just GOSUB without RETURN.

T.B. wrote:
I don't think it's any less surprising with "join" -- when you join two things, you just as much expect both of them to be part of the result. There doesn't seem to be any concise term that encompasses all the nuances of the operation. Using an arbitrarily chosen operator would at least have the advantage of sidestepping the whole concern. Programmer 1: "Hey, what does ^ do on path objects?" Programmer 2: "It concatenates them with a path separator between, except when the second one is an absolute path, in which case it just returns the second one." Programmer 1: "That's so obscure. Why didn't they just define a concat_with_pathsep_or_second_if_absolute() method... oh, wait, I think I see..." -- Greg

I'm forwarding Barry's answer: -------- Message transféré -------- De: Barry Warsaw <barry@python.org> À: Antoine Pitrou <public-solipsis-xNDA5Wrcr86sTnJN9+BGXg@plane.gmane.org> Sujet: Re: PEP 428: poll about the joining syntax Date: Mon, 8 Oct 2012 15:17:01 -0400 Like a good American low-information voter, I'll cast my ballot without having read PEP 428. On Oct 08, 2012, at 08:47 PM, Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1 Definitely not intuitive.
- `p + q` joins path q to path p
+0. IMHO, the most intuitive, but causes problems when you just want to tack on an extension, er, suffix. I guess if PathObj + str works it's not so bad.
- `p / q` joins path q to path p
+0. Cute! Too *nix centric?
- `p.join(q)` joins path q to path p
-0. Explicit (yay), but a bit verbose (boo). Maybe this should be the default underlying API, with one of the above as nice syntactic sugar? -Barry

`p[q]` 0 `p + q` -1 `p / q` +0 `p.join(q)` +1 `p.pathjoin(q)` +0 Where .join/.pathjoin shall take argument lists. The arguments my be path objects or strings. Example usage (where filename is a string):
prefix.join(some,path,components,filename+".txt")
I'm against + because how would you do the example above? Because this:
prefix + some + path + components + filename + ".txt"
would do something different than this:
prefix + some + path + components + (filename + ".txt")
Which might surprise a user and is in any case confusing.

On 08.10.12 21:47, Antoine Pitrou wrote:
Of course I have no right to vote, but because the poll is informal, I give my humble opinion.
- `p[q]` joins path q to path p
-1. Counter intuitive and indexing can be used for path splitting.
- `p + q` joins path q to path p
-1. Confusion with strings. path + str can be used for suffix appending.
- `p / q` joins path q to path p
+1. Intuitive. No risk of conflicts.
- `p.join(q)` joins path q to path p
-0. A bit confusion with strings. -0.1 verbose. +0.1 can have many arguments. +0.1 similar to os.path.join. -0.1 but have a little different semantic.
- `p.pathjoin(q)` joins path q to path p
+0. Same as `p.join(q)`, but more verbose (-) and less confusion (+).

On Oct 8, 2012, at 11:47 AM, Antoine Pitrou <solipsis@pitrou.net> wrote:
If we want a p.pathjoin method, it would make sense to me for it to work similar to urllib.parse.urljoin, i.e., if the joined path is absolute, have it replace the path, except possibly for the drive on windows. I like to follow any parallels to list that make sense. Ryan

On 9 October 2012 00:19, Ryan D Hiebert <ryan@ryanhiebert.com> wrote:
If we want a p.pathjoin method, it would make sense to me for it to work similar to urllib.parse.urljoin
The parallel with urljoin also suggests that pathjoin is a better name than joinpath. But note that I've seen both used in this thread - there is obviously some level of confusion possible. Paul.

On Tue, Oct 9, 2012 at 12:12 AM, Paul Moore <p.f.moore@gmail.com> wrote:
pathjoin is strikes well, if we are already accustomed with the term 'urljoin'. Ryan - the protocols of those two joins will vary and should not be confused. Also pathjoin specifics would be listed in PEP 428. Thanks Senthil

- `p[q]` joins path q to path p -1. Because I can't imagine consistent iterator and __contains__. - `p + q` joins path q to path p +0. I prefer '/' because it is very common path separator. - `p / q` joins path q to path p +1 - `p.join(q)` joins path q to path p +1. But `q` should be `*q`. -1 on `pathjoin`. `Path.pathjoin` is ugly. The `urljoin()` is OK because it is just a function. -- INADA Naoki <songofacandy@gmail.com>

On Tue, Oct 9, 2012 at 1:22 PM, INADA Naoki <songofacandy@gmail.com> wrote:
-1 on `pathjoin`. `Path.pathjoin` is ugly. The `urljoin()` is OK because it is just a function.
Hmm, this is a *very* interesting point. *All* of the alternatives presented are mainly replacements for just doing this: Path(p, q) And if you want a partially applied version, that's just: prefix = functools.partial(Path, p) So perhaps the right answer for the initial API is: no method, no operator, just use the constructor? The counterargument is that this approach doesn't let "p" control the return type the way a method or operator does, though. It does suggest a whole new class of verbs though, like "make" or "build". Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan <ncoghlan@...> writes:
Well, you would have to use either PurePath(p, q) or Path(p, q) based on whether p is pure or concrete. Unless we make the constructor more magic and let Path() switch to PurePath() when the first argument is a pure path. Which does sounds a bit too magic to me (Path would instantiate something which is not a Path instance...).
It does suggest a whole new class of verbs though, like "make" or "build".
They are rather vague, though. Regards Antoine.

I just consulted a thesaurus about synonyms for 'append', and it came up with 'affix' and 'adjoin'. -- Greg

On Tue, Oct 09, 2012 at 11:34:24PM +1300, Greg Ewing wrote:
I just consulted a thesaurus about synonyms for 'append', and it came up with 'affix' and 'adjoin'.
Yet another possibility is "combine", which unlike "join", gives less of an implicit guarantee that it's a straightforward concatenation. Other synonyms for "combine" include "couple", "fuse", and "hitch". -- Andrew McNabb http://www.mcnabbs.org/andrew/ PGP Fingerprint: 8A17 B57C 6879 1863 DE55 8012 AB4D 6098 8826 6868

On Tue, Oct 9, 2012 at 3:13 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
It does suggest a whole new class of verbs though, like "make" or "build".
They are rather vague, though.
Agreed, but what os.path.join actually *does* is rather vague, since it is really "joins the path segments, starting with the last absolute path segment". I'm mostly playing Devil's Advocate here, but I thought it was a very good point that requesting a Path or PurePath directly is *always* going to be an option. And really, the only time you *need* a PurePath is when you want to construct a non-native path - for native paths you'll always be able to create it, some methods just won't work if it doesn't actually exist on the filesystem. Using Victor's more complicated example, compare: open(os.path.join(home, ".config", name + ".conf")) open(str(Path(home, ".config", name + ".conf"))) open(home.join(".config", name + ".conf"))) open(str(home / ".config" / name + ".conf")) Path(home, ".config", name + ".conf").open() home.join(".config", name + ".conf").open() (home / ".config" / name + ".conf").open() One note regarding the extra "str()" calls relative to something like path.py: we get to define the language, so we can get the benefits of implicit conversion without the many downsides by *defining a new conversion method*, such as __fspath__. That may provide an attractive alternative to offering methods that shadow builtin functions: open(Path(home, ".config", name + ".conf")) open(home.join(".config", name + ".conf")) open(home / ".config" / name + ".conf") "As easy to use as path.py, without the design compromises imposed by inheriting from str" is a worthwhile goal. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Tue, Oct 9, 2012 at 6:04 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
The other thing I thought might be useful is to try to tie it into the new "opener" parameter for open somehow, On the other hand, that's getting further into full-blown filesystem emulation territory (http://packages.python.org/fs/). That may not be a bad thing, though - a proper filesystem abstraction might finally let us deal with encoding and case-sensitivity issues in a sane way, since they're filesystem dependent rather than platform dependent (e.g. opening FAT/FAT32 devices on *nix systems). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

`p[q]` 0 `p + q` +0.5 `p / q` +1 `p.join(q)` -1 `p.pathjoin(q)` -1 `p.pathjoin(q)` -1 `p.add(q)` +0.5 Joining/adding/appending paths is one of the most common ops. Please let's make it short, easy and obvious.

On Oct 8, 2012 5:35 PM, "Eric Snow" <ericsnowcurrently@gmail.com> wrote:
On Mon, Oct 8, 2012 at 12:47 PM, Antoine Pitrou <solipsis@pitrou.net>
wrote:
Changing my vote: p[q] -1 p + q -1 p / q +0 p.pathjoin() +1 A method is essential, regardless of the color the bikeshed ends up. As far as operators go, / is the only option here that doesn't conflict with string/collection APIs. The alternative has an adverse impact on subclassing and on future design choices on the path API. This goes for the method name too. -eric

On Mon, Oct 8, 2012 at 2:47 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
-1 This syntax makes no sense, it doesn't match the syntax in an obvious way
- `p + q` joins path q to path p
-1 Too easy to confuse with string concat
- `p / q` joins path q to path p
+1 Reads like a path, makes logical sense
- `p.join(q)` joins path q to path p
+1 Allows passing as a callable (tho we could just use operator module). I think we should have both / and .join() (you can include a rationale if you want, but don't forget to vote :-))
-- Read my blog! I depend on your acceptance of my opinion! I am interesting! http://techblog.ironfroggy.com/ Follow me if you're into that sort of thing: http://www.twitter.com/ironfroggy

Speaking as a relatively inexperienced user (whose opinions should justly be given little weight as such),
- `p[q]` joins path q to path p -1: Doesn't make sense at first glance
- `p.join(q)` joins path q to path p +1: Except it needs a different name, for the same reasons as +
What about p.unite(q)? The one word definition of 'join' is 'unite' and it's definitely not used by str, and I don't know of anywhere else that it is used. And it's only one extra character instead of the 4 of 'pathjoin' or 'joinpath'.

Summary: - favourite & second-favourite choices: `p.join(q)` or `p.add(q)`. - most disliked: `p[q]` `p[q]` -1: looks like indexing or key-lookup, isn't either. `p + q` +0: potential confusion between path component joining and filename suffix appending. `p / q` +0: looks funny if either arg is a literal string. `p.join(q)`: +1: self-explanatory, suggests os.path.join, I am not convinced that Nick's fears about confusing Path.join and str.join will be a problem in practice. `p.pathjoin(q)` and `p.joinpath(q)` -1: dislike repeating the type name in the method name; also, too easy to forget which one is used. `p.add(q)` +0.5: nice and short, but not quite self-explanatory. `p.append(q)` -0: suggests an in-place modification. -- Steven

And I knew there was another suggestion tickling around in my subconscious... I have a new favourite: `p & q` +1: unlikely to be confused with int or set &; strings do not currently use it; suggests concatenation; short, can work with two paths or path and string if needed. p + ".ext" to add a suffix to the file name; an error if p is a directory. "spam" + p should probably an error. I can't think of a good use case for prepending a string to a path. p & q to concatenate (join) path q to path p. p.add(q [, r, s, ...]) to concatentation multiple path components at once, more efficient than p & q & r & ..., and to make the function more discoverable and searchable. -- Steven

Antoine Pitrou <solipsis@pitrou.net> writes:
I hope you count U+2212 MINUS SIGN and not only U+002D HYPHEN-MINUS :-)
- `p[q]` joins path q to path p
−1. Ugly and counter-intuitive. Bracket syntax is for accessing items of a collection.
- `p + q` joins path q to path p
+1. Works as I'd expect it to work, and is easily discovered.
- `p / q` joins path q to path p
−1. ‘/’ as a Python operator strongly connotes “division”, and this isn't it.
- `p.join(q)` joins path q to path p
+1. Explicit and clear.
(you can include a rationale if you want, but don't forget to vote :-))
Thanks for the poll. -- \ “The whole area of [treating source code as intellectual | `\ property] is almost assuring a customer that you are not going | _o__) to do any innovation in the future.” —Gary Barnett | Ben Finney

Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1, confuses operation on a path with operation on the object named by the path.
- `p + q` joins path q to path p
-0.9, interacts with string concatenation in undesirable ways
- `p / q` joins path q to path p
+1
- `p.join(q)` joins path q to path p
-0.9, 'append' would be clearer IMO -- Greg

Antoine Pitrou <solipsis@pitrou.net> wrote:
- `p[q]` joins path q to path p
-1 I think, this is listed as example in PEP 428. I had to look it up to understand. Not intuitive (to me atleast) as join.
- `p + q` joins path q to path p
+0. I would be +1. But in the PEP you have listed that we need a way separate path behaviors from confusing with builtins Though it provides a lot of convenience, it can be confused with str behaviors or other object behaviors.
- `p / q` joins path q to path p
-1.
- `p.join(q)` joins path q to path p
+0
`p.pathjoin(q)`
+1 It is very explicit and hard to get it wrong.

Eric Snow <ericsnowcurrently@...> writes:
Or, precisely, since it's provisional, we needn't *wait* before we provide an operator. Any stdlib module API can be augmented; what provisional modules allow in addition to that is to modify or remove existing APIs. So we can, say, enable Path.__truediv__ and wait for people to complain about it. By the way, it's not new to have dual operator / method APIs. For example set.union and set.__or__; list.extend and list.__iadd__; etc. Regards Antoine.

On Tue, Oct 9, 2012 at 3:32 PM, Terry Reedy <tjreedy@udel.edu> wrote:
I like the [] syntax. ZODB works this way when the subpath name is not a valid Python identifier. a.b['c-d'] would be like a/b/c-d if ZODB was a filesystem. I like the + syntax. No one has suggested overloading the > operator? p1 > p2 > p3 The < operator would keep its normal use for sorting. ;-)

I cannot decide with such trivial examples. More realistic examples: --- def read_config(name): home = Path(os.path.expanduser("~")) # pathlib doesn't support expanduser?? with open(home / ".config" / name + ".conf") as f: return fp.read() --- The join() method has an advantage: it avoids temporary objects (config / ".config" is my example). --- def read_config(name): home = Path(os.path.expanduser("~")) # pathlib doesn't support expanduser?? with open(home.join(".config", name + ".conf")) as f: return fp.read() --- It should work even if name is a Path object, so Path + str should concatenate a suffix without adding directory separator. My vote:
- `p[q]` joins path q to path p
home[".config"][name] # + ".conf" ??? -1
- `p + q` joins path q to path p
home + ".config" + name # + ".conf" ??? -1 -> Path + str must be reserved to add a suffix
- `p / q` joins path q to path p
home / ".config" / name + ".conf" +1: it's natural, but maybe "suboptimal" in performance
- `p.join(q)` joins path q to path p
home.join(".config", name + ".conf") +0: more efficient, but it may be confusing with str.join() which is very different. a.join(b, c) : a is the separator or the root directory, depending on the type of a (str or Path). We should avoid confusion between Path and str methods and operator (a+b and a.join(b)). Victor

On 08/10/2012 19:47, Antoine Pitrou wrote:
-1 yuck
- `p + q` joins path q to path p
+1 Pythonic
- `p / q` joins path q to path p
-0 veering to +0 it just seems wrong but I can't strongly put my finger on why.
- `p.join(q)` joins path q to path p
-1 likely to confuse idiots like me as it's too similar to string.join. For the last one would there be a real need for a path.join method, or has this already been discussed and I've forgotten about it?
-- Cheers. Mark Lawrence.

- `p[q]` joins path q to path p
For some obscure reason I really like this one. I can understand the arguments against it though. So I'll probably be the only one to be +0 on this proposal. - `p + q` joins path q to path p
-0 I agree with who says this operator should be used as suffix appending, and not for path components. - `p / q` joins path q to path p
+0 I'm not against the div operator I'd prefer to use another one. I'm a *nix person but I find this proposal too *nix-centric. About the operator: I really like *Steven D'Aprano*'s proposal: I find *&*just perfect. I'm way more than +1 on it. - `p.join(q)` joins path q to path p
+1 I feel the need for a method, in parallel with some operator. About the name: if join is rejected I am: +1 on add() +1 on adjoin() +0 on append() -1 on pathjoin() / joinpath() -- too long, too similar, way too ugly.

On Mon, Oct 8, 2012 at 3:47 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
-1 ... semantics too different from usual meaning of [ ] - `p + q` joins path q to path p
-1 ... too problematic with strings... - `p / q` joins path q to path p
+0 ... my brain is hard-wired to see / as division or equivalent (e.g. quotient groups, etc.) - `p.join(q)` joins path q to path p
+1 .... only remaining choice. Besides, I think an explicit method makes more sense. If paths were only directories, I would have really like p.cd(q) [with support for multiple arguments of course] as everyone (I think) would naturally recognize this... However, since we can have file as well, I was trying to think of something to mean change path p so that it now points to the joining of path p and q ... and suggest p.goto(q) ;-) ;-) André

On Mon, Oct 8, 2012 at 8:47 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
- `p[q]` joins path q to path p
-1. Much less intuitive than the other two proposed operators.
- `p + q` joins path q to path p
-1. Silently does the wrong thing if p and q are both strings.
- `p / q` joins path q to path p
+1. Reads naturally, and fails loudly if p is a string.
- `p.join(q)` joins path q to path p
-1. Produces a nonsensical result if p and q are both strings. I'd be +1 on `p.joinpath(q)`, since it doesn't have this problem. Cheers, Nadeem

On 08/10/2012 19:47, Antoine Pitrou wrote:
How about using the caret symbol to join so `p ^ q`? Rationale, it looks like a miniature combination of the backslash and forwardslash so should keep Windows and *nix camps happy, plus it's only used in Python (I think?) for bitwise operations so shouldn't confuse anybody. Parachute is ready for the antiaircraft fire :) -- Cheers. Mark Lawrence.

On Tue, Oct 9, 2012 at 12:24 AM, Guido van Rossum <guido@python.org> wrote:
I don't like any of those; I'd vote for another regular method, maybe p.pathjoin(q).
My own current preference is to take "p.joinpath(q)" straight from path.py (https://github.com/jaraco/path.py/blob/master/path.py#L236). My rationale for disliking all of the poll options (clarified during the previous discussions, so I can summarise it better now): "p[q]", "p + q", "p / q": A method API is desirable *anyway* (for better integration with all the tools that deal with callables in general), and no compelling justification has been provided for offering two ways to do it (mere brevity when writing doesn't cut it, when the result is something that is more cryptic when reading and learning). "p + q", "p.join(q)": passing strings where path objects are needed is expected to be a common error mode, especially for people just starting to use the new API. It is desirable that such errors produce an exception rather than silently producing an incorrect string. I don't *love* joinpath as a name, I just don't actively dislike it the way I do the four presented options (and it has the virtue of the path.py precedent). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Reducing to numeric votes: p[q]: -1 (confusing w.r.t to indexing/slicing, not convinced it is needed) p + q : -1 (confusing w.r.t to strings, not convinced it is needed) p / q : -0 (not convinced it is needed) p.join(q): -0 (confusing w.r.t strings) p.joinpath(q): +1 (avoids confusion, path.py precedent, need a method API anyway) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

p[q]: -1 p + q : +1 p / q : -1 p.join(q): -1 p.joinpath(q): -0 Looks like I am a minority. :-( Rationale: A directory structure with symbolic links is a graph. A path is an ordered set of links. A path can start anywhere in the graph and can end up anywhere. Links are represented by folder names. To me this means a natural representation of a Path as a list of strings which can be serialized in a OS-specific path. In fact today we all do, already: path.split(os.path.sep) and then manipulate the resulting list. Representing the Path with an object that has the same API as a list of strings (add, radd, append, insert, get item, get slice) and a few extra ones, will make it easier for new users to understand it and remember the APIs. I do not like p[q] and p/q because they fire the wrong neurons in my brain. p[q] picks an element in a set, p/q picks a subset of p. I also do not like p.join because p is not a string and it may be confusing. I am not opposed to q.joinpath(q) but it would require that users learn a new API. They cannot just guess it. they would have to look it up. That gives aways the main reason I use Python: it is intuitive. Moreover p.joinpath(q) seems to indicate that q is a path but q could be a string not a Path. Massimo On Oct 8, 2012, at 2:29 PM, Nick Coghlan wrote:

Am 08.10.2012 21:15, schrieb Nick Coghlan:
I dislike + and [] because I find the result too surprising. If I'd be forced to choose between +, / and [] then I would go for / as it looks kinda like a path. +1 for p.joinpath(*args). It's really a must have feature. The name is debatable, though. +0 for p / sub -1 for p + sub and p[sub] Christian

On Tue, 9 Oct 2012 00:45:41 +0530 Nick Coghlan <ncoghlan@gmail.com> wrote:
[...]
How about one_path.to(other_path) ? Regards Antoine. -- Software development and contracting: http://pro.pitrou.net

On 11/10/12 06:07, Antoine Pitrou wrote:
-1 "To" implies to me either: * one_path is mutated to become other_path; or * you supply the end points, and the method finds a path between them neither of which is remotely relevant. It certainly is not a synonym for add/join/combine/concat paths. Brevity is not more important than clarity. -- Steven

Antoine Pitrou writes:
+1
How about one_path.to(other_path) ?
TOOWDTI, yes, but to me what it obviously does is Path("/usr/local/bin").to(Path("/usr/bin")) => Path("../bin") Ie, to me it's another spelling for .relative_to(), except that the operands have reversed. FWIW M€2% YMMV etc. Some random thoughts follow. If you think that is out of keeping with the progress of this thread<wink/>, stop reading now. I just don't think this problem (of convenient and object-oriented paths) is going to get solved. Basically what most of the people who are posting about this seem to want is a subclass of string that DWIMs. The problem is that "DWIM" varies substantially across programmers, and seems to be nondeterministic for some (me, for one). If path "objects" "should" behave like strings with specialized convenience methods, how can you improve on os.path? I haven't seen any answers to that, only "WIBNI Paths looked like strings representing paths?" And only piece by piece at that, no coherent overview of what Paths-like-str might look like from a space station. If we're going to have an object-oriented path module, why can't it be object-oriented? Paths are sequences of path components. They are not sequences of characters. Sorry! Path components are strings (or subclasses thereof), but they have additional syntax (extensions, Windows devices, Windows remote paths). Maybe that, we can do something with! Antoine says that Paths need to be immutable. Makes sense, but does that preclude having MutablePath? Then `mp[-1] += ".ext"` is a natural notation, no? How about `mp[-1] %= ".tex"; mp[-1] += .pdf"`? Then just my_path = MutablePath(arg_path) mutate(my_path) return Path(my_path) does the work safely. As has been noted several times, all paths have syntax resembling URL syntax. Even the semantics are similar, except (of course you are in no way surprised by this) on Windows, where the syntactic role of "scheme" has semantics "device", and there is the issue of the different path separator. Maybe it would be reasonable to forget object-oriented Paths restricted to filesystems and use URLs when you want object-oriented behavior. Under the hood URL methods working with file URLs would be manipulating paths via os.path, perhaps. I realize that this would impose an asymmetric burden on developers on Windows. On the other hand, these days who isn't familiar with URL syntax and passing familiar with its minor differences from file system path semantics? Perhaps the benefits of working with a well-defined object model would outweight the costs, at least when developing new code. In ordinary maintenance or major refactoring, the developer would have the option of continuing to use os.path or homebrew functions to manipulate paths. Steve

On Mon, Oct 08, 2012 at 11:54:06AM -0700, Guido van Rossum wrote:
I don't like any of those; I'd vote for another regular method, maybe p.pathjoin(q).
Path.pathjoin? Like list.listappend and dict.dictupdate perhaps? :-) I'm never going to remember whether it is pathjoin or joinpath. -1 on method names that repeat the type name. -- Steven

I'm a Python developer rather than a developer of Python, but I'd like to ask a question about this option (and implicitly vote against it, I suppose); if you specialize a method name, such as .pathjoin, aren't you implying that methods must be unambiguous even across types and classes? This seems negative. Even if .join is already used for strings, it also makes sense for this use case. Of course, the proposed syntactic sugar options (operator overloading) seems more pathological than either of the method-based options, so I suppose you could consider my votes as -1 to everything else, +.5 to .pathjoin, and +1 to .join. On Mon, Oct 8, 2012 at 2:54 PM, Guido van Rossum <guido@python.org> wrote:

Am 12.10.2012 14:45, schrieb Blake Hyde:
Of course different classes can have methods of the same name. The issue here is that due to the similarity (and interchangeability) of path objects and strings it is likely that people get them mixed up every now and then, and if .join() works on both objects the failure mode (strange result from str.join when you expected path.join) is horribly confusing. It's the same argument against the "+" operator. (Apart from the other downside that it will act differently depending on *two* objects, i.e. both operands.) In contrast, the "/" operator is not defined on strings, but on numbers, and the both the confusion likelihood and failure mode of mixing numbers and strings are much less severe. It's really kind of the same reason why integer floor division was awkward with "/", and has been changed to "//" in Python 3. Georg

Georg Brandl wrote:
I don't understand the "horribly confusing" part. Sure, when I got them mixed up and ended up with a plain ol' string instead of a really cool Path it took a moment to figure out where I had made the error, but the traceback of "AttributeError: 'str' object has no attribute 'path'" left absolutely no room for confusion as to what the problem was. ~Ethan~

On Sat, Oct 13, 2012 at 2:27 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
Now, instead of retrieving an attribute, call str() and send the path name over a pipe or socket, or save it to a file. Instead of an immediate error, you'll get a bad path somewhere *else*, and have to track down where the data corruption came from (which not even be in the current process, or in a process that was even running on the current machine). Making "+" and "Path.join" mean something different from what they mean when called on strings is, in the specific case of a path representation, far too likely to lead to data corruption bugs for us to be happy with allowing it in the standard library. This is one I think Jason Orendorff's original path.py got right, which is why my current preference is "just copy path.py and use / and Path.joinpath". Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sat, 13 Oct 2012 04:26:44 +1000 Nick Coghlan <ncoghlan@gmail.com> wrote:
This is my current preference too. I don't like joinpath(), but as long as there is an operator to do the same thing I don't care :-) Regards Antoine. -- Software development and contracting: http://pro.pitrou.net

Nick Coghlan wrote:
Okay, that makes sense. I think we should settle on one of the possibilities that does /not/ duplicate the word 'path', however. That's one of those things that drives me nuts. ;) (It's a Path object -- of /course/ it's joining path stuff!) ~Ethan~

On 12 October 2012 20:39, Georg Brandl <g.brandl@gmx.net> wrote:
I don't know about you, but I found that so horribly confusing I had to check the output. I'm just not used to thinking of str.join(str) as sensible, and I could not for the life of me see where the output 'l/usri/usrb' came from. Where was "lib"? I might just have been an idiot for a minute, but it'll just get harder in real code. And I'm not the worst for stupid mistakes: we wan't newbies to be able (and want) to use the built in path modules. When they come back wondering why
homepath.join("joshua").join(".config")
returned
'.j/homeo/homes/homeh/homeu/homeacj/homeo/homes/homeh/homeu/homeaoj/homeo/homes/homeh/homeu/homeanj/homeo/homes/homeh/homeu/homeafj/homeo/homes/homeh/homeu/homeaij/homeo/homes/homeh/homeu/homeag'
we are going to have a problem. So I agree with you [Georg Brandl] here. I would even rather .pathjoin/.joinpath than .join despite the utterly painful name*. * As others have stated, if you like it why not .strjoin and .dictupdate and .listappend?

`p[q]` -1 `p + q` -1 ('+' should just tack on to the filename field) `p / q` +1 `p.join(q)` +0

On Mon, Oct 08, 2012 at 08:47:07PM +0200, Antoine Pitrou <solipsis@pitrou.net> wrote:
- `p[q]` joins path q to path p
-1. Confusing with p[-2]
- `p + q` joins path q to path p
-0. What is "path addition"? Concatenation? Joining? Puzzled...
- `p / q` joins path q to path p
+0. Again, "path division" is a bit strange but at least I understand '/' is the separation symbol.
- `p.join(q)` joins path q to path p
+1. That one I love best, even with the name "join". I used to use os.path.join() quite extensively so there is no chance I confuse that with str.join(). Oleg. -- Oleg Broytman http://phdru.name/ phd@phdru.name Programmers don't die, they just GOSUB without RETURN.

On 2012-10-08 19:47, Antoine Pitrou wrote:
-1. I would much prefer subscripting to be used to slice paths, e.g. p[-1] == os.path.basename(p).
- `p + q` joins path q to path p
+0. I would prefer that to mean "join without directory separator", e.g. Path("/foo/bar") + ".txt" == Path("/foo/bar.txt").
- `p / q` joins path q to path p
+1. Join with directory separator, e.g. Path("/foo") / "bar" == Path("/foo/bar").
- `p.join(q)` joins path q to path p
+0
(you can include a rationale if you want, but don't forget to vote :-))

On Mon, Oct 08, 2012 at 08:47:07PM +0200, Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1
- `p + q` joins path q to path p
-1 (or +0 if q is forbidden from being a string)
- `p / q` joins path q to path p
-1
- `p.join(q)` joins path q to path p
+1 -- Andrew McNabb http://www.mcnabbs.org/andrew/ PGP Fingerprint: 8A17 B57C 6879 1863 DE55 8012 AB4D 6098 8826 6868

On 2012-10-08, at 20:47 , Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1
- `p + q` joins path q to path p
-1
- `p / q` joins path q to path p
+0, looks like a unix path although others will have issues
- `p.join(q)` joins path q to path p
+1, especially if `p.join(*q)`, strongly reminiscent of os.path.join (which I often import "bare" in path-heavy code), I don't think the common naming with str.join is an issue anymore than it is for threading.Thread.join.
- `p.joinpath(q)` joins path q to path p
same as `join`, although more of a +0.9 as it's longer without benefits.

On Monday, 8 October 2012, Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1 it isn't really indexing
- `p + q` joins path q to path p
-1 risk of ambiguity (string concatenation, e.g. it's too easy to assume you can add an extension with p + '.txt')
- `p / q` joins path q to path p
-0 best of the operator options
- `p.join(q)` joins path q to path p
+0 would like it except for the risk of silent errors if p is a string p.joinpath(q) +1 I wish there was a better name, but I doubt one will appear :-( Paul

On 2012-10-08 23:17, Richard Oudkerk wrote:
I like the short 'add'. A small problem I see with 'add' (and with 'append') is that the outcome of adding (or appending) an absolute path is too surprising, unlike with the 'join' or 'joinpath' names. Also, How would we add an extension to a path (without turning it into a str first)? Will there be a method called addext() or addsuffix() as the .ext/.suffix property is immutable? The suggestions I saw in the thread so far targeted substituting the extension, not adding. Regarding '/', I would like to mention Scapy [1], the packet manipulation program. From its documentation: "The / operator has been used as a composition operator between two layers". The '/' feels natural to use with Scapy. An example from the docs:
Let’s say I want a broadcast MAC address, and IP payload to ketchup.com and to mayo.com, TTL value from 1 to 9, and an UDP payload:
Ether(dst="ff:ff:ff:ff:ff:ff")/IP(dst=["ketchup.com","mayo.com"],ttl=(1,9))/UDP()
Regards, TB [1] http://www.secdev.org/projects/scapy/

On Tue, Oct 09, 2012 at 01:02:05AM +0200, "T.B." <bauertomer@gmail.com> wrote:
Except that layers are divided (pun intended) in wrong order. It seems that Ether is at the top where the traditional stack order from Ether to UDP is from bottom to top. Oleg. -- Oleg Broytman http://phdru.name/ phd@phdru.name Programmers don't die, they just GOSUB without RETURN.

T.B. wrote:
I don't think it's any less surprising with "join" -- when you join two things, you just as much expect both of them to be part of the result. There doesn't seem to be any concise term that encompasses all the nuances of the operation. Using an arbitrarily chosen operator would at least have the advantage of sidestepping the whole concern. Programmer 1: "Hey, what does ^ do on path objects?" Programmer 2: "It concatenates them with a path separator between, except when the second one is an absolute path, in which case it just returns the second one." Programmer 1: "That's so obscure. Why didn't they just define a concat_with_pathsep_or_second_if_absolute() method... oh, wait, I think I see..." -- Greg

I'm forwarding Barry's answer: -------- Message transféré -------- De: Barry Warsaw <barry@python.org> À: Antoine Pitrou <public-solipsis-xNDA5Wrcr86sTnJN9+BGXg@plane.gmane.org> Sujet: Re: PEP 428: poll about the joining syntax Date: Mon, 8 Oct 2012 15:17:01 -0400 Like a good American low-information voter, I'll cast my ballot without having read PEP 428. On Oct 08, 2012, at 08:47 PM, Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1 Definitely not intuitive.
- `p + q` joins path q to path p
+0. IMHO, the most intuitive, but causes problems when you just want to tack on an extension, er, suffix. I guess if PathObj + str works it's not so bad.
- `p / q` joins path q to path p
+0. Cute! Too *nix centric?
- `p.join(q)` joins path q to path p
-0. Explicit (yay), but a bit verbose (boo). Maybe this should be the default underlying API, with one of the above as nice syntactic sugar? -Barry

`p[q]` 0 `p + q` -1 `p / q` +0 `p.join(q)` +1 `p.pathjoin(q)` +0 Where .join/.pathjoin shall take argument lists. The arguments my be path objects or strings. Example usage (where filename is a string):
prefix.join(some,path,components,filename+".txt")
I'm against + because how would you do the example above? Because this:
prefix + some + path + components + filename + ".txt"
would do something different than this:
prefix + some + path + components + (filename + ".txt")
Which might surprise a user and is in any case confusing.

On 08.10.12 21:47, Antoine Pitrou wrote:
Of course I have no right to vote, but because the poll is informal, I give my humble opinion.
- `p[q]` joins path q to path p
-1. Counter intuitive and indexing can be used for path splitting.
- `p + q` joins path q to path p
-1. Confusion with strings. path + str can be used for suffix appending.
- `p / q` joins path q to path p
+1. Intuitive. No risk of conflicts.
- `p.join(q)` joins path q to path p
-0. A bit confusion with strings. -0.1 verbose. +0.1 can have many arguments. +0.1 similar to os.path.join. -0.1 but have a little different semantic.
- `p.pathjoin(q)` joins path q to path p
+0. Same as `p.join(q)`, but more verbose (-) and less confusion (+).

On Oct 8, 2012, at 11:47 AM, Antoine Pitrou <solipsis@pitrou.net> wrote:
If we want a p.pathjoin method, it would make sense to me for it to work similar to urllib.parse.urljoin, i.e., if the joined path is absolute, have it replace the path, except possibly for the drive on windows. I like to follow any parallels to list that make sense. Ryan

On 9 October 2012 00:19, Ryan D Hiebert <ryan@ryanhiebert.com> wrote:
If we want a p.pathjoin method, it would make sense to me for it to work similar to urllib.parse.urljoin
The parallel with urljoin also suggests that pathjoin is a better name than joinpath. But note that I've seen both used in this thread - there is obviously some level of confusion possible. Paul.

On Tue, Oct 9, 2012 at 12:12 AM, Paul Moore <p.f.moore@gmail.com> wrote:
pathjoin is strikes well, if we are already accustomed with the term 'urljoin'. Ryan - the protocols of those two joins will vary and should not be confused. Also pathjoin specifics would be listed in PEP 428. Thanks Senthil

- `p[q]` joins path q to path p -1. Because I can't imagine consistent iterator and __contains__. - `p + q` joins path q to path p +0. I prefer '/' because it is very common path separator. - `p / q` joins path q to path p +1 - `p.join(q)` joins path q to path p +1. But `q` should be `*q`. -1 on `pathjoin`. `Path.pathjoin` is ugly. The `urljoin()` is OK because it is just a function. -- INADA Naoki <songofacandy@gmail.com>

On Tue, Oct 9, 2012 at 1:22 PM, INADA Naoki <songofacandy@gmail.com> wrote:
-1 on `pathjoin`. `Path.pathjoin` is ugly. The `urljoin()` is OK because it is just a function.
Hmm, this is a *very* interesting point. *All* of the alternatives presented are mainly replacements for just doing this: Path(p, q) And if you want a partially applied version, that's just: prefix = functools.partial(Path, p) So perhaps the right answer for the initial API is: no method, no operator, just use the constructor? The counterargument is that this approach doesn't let "p" control the return type the way a method or operator does, though. It does suggest a whole new class of verbs though, like "make" or "build". Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan <ncoghlan@...> writes:
Well, you would have to use either PurePath(p, q) or Path(p, q) based on whether p is pure or concrete. Unless we make the constructor more magic and let Path() switch to PurePath() when the first argument is a pure path. Which does sounds a bit too magic to me (Path would instantiate something which is not a Path instance...).
It does suggest a whole new class of verbs though, like "make" or "build".
They are rather vague, though. Regards Antoine.

I just consulted a thesaurus about synonyms for 'append', and it came up with 'affix' and 'adjoin'. -- Greg

On Tue, Oct 09, 2012 at 11:34:24PM +1300, Greg Ewing wrote:
I just consulted a thesaurus about synonyms for 'append', and it came up with 'affix' and 'adjoin'.
Yet another possibility is "combine", which unlike "join", gives less of an implicit guarantee that it's a straightforward concatenation. Other synonyms for "combine" include "couple", "fuse", and "hitch". -- Andrew McNabb http://www.mcnabbs.org/andrew/ PGP Fingerprint: 8A17 B57C 6879 1863 DE55 8012 AB4D 6098 8826 6868

On Tue, Oct 9, 2012 at 3:13 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
It does suggest a whole new class of verbs though, like "make" or "build".
They are rather vague, though.
Agreed, but what os.path.join actually *does* is rather vague, since it is really "joins the path segments, starting with the last absolute path segment". I'm mostly playing Devil's Advocate here, but I thought it was a very good point that requesting a Path or PurePath directly is *always* going to be an option. And really, the only time you *need* a PurePath is when you want to construct a non-native path - for native paths you'll always be able to create it, some methods just won't work if it doesn't actually exist on the filesystem. Using Victor's more complicated example, compare: open(os.path.join(home, ".config", name + ".conf")) open(str(Path(home, ".config", name + ".conf"))) open(home.join(".config", name + ".conf"))) open(str(home / ".config" / name + ".conf")) Path(home, ".config", name + ".conf").open() home.join(".config", name + ".conf").open() (home / ".config" / name + ".conf").open() One note regarding the extra "str()" calls relative to something like path.py: we get to define the language, so we can get the benefits of implicit conversion without the many downsides by *defining a new conversion method*, such as __fspath__. That may provide an attractive alternative to offering methods that shadow builtin functions: open(Path(home, ".config", name + ".conf")) open(home.join(".config", name + ".conf")) open(home / ".config" / name + ".conf") "As easy to use as path.py, without the design compromises imposed by inheriting from str" is a worthwhile goal. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Tue, Oct 9, 2012 at 6:04 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
The other thing I thought might be useful is to try to tie it into the new "opener" parameter for open somehow, On the other hand, that's getting further into full-blown filesystem emulation territory (http://packages.python.org/fs/). That may not be a bad thing, though - a proper filesystem abstraction might finally let us deal with encoding and case-sensitivity issues in a sane way, since they're filesystem dependent rather than platform dependent (e.g. opening FAT/FAT32 devices on *nix systems). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

`p[q]` 0 `p + q` +0.5 `p / q` +1 `p.join(q)` -1 `p.pathjoin(q)` -1 `p.pathjoin(q)` -1 `p.add(q)` +0.5 Joining/adding/appending paths is one of the most common ops. Please let's make it short, easy and obvious.

On Oct 8, 2012 5:35 PM, "Eric Snow" <ericsnowcurrently@gmail.com> wrote:
On Mon, Oct 8, 2012 at 12:47 PM, Antoine Pitrou <solipsis@pitrou.net>
wrote:
Changing my vote: p[q] -1 p + q -1 p / q +0 p.pathjoin() +1 A method is essential, regardless of the color the bikeshed ends up. As far as operators go, / is the only option here that doesn't conflict with string/collection APIs. The alternative has an adverse impact on subclassing and on future design choices on the path API. This goes for the method name too. -eric

On Mon, Oct 8, 2012 at 2:47 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
-1 This syntax makes no sense, it doesn't match the syntax in an obvious way
- `p + q` joins path q to path p
-1 Too easy to confuse with string concat
- `p / q` joins path q to path p
+1 Reads like a path, makes logical sense
- `p.join(q)` joins path q to path p
+1 Allows passing as a callable (tho we could just use operator module). I think we should have both / and .join() (you can include a rationale if you want, but don't forget to vote :-))
-- Read my blog! I depend on your acceptance of my opinion! I am interesting! http://techblog.ironfroggy.com/ Follow me if you're into that sort of thing: http://www.twitter.com/ironfroggy

Speaking as a relatively inexperienced user (whose opinions should justly be given little weight as such),
- `p[q]` joins path q to path p -1: Doesn't make sense at first glance
- `p.join(q)` joins path q to path p +1: Except it needs a different name, for the same reasons as +
What about p.unite(q)? The one word definition of 'join' is 'unite' and it's definitely not used by str, and I don't know of anywhere else that it is used. And it's only one extra character instead of the 4 of 'pathjoin' or 'joinpath'.

Summary: - favourite & second-favourite choices: `p.join(q)` or `p.add(q)`. - most disliked: `p[q]` `p[q]` -1: looks like indexing or key-lookup, isn't either. `p + q` +0: potential confusion between path component joining and filename suffix appending. `p / q` +0: looks funny if either arg is a literal string. `p.join(q)`: +1: self-explanatory, suggests os.path.join, I am not convinced that Nick's fears about confusing Path.join and str.join will be a problem in practice. `p.pathjoin(q)` and `p.joinpath(q)` -1: dislike repeating the type name in the method name; also, too easy to forget which one is used. `p.add(q)` +0.5: nice and short, but not quite self-explanatory. `p.append(q)` -0: suggests an in-place modification. -- Steven

And I knew there was another suggestion tickling around in my subconscious... I have a new favourite: `p & q` +1: unlikely to be confused with int or set &; strings do not currently use it; suggests concatenation; short, can work with two paths or path and string if needed. p + ".ext" to add a suffix to the file name; an error if p is a directory. "spam" + p should probably an error. I can't think of a good use case for prepending a string to a path. p & q to concatenate (join) path q to path p. p.add(q [, r, s, ...]) to concatentation multiple path components at once, more efficient than p & q & r & ..., and to make the function more discoverable and searchable. -- Steven

Antoine Pitrou <solipsis@pitrou.net> writes:
I hope you count U+2212 MINUS SIGN and not only U+002D HYPHEN-MINUS :-)
- `p[q]` joins path q to path p
−1. Ugly and counter-intuitive. Bracket syntax is for accessing items of a collection.
- `p + q` joins path q to path p
+1. Works as I'd expect it to work, and is easily discovered.
- `p / q` joins path q to path p
−1. ‘/’ as a Python operator strongly connotes “division”, and this isn't it.
- `p.join(q)` joins path q to path p
+1. Explicit and clear.
(you can include a rationale if you want, but don't forget to vote :-))
Thanks for the poll. -- \ “The whole area of [treating source code as intellectual | `\ property] is almost assuring a customer that you are not going | _o__) to do any innovation in the future.” —Gary Barnett | Ben Finney

Antoine Pitrou wrote:
- `p[q]` joins path q to path p
-1, confuses operation on a path with operation on the object named by the path.
- `p + q` joins path q to path p
-0.9, interacts with string concatenation in undesirable ways
- `p / q` joins path q to path p
+1
- `p.join(q)` joins path q to path p
-0.9, 'append' would be clearer IMO -- Greg

Antoine Pitrou <solipsis@pitrou.net> wrote:
- `p[q]` joins path q to path p
-1 I think, this is listed as example in PEP 428. I had to look it up to understand. Not intuitive (to me atleast) as join.
- `p + q` joins path q to path p
+0. I would be +1. But in the PEP you have listed that we need a way separate path behaviors from confusing with builtins Though it provides a lot of convenience, it can be confused with str behaviors or other object behaviors.
- `p / q` joins path q to path p
-1.
- `p.join(q)` joins path q to path p
+0
`p.pathjoin(q)`
+1 It is very explicit and hard to get it wrong.

Eric Snow <ericsnowcurrently@...> writes:
Or, precisely, since it's provisional, we needn't *wait* before we provide an operator. Any stdlib module API can be augmented; what provisional modules allow in addition to that is to modify or remove existing APIs. So we can, say, enable Path.__truediv__ and wait for people to complain about it. By the way, it's not new to have dual operator / method APIs. For example set.union and set.__or__; list.extend and list.__iadd__; etc. Regards Antoine.

On Tue, Oct 9, 2012 at 3:32 PM, Terry Reedy <tjreedy@udel.edu> wrote:
I like the [] syntax. ZODB works this way when the subpath name is not a valid Python identifier. a.b['c-d'] would be like a/b/c-d if ZODB was a filesystem. I like the + syntax. No one has suggested overloading the > operator? p1 > p2 > p3 The < operator would keep its normal use for sorting. ;-)

I cannot decide with such trivial examples. More realistic examples: --- def read_config(name): home = Path(os.path.expanduser("~")) # pathlib doesn't support expanduser?? with open(home / ".config" / name + ".conf") as f: return fp.read() --- The join() method has an advantage: it avoids temporary objects (config / ".config" is my example). --- def read_config(name): home = Path(os.path.expanduser("~")) # pathlib doesn't support expanduser?? with open(home.join(".config", name + ".conf")) as f: return fp.read() --- It should work even if name is a Path object, so Path + str should concatenate a suffix without adding directory separator. My vote:
- `p[q]` joins path q to path p
home[".config"][name] # + ".conf" ??? -1
- `p + q` joins path q to path p
home + ".config" + name # + ".conf" ??? -1 -> Path + str must be reserved to add a suffix
- `p / q` joins path q to path p
home / ".config" / name + ".conf" +1: it's natural, but maybe "suboptimal" in performance
- `p.join(q)` joins path q to path p
home.join(".config", name + ".conf") +0: more efficient, but it may be confusing with str.join() which is very different. a.join(b, c) : a is the separator or the root directory, depending on the type of a (str or Path). We should avoid confusion between Path and str methods and operator (a+b and a.join(b)). Victor

On 08/10/2012 19:47, Antoine Pitrou wrote:
-1 yuck
- `p + q` joins path q to path p
+1 Pythonic
- `p / q` joins path q to path p
-0 veering to +0 it just seems wrong but I can't strongly put my finger on why.
- `p.join(q)` joins path q to path p
-1 likely to confuse idiots like me as it's too similar to string.join. For the last one would there be a real need for a path.join method, or has this already been discussed and I've forgotten about it?
-- Cheers. Mark Lawrence.

- `p[q]` joins path q to path p
For some obscure reason I really like this one. I can understand the arguments against it though. So I'll probably be the only one to be +0 on this proposal. - `p + q` joins path q to path p
-0 I agree with who says this operator should be used as suffix appending, and not for path components. - `p / q` joins path q to path p
+0 I'm not against the div operator I'd prefer to use another one. I'm a *nix person but I find this proposal too *nix-centric. About the operator: I really like *Steven D'Aprano*'s proposal: I find *&*just perfect. I'm way more than +1 on it. - `p.join(q)` joins path q to path p
+1 I feel the need for a method, in parallel with some operator. About the name: if join is rejected I am: +1 on add() +1 on adjoin() +0 on append() -1 on pathjoin() / joinpath() -- too long, too similar, way too ugly.

On Mon, Oct 8, 2012 at 3:47 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
-1 ... semantics too different from usual meaning of [ ] - `p + q` joins path q to path p
-1 ... too problematic with strings... - `p / q` joins path q to path p
+0 ... my brain is hard-wired to see / as division or equivalent (e.g. quotient groups, etc.) - `p.join(q)` joins path q to path p
+1 .... only remaining choice. Besides, I think an explicit method makes more sense. If paths were only directories, I would have really like p.cd(q) [with support for multiple arguments of course] as everyone (I think) would naturally recognize this... However, since we can have file as well, I was trying to think of something to mean change path p so that it now points to the joining of path p and q ... and suggest p.goto(q) ;-) ;-) André

On Mon, Oct 8, 2012 at 8:47 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
- `p[q]` joins path q to path p
-1. Much less intuitive than the other two proposed operators.
- `p + q` joins path q to path p
-1. Silently does the wrong thing if p and q are both strings.
- `p / q` joins path q to path p
+1. Reads naturally, and fails loudly if p is a string.
- `p.join(q)` joins path q to path p
-1. Produces a nonsensical result if p and q are both strings. I'd be +1 on `p.joinpath(q)`, since it doesn't have this problem. Cheers, Nadeem

On 08/10/2012 19:47, Antoine Pitrou wrote:
How about using the caret symbol to join so `p ^ q`? Rationale, it looks like a miniature combination of the backslash and forwardslash so should keep Windows and *nix camps happy, plus it's only used in Python (I think?) for bitwise operations so shouldn't confuse anybody. Parachute is ready for the antiaircraft fire :) -- Cheers. Mark Lawrence.
participants (44)
-
Andre Roberge
-
Andrew McNabb
-
Antoine Pitrou
-
Arnaud Delobelle
-
Ben Finney
-
Blake Hyde
-
Calvin Spealman
-
Carlo Pires
-
Chris Jerdonek
-
Christian Heimes
-
Daniel Holth
-
Eric Snow
-
Ethan Furman
-
Georg Brandl
-
Greg Ewing
-
Guido van Rossum
-
Henshaw, Andy
-
INADA Naoki
-
Joachim König
-
Joshua Landau
-
Mark Lawrence
-
Mark Shannon
-
Masklinn
-
Massimo DiPierro
-
Mathias Panzenböck
-
Matt Chaput
-
Michele Lacchia
-
MRAB
-
Nadeem Vawda
-
Nick Coghlan
-
Oleg Broytman
-
Paul Moore
-
Richard Oudkerk
-
rohit sharma
-
Ryan D Hiebert
-
Senthil Kumaran
-
Serhiy Storchaka
-
Stephen J. Turnbull
-
Steven D'Aprano
-
T.B.
-
Terry Reedy
-
Victor Stinner
-
Yuval Greenfield
-
Zachary Ware