args v. *args passed to: os.path.join()

P

Pierre Fortin

This quest for understanding started very innocently...

A simple error on my part, passing on args as "args" instead of "*args" to
os.path.join() led me to wonder why an error wasn't raised...

def foo(*args):
...
return os.path.join(*args)

foo('a','b') # returns 'a/b'

With: return os.path.join(args)

foo('a','b') # returns ('a', 'b') (unchanged -- no error)

Replacing the 'return's in the function with each print below in turn
produced the results in the comments:

# without '*'
print args # ('a', 'b')
print (args) # ('a', 'b')
print "/".join(args) # a/b
print string.join(args,"/") # a/b
print os.path.join(args) # ('a', 'b')

# with '*'
print *args # syntax error at *
print (*args) # syntax error at *
print "/".join(*args) # TypeError
print string.join(*args,"/") # syntax error at 2nd (")
print os.path.join(*args) # a/b

The results in matrix form (numbers refer to order of print statements):

args *args
1 ('a', 'b') syntax error
2 ('a', 'b') syntax error
3 a/b TypeError
4 a/b syntax error
5 ('a', 'b') a/b

os.path.join() is the only one in this list which doesn't return an error.
Also, os.path.join()'s results are reversed relative to the others by
requiring *args...

My question is: other than discovering this anomaly (and wrong result) at
runtime, is there a simple rule for when to pass on '*args' v. 'args' that
I've somehow missed...?

Thanks,
Pierre
 
H

Heiko Wundram

Before applying args or *args to a real world sample, lets see what each of
them does:
.... print args
....

Define a function called testfunc, for which all positional arguments are
stored to the tuple args. The body of this functions contains a single
statement which prints this tuple to screen.
([1, 2, 3],)

The tuple which is printed to screen is the following: a tuple with a single
item which is a list which contains the elements 1, 2 and 3 in this order.
(1, 2, 3)

The tuple which is printed to screen is the following: a tuple with the items
1, 2 and 3 in this order.

See where we're heading at? Let's see another example.
.... print a
.... print b
....

Lets define another function which takes two arguments (a, b) and prints them
out in order.
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: testfunc2() takes exactly 2 arguments (1 given)

Lets call this function with a single argument (a list, which contains the two
items). The interpreter duly complains that the function takes two arguments,
but only one is given, and raises a TypeError.
1
2

Again, using the star syntax: We pass two arguments (because the list which is
starred contains two arguments), and these end up in a and b, respectively.

So, what does the star-operator do? It splits up an iterable (a list in this
case) and fills the first len(iterable) positional arguments with the values
it got from the list.

This is why you see different behavior when passing a star or not. Let's look
at the documentation for os.path.join:

join(a, *p)
Join two or more pathname components, inserting '/' as needed

This means that you have to pass at least one argument (a), which is the path
base, and may pass more arguments (*p) which are joined with this item,
inserting slashes as needed.

Now, when you call:

os.path.join(["a","b"])

the list ends up in the a parameter, and because the function doesn't have to
do anything (there are no more arguments), the list is returned unchanged
(although this should probably raise a TypeError, anyone?).

But when you call:

os.path.join(*["a","b"])

the first item of the list ends up as parameter a to the function, the second
item of the list ends up as the second parameter to the function, which are
then duly joined by the function.

And one more look why "".join(*["a","b"]) raises an error:

join(...)
S.join(sequence) -> string

Return a string which is the concatenation of the strings in the
sequence. The separator between elements is S.

The documentation for str.join() states that you pass it one parameter, a
sequence.

Now, when you do "".join(*["a","b"]), the number of passed parameters is two,
and because the function only accepts one parameter, a sequence, the function
call raises an Exception.

HTH!

Heiko.
 
P

Pierre Fortin

Now, when you call:

os.path.join(["a","b"])

the list ends up in the a parameter, and because the function doesn't
have to do anything (there are no more arguments), the list is returned
unchanged (although this should probably raise a TypeError, anyone?).
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ =======

That was the whole point; the rest was pretty much my understanding too...
I can't think of any reason why os.path.join() should ever be presented
anything but strings... on the other hand, maybe it could
check for lists/tuples and use those to return the expected "a/b"...

Then again, I just checked a number of functions in os and os.path;
os.path.join() seems to be the only one which fails to return a
TypeError...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top