Olá! Tudo bem? A diferença entre os três é a seguinte:
re.match(padrão, string) - Começa a buscar o padrão a partir do começo e exige que o padrão seja encontrado logo no começo
print(re.match("abc", "123abc"))
# Mostra None, porque o começo 123 não segue o padrão abc
print(re.match("abc", "abc123"))
# Retorna <re.Match object; span=(0, 3), match='abc'>, indicando que o padrão foi encontrado entre as posições 0 e 3
print(re.match("[0-9]", "123abc"))
# Retorna <re.Match object; span=(0, 1), match='1'>, somente o primeiro número
print(re.match("[0-9]", "abc123abc"))
# Retorna None, porque o padrão não pôde ser encontrado logo no começo
re.search(padrão, string) - Busca o padrão na string inteira e retorna logo o primeiro padrão encontrado
print(re.search("abc", "123abc"))
# Retorna <re.Match object; span=(3, 6), match='abc'>, padrão encontrado entre as posições 3 e 6
print(re.search("abc", "123ab"))
# Retorna None porque o padrão não foi encontrado
re.findall(padrão, string) - Também busca o padrão na string inteira e retorna uma lista de todos os padrões encontrados
print(re.findall("abc", "2abc123abc"))
# Retorna ['abc', 'abc'], uma lista com os dois padrões encontrados
print(re.findall("[0-9]", "2abc123abc"))
# Retorna ['2', '1', '2', '3'], todos os números dentro da string
print(re.search("[0-9]", "2abc123abc"))
# Já o método search retornaria <re.Match object; span=(0, 1), match='2'>, somente o primeiro número da string
print(re.findall("[0-9]", "abc"))
# Retorna uma lista vazia [], porque nenhum padrão foi encontrado
Espero ter ajudado!