Joining a list:
>>> \'\'.join([ str(_) for _ in xrange(10) ])
\'0123456789\'
join must take an iterable.
Appa
Your second example uses a generator expression rather than a list comprehension. The difference is that with the list comprehension, a list is completely built and passed to .join(). With the generator expression, items are generated one by one and consumed by .join(). The latter uses less memory and is generally faster.
As it happens, the list constructor will happily consume any iterable, including a generator expression. So:
[str(n) for n in xrange(10)]
is just "syntactic sugar" for:
list(str(n) for n in xrange(10))
In other words, a list comprehension is just a generator expression that is turned into a list.