“Python split string by character” is asked about two completely different jobs, which is why the answers people find so often miss. One means breaking a string into its individual characters. The other means cutting it at a particular character:
list("a,b,c") # ['a', ',', 'b', ',', 'c'] every character
"a,b,c".split(",") # ['a', 'b', 'c'] cut at the commas
list() handles the first. str.split() handles the second. Both are covered below, along with the ValueError that comes from mixing them up.
The output shown under each block is from a run on Python 3.12.5.

The two meanings side by side
Run them together and the difference is obvious:
text = "a,b,c"
print("two different jobs, two different tools")
print()
print("1. into individual characters:")
print(" list(text) ->", list(text))
print()
print("2. on a delimiter character:")
print(" text.split(',') ->", text.split(","))
print()
print("same input, completely different answers")
Output:
two different jobs, two different tools
1. into individual characters:
list(text) -> ['a', ',', 'b', ',', 'c']
2. on a delimiter character:
text.split(',') -> ['a', 'b', 'c']
same input, completely different answers

list() keeps the commas as items; split(",") consumes them.Notice that split(",") throws the commas away. They were the cut marks, so they do not appear in the result.
Split a string into characters with list()
list() is the direct answer for turning a string into a list of characters:
word = "python"
characters = list(word)
print("word ->", word)
print("list(word) ->", characters)
print("how many ->", len(characters))
print()
print("each piece is itself a one character string:")
print("characters[0] ->", repr(characters[0]), type(characters[0]))
print()
print("and it goes back together with join:")
print('"".join(characters) ->', "".join(characters))
Output:
word -> python
list(word) -> ['p', 'y', 't', 'h', 'o', 'n']
how many -> 6
each piece is itself a one character string:
characters[0] -> 'p' <class 'str'>
and it goes back together with join:
"".join(characters) -> python
Python has no separate character type, so each item is a string of length one. That is why characters[0] reports str rather than something like char.
Joining them back is "".join(characters). If you need the pieces as a different type, converting a string to a list covers the variations.
Four ways to get a list of characters
There is more than one route, and they do not all cost the same:
import timeit
word = "hello"
print("list(word) ->", list(word))
print("[c for c in word] ->", [c for c in word])
print("[*word] ->", [*word])
print("tuple(word) ->", tuple(word))
print("sorted(set(word)) ->", sorted(set(word)), " duplicates dropped")
print()
setup = "s = 'abcdefghij' * 500"
for label, snippet in [("list(s) ", "list(s)"),
("[*s] ", "[*s]"),
("[c for c in s] ", "[c for c in s]")]:
secs = timeit.timeit(snippet, setup, number=20000)
print(f"{label} {secs:.2f}s for 20,000 passes over 5,000 characters")
print()
print("list() and [*s] run in C; the comprehension loops in Python.")
Output:
list(word) -> ['h', 'e', 'l', 'l', 'o']
[c for c in word] -> ['h', 'e', 'l', 'l', 'o']
[*word] -> ['h', 'e', 'l', 'l', 'o']
tuple(word) -> ('h', 'e', 'l', 'l', 'o')
sorted(set(word)) -> ['e', 'h', 'l', 'o'] duplicates dropped
list(s) 0.21s for 20,000 passes over 5,000 characters
[*s] 0.23s for 20,000 passes over 5,000 characters
[c for c in s] 0.90s for 20,000 passes over 5,000 characters
list() and [*s] run in C; the comprehension loops in Python.

list() and the unpacking form [*word] are several times quicker than the comprehension, because both run inside C rather than looping in Python. On short strings the gap is irrelevant, but it shows up on long text.
| Expression | Gives | When to reach for it |
|---|---|---|
list(s) | list of characters | The default. Clearest and fastest. |
[*s] | list of characters | Handy inside a larger expression. |
[c for c in s] | list of characters | Only when you are also filtering or transforming. |
tuple(s) | tuple of characters | When the result must not change. |
set(s) | unique characters | Duplicate letters collapse into one. |
The comprehension earns its place as soon as there is a condition attached, which is the next section.
Split a word into letters
Splitting a word into letters is the same call, with one caveat about what counts as a letter:
word = "Hello World 42!"
print("list(word) ->", list(word))
print()
print("it keeps the space, the digits and the punctuation too.")
print("for letters only, filter with isalpha:")
letters = [c for c in word if c.isalpha()]
print("letters ->", letters)
print()
print("and to keep the letters but drop the case difference:")
print("lowercase ->", [c.lower() for c in word if c.isalpha()])
Output:
list(word) -> ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', ' ', '4', '2', '!']
it keeps the space, the digits and the punctuation too.
for letters only, filter with isalpha:
letters -> ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
and to keep the letters but drop the case difference:
lowercase -> ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
list() is indiscriminate. Spaces, digits and punctuation all become items, so filtering with isalpha() is usually what people actually want.
For breaking a sentence into words instead of letters, use split a sentence into a list of words, and for digits specifically there is split a number into digits.
Split a string on a character
This is the delimiter reading of the question, and str.split() is the tool:
line = "name,age,city"
print('line.split(",") ->', line.split(","))
print('maxsplit=1 ->', line.split(",", 1))
print('rsplit from the end ->', "a,b,c".rsplit(",", 1))
print()
print("two results that surprise people:")
print('"abc".split(",") ->', "abc".split(","), " no delimiter, so one piece")
print('"".split(",") ->', "".split(","), " an empty string, not an empty list")
print()
print("partition splits once and keeps the separator:")
print('"key=value=x".partition("=") ->', "key=value=x".partition("="))
print()
print("with no argument at all, split() uses runs of whitespace:")
print('" a b \t c \n".split() ->', " a b \t c \n".split())
Output:
line.split(",") -> ['name', 'age', 'city']
maxsplit=1 -> ['name', 'age,city']
rsplit from the end -> ['a,b', 'c']
two results that surprise people:
"abc".split(",") -> ['abc'] no delimiter, so one piece
"".split(",") -> [''] an empty string, not an empty list
partition splits once and keeps the separator:
"key=value=x".partition("=") -> ('key', '=', 'value=x')
with no argument at all, split() uses runs of whitespace:
" a b c
".split() -> ['a', 'b', 'c']

split() with a delimiter, plus maxsplit, rsplit() and partition().Two results catch people out. Splitting a string that does not contain the delimiter gives a one-item list rather than an error, and splitting an empty string gives [''] rather than an empty list.
That second one matters when you are checking results with if not pieces, because a list holding one empty string is still truthy. Comma separated input has its own guide in split a string by comma, and splitting on spaces covers the whitespace case.
Why split(“”) raises ValueError
The obvious guess for splitting into characters does not work:
text = "hello"
try:
text.split("")
except ValueError as err:
print('text.split("") -> ValueError:', err)
print()
print("split() needs something to split ON, and an empty string is nothing.")
print("when you want every character, that is list():")
print("list(text) ->", list(text))
Output:
text.split("") -> ValueError: empty separator
split() needs something to split ON, and an empty string is nothing.
when you want every character, that is list():
list(text) -> ['h', 'e', 'l', 'l', 'o']
