String concatenation in Python and commas

24,705

Solution 1

The comma-separated list to the left of Lyrics += is defining a "tuple".

You'll want to change the "," to "+", since string concatenation does not take a comma-separated list of values.

Also, consider building up a list of strings to concatenate and then using the "join" method.

Solution 2

Don't use string concatenation for this. Put the strings in a list and join the list at the end. I'd also suggest using str.format.

verse = "{0} bottles of beer on the wall, {0} bottles of beer.\n" \
        "Take one down and pass it around, {1} bottles of beer on the wall.\n"

def bottles_of_beer_lyrics(bottles=99):
    lyrics = []
    for x in range(bottles, 0, -1):
        lyrics.append(verse.format(x, x-1))
    return '\n'.join(lyrics)

You could also get the result more directly by using a generator expression:

def bottles_of_beer_lyrics(bottles=99):
    return '\n'.join(verse.format(x, x-1) for x in range(bottles, 0, -1))

As a final point you should note that "1 bottles" is not grammatically correct. You may want to create a function that can give you the correct form of "bottle" depending on the number. If internationalization is an issue (I know it's probably not) then you should also be aware that some languages have more forms than just "singular" and "plural".

Solution 3

The comma operator creates a tuple. The line "Lyrics += ..." is creating a tuple on the right side.

To concatenate strings in Python use the "+" operator.

Lyrics += str(BottlesOfBeer) + " bottles of beer..." + ...

However, that's still not the preferred way for this kind of thing, but is fine for learning string concatenation.

Solution 4

It looks like you've got the original problem cleared up and are now asking about using join() -- yes, that's generally a better than using += for string concatenation (although not always faster).

But even better is to not even do it or do so as little as possible. Python had something called format strings which are something like those used by C's printf() function -- see Format String Syntax in the online documentation. Using that along with the format() string method (and some other Pythonisms) can really simplify things:

def BottlesOfBeerLyrics(NumOfBottlesOfBeer=99):
    PluralSuffix = lambda n: "s" if n != 1 else ""
    Stanza = "\n".join([
        "{0} bottle{1} of beer on the wall, {0} bottle{1} of beer.",
        "Take one down and pass it around, {2} bottle{3} of beer on the wall.",
        "\n"])
    Lyrics = ""
    for Bottles in range(NumOfBottlesOfBeer, 0, -1):
        Lyrics += Stanza.format(Bottles, PluralSuffix(Bottles),
                                Bottles-1, PluralSuffix(Bottles-1))
    return Lyrics

print BottlesOfBeerLyrics(3)
# 3 bottles of beer on the wall, 3 bottles of beer.
# Take one down and pass it around, 2 bottles of beer on the wall.
#
# 2 bottles of beer on the wall, 2 bottles of beer.
# Take one down and pass it around, 1 bottle of beer on the wall.
#
# 1 bottle of beer on the wall, 1 bottle of beer.
# Take one down and pass it around, 0 bottles of beer on the wall.
Share:
24,705
jkeys
Author by

jkeys

Updated on July 09, 2022

Comments

  • jkeys
    jkeys almost 2 years

    I'm having a hard time figuring out how the native data types interact in Python. Here, I am trying to concatenate different parts of the lyrics into one long string which will be returned as output.

    The error I receive upon trying run the script is:

    TypeError: cannot concatenate 'str' and 'tuple' objects.

    I put everything that wasn't a string in the function str(), but apparently something is still a "tuple" (a data type I've never used before).

    How can I get whatever tuple is in there to a string so this will all concatenate smoothly?

    (P.S.: I used the variable "Copy", because I wasn't sure if, when I decremented the other variable, it would mess with the for loop construct. Would it?)

    #99 bottles of beer on the wall lyrics
    
    def BottlesOfBeerLyrics(NumOfBottlesOfBeer = 99):
            BottlesOfBeer = NumOfBottlesOfBeer
            Copy = BottlesOfBeer
            Lyrics = ''
    
            for i in range(Copy):
                    Lyrics += BottlesOfBeer, " bottles of beer on the wall, ", str(BottlesOfBeer), " bottles of beer. \n", \
                    "Take one down and pass it around, ", str(BottlesOfBeer - 1), " bottles of beer on the wall. \n"
    
                    if (BottlesOfBeer > 1):
                            Lyrics += "\n"
    
                    BottlesOfBeer -= 1
    
            return Lyrics
    
    print BottlesOfBeerLyrics(99)
    

    Some people suggested building a list and the joining it. I edited it a little bit, but is this the preferred method?

    #99 bottles of beer on the wall lyrics - list method
    
    def BottlesOfBeerLyrics(NumOfBottlesOfBeer = 99):
            BottlesOfBeer = NumOfBottlesOfBeer
            Copy = BottlesOfBeer
            Lyrics = []
    
            for i in range(Copy):
                    Lyrics += str(BottlesOfBeer) + " bottles of beer on the wall, " + str(BottlesOfBeer) + " bottles of beer. \n" + \
                    "Take one down and pass it around, " + str(BottlesOfBeer - 1) + " bottles of beer on the wall. \n"
    
                    if (BottlesOfBeer > 1):
                            Lyrics += "\n"
    
                    BottlesOfBeer -= 1
    
            return "".join(Lyrics)
    
    print BottlesOfBeerLyrics(99)
    
    • Keith
      Keith over 13 years
      PS. numbers are also immutable objects, and implicitly copied. Thus you don't need the Copy variable.
    • Keith
      Keith over 13 years
      RE: attempt #2, use Lyrics.append(...) now. You can also remove all "\n" in the strings and use "\n".join(Lyrics) at the end.
    • jkeys
      jkeys over 13 years
      But I would still use the + operator and append the whole thing all at once? Also, wouldn't that solution overlook the newline in between the two lines, or does the '\' operator make the append method act as if there were two method calls?
  • jkeys
    jkeys over 13 years
    Perfect! Also, I forgot to put the first BottlesOfBeer into the str() function. Both changes fixed it. Thanks for that! But could you tell me why building it into a list and using "".join(Lyrics) would be a better solution?
  • jkeys
    jkeys over 13 years
    Why is using the join function a superior method? Is string concatenation inherently slower?
  • Josh Bleecher Snyder
    Josh Bleecher Snyder over 13 years
    It is slower in some implementations of Python; in cPython, it happens not be much slower. The reason to do this is that it is now a Pythonic idiom (originally due to performance, I believe).
  • Keith
    Keith over 13 years
    Strings are immutable. Concatenating them creates a new string and copies the other stings into it, for each "+" operator. Putting into a list and joining only creates each string once, and one final string copy at the end. Thus it's faster.
  • Mark Byers
    Mark Byers over 13 years
    @Hooked: It also means you don't have the special case for not adding the separator after the last line. str.join already handles this for you.
  • jkeys
    jkeys over 4 years
    @Keith lol looking back at questions I asked nearly ten years ago. Learned a lot since then, though I'm not sure I'm a much better programmer. It's just my goalposts have shifted significantly... :D