Search code examples
pythontuples

Python join method on a tuple


I'd like some clarification on the way the str.join() method works when passed a tuple. Take for example this code snippet:

a = ("A", "B")
print(''.join(a)) 

This gives expected output: AB

So from this I ascertained that passing join an iterable (Such as a tuple) would allow it to unpack and "join" the contents together.

With this knowledge in mind I wrote the following generator expression:

import random
import string

generated_string = ''.join((random.choice(string.ascii_letters), random.choice(string.digits)) for _ in range(4))

This results in the following error:

Traceback (most recent call last):
  File "main.py", line 5, in <module>
    generated_string = ''.join((random.choice(string.ascii_letters), random.choice(string.digits)) for _ in range(4))
TypeError: sequence item 0: expected str instance, tuple found

I'm confused as to why this isn't working. I can manually iterate through the contents of the generator and "join" the tuples manually, for example:

generated_string = ((random.choice(string.ascii_letters), random.choice(string.digits)) for _ in range(4))

for val in generated_string:
    print(''.join(val))

Which gives the following output:

U7
W1
y7
G8

What am I missing here? It feels like it's obvious but I feel like I've covered all bases.

Thank you for your time.


Solution

  • As you said, you pass an iterable to join, and the items must be strings. The iterable can be a tuple, generator, list, or whatever. However:

    ''.join((random.choice(string.ascii_letters), random.choice(string.digits)) for _ in range(4))
    

    That's not a tuple of strings, it's a sequence of tuples of strings.

    print(list((random.choice(string.ascii_letters), random.choice(string.digits)) for _ in range(4)))
    # [('w', '7'), ('X', '6'), ('W', '7'), ('P', '1')]
    

    Edit: To fix it you can either nest two ''.join like so:

    ''.join(''.join((random.choice(string.ascii_letters), random.choice(string.digits))) for _ in range(4))
    

    or, since you know you're always joining exactly two items, use an f-string:

    ''.join(f'{random.choice(string.ascii_letters)}{random.choice(string.digits)}' for _ in range(4))