26

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"
Asclepius
  • 57,944
  • 17
  • 167
  • 143
user1277170
  • 3,127
  • 3
  • 19
  • 19
  • 11
    who cares about an Oxford comma? – Neil G Nov 07 '13 at 17:48
  • 2
    This question and answers do not use the [**Oxford comma**](https://en.wikipedia.org/wiki/Serial_comma). For a question and answers that use the Oxford comma, see [this question](https://stackoverflow.com/q/53981845/832230) instead. – Asclepius Dec 31 '18 at 02:33

6 Answers6

31

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
Matt Anderson
  • 19,311
  • 11
  • 41
  • 57
14

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"
Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • 1
    Not so "smart" as other answers, and therefore the most "pythonic" solution. The only point, I'd use `format` in the second branch too, so that the fun always returns a string. – georg Nov 07 '13 at 15:39
  • @thg435 I prefer readability over "smartness" :) For the second branch, a simple `str()` will do – Óscar López Nov 07 '13 at 15:45
  • 1
    +1 for not trying to be clever like the others... It's very frustrating, running across code like the currently most-upvoted answer only to discover later on it has a bug not immediately noticed due to the cleverness – Izkata Nov 07 '13 at 19:08
  • +1 for function with test cases – Alex Jul 22 '14 at 20:14
5

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'
ely
  • 74,674
  • 34
  • 147
  • 228
1

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
doog abides
  • 2,270
  • 16
  • 13
-1

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.

Corley Brigman
  • 11,633
  • 5
  • 33
  • 40
-1

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'])
dhackner
  • 2,942
  • 2
  • 20
  • 23