Skip to content
VLSI Mentor

Python Part I · Module 4 · Strings

join(): Building One String From Many

Chapter 4.8 took a line apart. This chapter puts one together, and it is the exact inverse: split turns one string into pieces, join turns pieces into one string. It is also the most notoriously backwards-looking method in the language, because it is written on the separator rather than on the pieces — you write a space and ask the space to do the joining. That reads strangely until you see the reason for it, which takes one paragraph and then never bothers you again. Chapter 4.3 promised this method as the right tool for assembling text from many pieces; this is where that promise is kept, and where you find out what it does when a number sneaks into the pieces.

Foundation12 min readPythonPart IFundamentalsStringsAssembly

Part I · Module 4 · Chapter 4.9 · join(): Building One String From Many

1. The Problem, and the Call

You have several pieces of text and you want one string with something between them. A command line, for instance — four separate arguments that need to become one command:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
parts = ["vcs", "-full64", "-f", "files.f"]
print(" ".join(parts))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
vcs -full64 -f files.f

One call, one string, spaces in all three places between the four pieces and nowhere else.

The square brackets on the first line are a list written out directly rather than produced by split() — the same kind of value Chapter 4.8 introduced, holding four strings in order. Module 5 teaches how to build them properly; here it is just a way to have four pieces on hand.

Compare the concatenation you would otherwise write, from Chapter 4.3:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("vcs" + " " + "-full64" + " " + "-f" + " " + "files.f")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
vcs -full64 -f files.f

Same answer. Seven operands, three of them separators typed out by hand, and one place to accidentally leave a space out. That is the comparison this chapter is about.

2. Why the Separator Owns the Method

Now the thing nobody explains. You wrote:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
" ".join(parts)

and it looks inside out. The natural instinct is parts.join(" ") — take the pieces and join them with a space. Python refuses that outright:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print(["a", "b"].join(","))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
AttributeError: 'list' object has no attribute 'join'

There genuinely is no join on the pieces. The reason is worth understanding rather than memorising.

join is a string operation because its result is a string, and the separator is the only part of that result it contributes. The pieces supply their own characters; the separator is the thing being inserted. So the method belongs to the separator, and the pieces are what you hand it.

Read it in that order and it says exactly what it does:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(",".join(["a", "b", "c"]))
print(" | ".join(["a", "b", "c"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a,b,c
a | b | c

separator.join(pieces) means: put this separator between these pieces.

The separator can be anything, including nothing at all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("".join(["a", "b", "c"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
abc

An empty separator puts nothing between the pieces, which glues them together. That is occasionally exactly what you want.

3. Between, Not After

Here is the fact that follows from the previous section and answers most "off by one" surprises: the separator goes between the pieces, so joining n pieces uses n − 1 separators.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
p3 = ["a", "b", "c"]
print(",".join(p3))
print(len(",".join(p3)))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a,b,c
5

Three pieces, two commas, five characters. There is no trailing comma, because there is no fourth piece for it to sit in front of.

The edges confirm the rule rather than contradicting it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(",".join(["only"]))
print(repr(",".join([])))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
only
''

One piece gets no separator at all — there is nothing for it to be between. Zero pieces give an empty string — nothing to join, so nothing comes out. Neither is an error, and both are what "between the pieces" has to mean at the boundary.

4. split and join Are Inverses

The two chapters fit together, and it is worth seeing as one movement rather than two techniques:

Five stages. One: one string, axi_smoke 4210 PASS. Two: split, written line.split(), which cuts at each run of whitespace. Three: three separate pieces, axi_smoke, 4210 and PASS, with no separators left in them. Four: join, written space dot join of the pieces, which puts one space back between each pair. Five: one string again, axi_smoke 4210 PASS.One line, taken apart and put back01ONE STRINGaxi_smoke 4210 PASS02SPLITline.split() — cut at each run of whitespace03THREE PIECESaxi_smoke · 4210 · PASS04JOIN" ".join(pieces) — one space between each pair05ONE STRING AGAINaxi_smoke 4210 PASS
Figure 1 — split and join are inverses. split removes the separators and hands back the pieces; join puts a separator back between them. The round trip returns the original line whenever the spacing was already regular — and when it was not, it returns a tidied version of it, which is what makes the pair useful together.

In code, the round trip returns the original:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line = "axi_smoke 4210 PASS 182.4"
print(" ".join(line.split()) == line)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True

And here is where the pair earns its keep. Run the same round trip on the uneven spacing from Chapter 4.8:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
messy = "axi_smoke    4210   PASS"
print(repr(" ".join(messy.split())))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'axi_smoke 4210 PASS'

The runs of spaces became single spaces. split() threw away every run of whitespace, and join put back exactly one space between each pair of pieces — so the line came back normalised.

That one line is the standard way to tidy irregular spacing in Python, and you now have both halves of why it works.

5. What join Refuses

join will only assemble strings. A number among the pieces stops it:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print(",".join(["a", 42]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: sequence item 1: expected str instance, int found

The message is precise and worth reading properly: sequence item 1 is telling you which piece was wrong — item 1, counting from zero, so the second one. expected str instance, int found is telling you what was wrong with it.

This is the same refusal as "seed_" + 42 in Chapter 4.3, for the same reason: Python will not silently decide how a number should look as text. The fix is the same too — convert first with str() from Chapter 2.11:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(",".join(["a", str(42)]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a,42

When the pieces come from split() this never arises, because splitting text produces text. It arises when you assemble pieces yourself and one of them is a measured value.

6. Three Things join Is Actually For

A command string, which is the canonical example from Section 1 — several arguments becoming one line.

A separated data row. A comma between the fields and no comma on the end:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(",".join(["axi_smoke", "4210", "PASS"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
axi_smoke,4210,PASS

Several lines of a report, using the newline from Chapter 4.2 as the separator:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("\n".join(["line one", "line two"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line one
line two

That last one is the answer to the cost problem Chapter 4.3 raised. Rather than building a report by repeatedly adding to a string — which, because strings are immutable, rebuilds the whole thing every time — you collect the lines and join them once. Module 10 will give you the means to collect many lines; the assembly step is this one, and it does not get slower as the report grows.

7. One Surprise Worth Knowing

join works on anything that supplies pieces in order — and a string supplies its characters in order:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("-".join("PASS"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
P-A-S-S

The four characters of "PASS" were treated as four pieces, with hyphens between them. This is rarely what you want, and it is included because it is a real source of confusion: if you accidentally pass a single string where you meant several, join will not complain — it will quietly space out the characters. A result full of separators between individual letters is the symptom.

8. Predict, Run, Observe

Predict all five before reading on.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print("-".join(["a", "b", "c"]))
print(len("-".join(["a", "b", "c"])))
print("/".join(["runs", "axi_smoke", "sim.log"]))
print(repr(",".join(["solo"])))
print(" ".join("ab"))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a-b-c
5
runs/axi_smoke/sim.log
'solo'
a b

The second is 5 — three pieces plus two separators, never three separators. The fourth has no comma anywhere, because one piece needs no separator. The fifth is the Section 7 surprise: "ab" was read as two pieces, a and b, so a space went between them.

If you predicted ab for the last one you expected join to leave a single string alone. It does not; it takes the string's characters as the pieces.

9. Exercises

Exercise 1 — build three strings. Using join, produce each of these from the pieces ["runs", "axi_smoke", "sim.log"]: (a) runs/axi_smoke/sim.log (b) runs axi_smoke sim.log (c) runsaxi_smokesim.log

Exercise 2 — count the separators. Without running anything, say how many separator characters appear in each result, then verify with len(): (a) ",".join(["a","b","c","d"]) (b) ",".join(["a"]) (c) ",".join([]) (d) "--".join(["a","b","c"])

Exercise 3 — read the error. A colleague's script fails with TypeError: sequence item 2: expected str instance, float found. Say which piece is at fault, counting from where, and give the fix.

Exercise 4 — the inversion. Explain in two sentences why join is written on the separator rather than on the pieces. Then say what error you get for writing it the other way, and why that error is an AttributeError rather than a TypeError.

Exercise 5 — normalise a line. Write one expression that turns " reg_rw FAIL 91.2 " into exactly "reg_rw FAIL 91.2". Then say why you did not need strip().

Exercise 6 — the dangling comma. A colleague builds a CSV row by adding field + "," for each field and then slicing the last character off. Say in two sentences what join does differently, and what breaks in their version when a row has exactly one field.

Exercise 7 — the accidental string. Predict the result of ", ".join("PASS"), run it, and explain in one sentence what join treated as the pieces.

10. Summary

  • separator.join(pieces) builds one string by putting the separator between the pieces.
  • The separator owns the method because the separator is the only part of the result that join contributes. pieces.join(sep) is an AttributeError.
  • Read it as "comma, join these", not "join these with a comma".
  • Separators go between, so n pieces use n − 1 separators. One piece gets none; zero pieces give "". There is never a trailing separator.
  • join is the inverse of split. " ".join(line.split()) returns the line, and on irregular spacing it returns a normalised version of it — the standard way to tidy whitespace.
  • join accepts only strings. A number gives TypeError: sequence item N: expected str instance, int found, and the item number names the offending piece counting from zero. Convert with str().
  • Use join rather than repeated + for many pieces — it assembles in one step instead of rebuilding the string at each step, which is the cost Chapter 4.3 described.
  • "\n".join(lines) builds a multi-line block, and is how reports get assembled.
  • Handing join a single string spaces out its characters rather than raising. A result with separators between individual letters is that mistake.

Splitting and joining cover taking text apart and putting it together. The next chapter asks precise questions about what is inside it, and changes parts of it in place.

Where this fits

Part of the Python curriculum.