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

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Pierre Fortin

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

    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(*a rgs)

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

    With: return os.path.join(ar gs)

    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(arg s,"/") # a/b
    print os.path.join(ar gs) # ('a', 'b')

    # with '*'
    print *args # syntax error at *
    print (*args) # syntax error at *
    print "/".join(*arg s) # TypeError
    print string.join(*ar gs,"/") # syntax error at 2nd (")
    print os.path.join(*a rgs) # 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


  • Heiko Wundram

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

    Before applying args or *args to a real world sample, lets see what each of
    them does:
    [color=blue][color=green][color=darkred]
    >>> def testfunc(*args) :[/color][/color][/color]
    .... 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.
    [color=blue][color=green][color=darkred]
    >>> testfunc([1,2,3])[/color][/color][/color]
    ([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.
    [color=blue][color=green][color=darkred]
    >>> testfunc(*[1,2,3])[/color][/color][/color]
    (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.
    [color=blue][color=green][color=darkred]
    >>> def testfunc2(a,b):[/color][/color][/color]
    .... print a
    .... print b
    ....

    Lets define another function which takes two arguments (a, b) and prints them
    out in order.
    [color=blue][color=green][color=darkred]
    >>> testfunc2([1,2])[/color][/color][/color]
    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.
    [color=blue][color=green][color=darkred]
    >>> testfunc2(*[1,2])[/color][/color][/color]
    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.

    Comment

    • Pierre Fortin

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

      On Sat, 18 Sep 2004 20:08:35 +0200 Heiko wrote:
      [color=blue]
      > 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?).[/color]
      ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^ =======

      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...

      Comment

      Working...