25
votes

What's the most pythonic way of joining a list so that there are commas between each item, except for the last which uses "and"?

["foo"] --> "foo"
["foo","bar"] --> "foo and bar"
["foo","bar","baz"] --> "foo, bar and baz"
["foo","bar","baz","bah"] --> "foo, bar, baz and bah"
6
who cares about an Oxford comma? - Neil G
This question and answers do not use the Oxford comma. For a question and answers that use the Oxford comma, see this question instead. - Acumenus

6 Answers

28
votes

This expression does it:

print ", ".join(data[:-2] + [" and ".join(data[-2:])])

As seen here:

>>> data
    ['foo', 'bar', 'baaz', 'bah']
>>> while data:
...     print ", ".join(data[:-2] + [" and ".join(data[-2:])])
...     data.pop()
...
foo, bar, baaz and bah
foo, bar and baaz
foo and bar
foo
14
votes

Try this, it takes into consideration the edge cases and uses format(), to show another possible solution:

def my_join(lst):
    if not lst:
        return ""
    elif len(lst) == 1:
        return str(lst[0])
    return "{} and {}".format(", ".join(lst[:-1]), lst[-1])

Works as expected:

 my_join([])
=> ""
 my_join(["x"])
=> "x"
 my_join(["x", "y"])
=> "x and y"
 my_join(["x", "y", "z"])
=> "x, y and z"
5
votes

The fix based on the comment led to this fun way. It assumes no commas occur in the string entries of the list to be joined (which would be problematic anyway, so is a reasonable assumption.)

def special_join(my_list):
    return ", ".join(my_list)[::-1].replace(",", "dna ", 1)[::-1]


In [50]: def special_join(my_list):
        return ", ".join(my_list)[::-1].replace(",", "dna ", 1)[::-1]
   ....:

In [51]: special_join(["foo", "bar", "baz", "bah"])
Out[51]: 'foo, bar, baz and bah'

In [52]: special_join(["foo"])
Out[52]: 'foo'

In [53]: special_join(["foo", "bar"])
Out[53]: 'foo and bar'
1
votes

Already good answers available. This one works in all test cases and is slightly different than some others.

def grammar_join(words):
    return reduce(lambda x, y: x and x + ' and ' + y or y,
                 (', '.join(words[:-1]), words[-1])) if words else ''

tests = ([], ['a'], ['a', 'b'], ['a', 'b', 'c'])
for test in tests:                                 
    print grammar_join(test)

a
a and b
a, b and c
-1
votes

just special-case the last one. something like this:

'%s and %s'%(', '.join(mylist[:-1]),mylist[-1])

there's probably not going to be any more concise method.

this will fail in the zero case too.

-1
votes

In case you need a solution where negative indexing isn't supported (i.e. Django QuerySet)

def oxford_join(string_list):
    if len(string_list) < 1:
        text = ''
    elif len(string_list) == 1:
        text = string_list[0]
    elif len(string_list) == 2:
        text = ' and '.join(string_list)
    else:
        text = ', '.join(string_list)
        text = '{parts[0]}, and {parts[2]}'.format(parts=text.rpartition(', '))  # oxford comma
    return text

oxford_join(['Apples', 'Oranges', 'Mangoes'])