Strings#

Na seção tipos de dados numéricos já começamos a ver um pouco sobre dois tipos de dados bastante comuns: int e float.

Nesta seção vamos apresentar outro tipo de dado representando textos. Tal estrutura também é chamado de string ou simplesmente str. Elas são utilizadas para armazenar e manipular texto. Neste capítulo, vamos explorar os conceitos básicos de strings e como trabalhar com elas em Python.

Definição#

Em Python, uma string é uma sequência de caracteres. Você pode definir uma string usando aspas simples 'Texto com aspas simples' ou aspas duplas "Texto com aspas dupla".

Quando não há a presença de ' ou " na string, usar aspas simples ou duplas é indiferente. Agora se o seu texto contiver uma das duas, como por exemplo, o texto Ela disse «Olá mundo!», como " faz parte do texto, só é possível criar uma string com esse texto entre aspas simples ', conforme exemplo abaixo.

msg_aspas_dupla = 'Ela disse "Olá mundo!"'
print(msg_aspas_dupla)
Ela disse "Olá mundo!"

Caso contrário, o Python pode confundir o que é aspas de abertura e fechamento da string com aspas do próprio texto. Observem no exemplo abaixo:

# Erro proposital!
erro_de_sintaxe = "Ela disse "Olá mundo!""
print(erro_de_sintaxe)
  Cell In[2], line 2
    erro_de_sintaxe = "Ela disse "Olá mundo!""
                                  ^
SyntaxError: invalid syntax

De forma a evitar tal erro, como recomendação geral, sempre que seu texto contiver ', a string deve ser iniciar e encerrar com ". Da mesma forma, se o texto contiver ", a string deve iniciar e encerrar com '.

É possível definir também uma string com multiplas linhas usando três aspas duplas """ ou três aspas simples '''.

msg_varias_linhas = """Esta é uma string
que se estende por
várias linhas."""
print(msg_varias_linhas)
Esta é uma string
que se estende por
várias linhas.

E podemos verificar que uma variável é do tipo string usando a função type (lembra dela, desta seção?)

variavel_tipo_str = 'Ela disse "Olá mundo!"'
print(type(variavel_tipo_str))
<class 'str'>

Operações com strings#

Concatenação com +#

Sabe aquela história de que 1 + 1 = 11? Pois bem! Isso é, em partes, verdade! Vamos entender melhor.

Aqui vamos fazer uma distinção clara entre operações com string de operações com int.

Quando somamos dois inteiros, o Python realiza a operação matemática de soma, conforme já vimos anteriormente.

print(1 + 1)
2

Porém, é possível representar 1 como o caracter número um, da seguinte forma

print("1" + "1")
11

Percebam que são operações completamente diferentes!

No primeiro caso acima, estamos somando o número inteiro 1 com outro número inteiro 1 (ambos são int), resultando em 2.

Já no segundo caso, estamos juntando o caracter 1 com outro caracter 1 (ambos são strings), resultando em 11. Tal operação de juntar texto é chamada de concatenação.

Agora você entende que 1 + 1 pode ser 11? No caso em que ambos 1 forem string!

Concatenação com f-strings#

Além do uso do operador + para concatenação, tal operação também pode ser feita através de f-strings, conforme vimos no exemplo 2 no capítulo sobre variáveis.

Usar f-strings para concatenação é muito mais comum em projetos reais, e elegante, do que usar +.

caracter_um = "1"
print(f"{caracter_um}{caracter_um}")
11

O interessante sobre f-strings é que mesmo a variável sendo do tipo int, ao usá-la dentro do contexto de f-string, o Python faz a conversão automática para str ao usar variávels em f-strings. Vamos ver conversões entre tipos mais à frente.

numero_um = 1
print(f"{caracter_um}{caracter_um}")
11

Replicação#

Da mesma forma que podemos concatenar strings usando +, podemos replicar uma string várias vezes usando *.

risada = "ha" * 10
print(risada)
hahahahahahahahahaha

Novamente, não é multiplicação matemática pois não estamos trabalhando com valores numéricos int ou float, mas sim uma replicação de texto, uma operação diferente!

Métodos de strings#

Nós já aprendemos a trabalhar com algumas funções já, como as funções print e type, que neste momento do livro você já deve saber como elas funcionam.

Agora vamos aprender outro conceito super importante: métodos.

O que são métodos?#

Antes de falarmos de métodos, vamos acrescentar um outro conceito: objeto.

Nesta altura, falamos muito sobre tipos de dados (int, float, str). No fundo, tipos de dados são objetos. Ao invés de falar tipo de dado str, podemos falar objeto do tipo str. Absolutamente tudo em Python é um objeto que tem um tipo! Mudar a forma de falar já está o colocando em outro patamar de entendimento sobre programação.

Vamos agora para definição de método. A definição que eu mais gosto é: métodos são funções que estão associadas a um determinado objeto e podem ser usadas para realizar operações nestes. Em termos simples, pense em métodos como ações que você pode realizar com um determinado objeto (int, float, str, etc.).

A diferença principal entre métodos e funções é a seguinte:

  • Métodos: são funções que estão associadas a um objeto e são chamadas usando a notação de ponto objeto.metodo(). Eles atuam sobre o próprio objeto ou acessam seus dados internos.

  • Funções: são blocos de código independentes que realizam uma tarefa específica e podem ser chamadas em qualquer lugar do código, não estando associadas a um objeto em particular, como as funções print e type.

Talvez seja mais fácil entender melhor com exemplos.

mensagem = "Oi, eu sou uma string!"
print(mensagem.upper())
OI, EU SOU UMA STRING!

No código acima temos uma notação que não vimos antes, mensagem.upper(). Sabemos já que mensagem é um objeto do tipo str (verifique com a função type se tiver dúvidas!). E strings tem várias «funções» associadas à ela. Estas «funções» são chamadas de métodos. No exemplo acima, estamos usando o método .upper() que transforma a string toda para maiúscula.

Portanto, todo método é acionado pelo . a partir do objeto. Veja no gif abaixo que quando você digita o . na variável mensagem, o VSCode abre uma lista de opções para você. Estas opções são todos os métodos disponíveis para str.

../_images/06-01-vscode-methods-str-autocomplete.gif

Métodos mais usados de strings#

Nota (lista completa de métodos)

Strings tem uma lista enorme de métodos disponíveis e aqui serão demonstrados apenas os mais comumente usados. Mas é de extrema importância que você saiba consultar todos os métodos disponíveis na documentação oficial. A lista completa com todos os métodos de string e suas respectivas documentações pode ser encontrada aqui.

Nota (valores booleanos)

Ao longo desta lista de métodos, vamos ver de forma indireta um outro tipo de dado chamado booleano, ou bool. Ele é bem simples, pois é uma estrutura de dados que contém apenas 2 valores: True, representando verdadeiro, e False representando falso.

Notem que as primeiras letras T e F são maísculas, caso contrário o Python gera erro de sintaxe.

Para fins puramente didáticos, vou separar os métodos em casos de uso, trazendo exemplos de uso de cada método.

  1. Transformação de case

  • str.upper(): Converte todos os caracteres da string para maiúsculas.

  • str.lower(): Converte todos os caracteres da string para minúsculas.

  • str.capitalize(): Converte o primeiro caractere da string para maiúscula e o restante para minúscula.

  • str.title(): Converte o primeiro caractere de cada palavra para maiúscula.

nome = "Um noME QuAlQuEr"
print(nome.upper())
print(nome.lower())
print(nome.capitalize())
print(nome.title())
UM NOME QUALQUER
um nome qualquer
Um nome qualquer
Um Nome Qualquer
  1. Remoção de espaços

  • str.lstrip(): Remove espaços em branco do início da string.

  • str.rstrip(): Remove espaços em branco do fim da string.

  • str.strip(): Remove espaços em branco do início e do fim da string.

Dica

O espaço em branco não é visível na saída. Sugiro selecionar o texto todo com o mouse para verificar os espaços em branco remanescentes.

string_com_espacos = "   Um texto qualquer com espaços em branco no começo e no final...      "
print(string_com_espacos.lstrip())
print(string_com_espacos.rstrip())
print(string_com_espacos.strip())
Um texto qualquer com espaços em branco no começo e no final...      
   Um texto qualquer com espaços em branco no começo e no final...
Um texto qualquer com espaços em branco no começo e no final...
  1. Contagem e verificação de sufixo/prefixo

  • str.startswith(prefix): Verifica se a string começa com um determinado prefixo.

  • str.endswith(suffix): Verifica se a string termina com um determinado sufixo.

  • str.count(sub): Conta o número de ocorrências de uma substring na string.

frase = "Olá, eu estou aprendendo a manipular textos em Python "
print(frase.startswith("Olá"))
print(frase.endswith("Python")) # False? Está correto mesmo?
print(frase.count("e")) # A letra 'e' aparece 5 vezes na frase
True
False
6

Atenção (espaços em branco)

O espaço em branco (deixado propositalmente) é super relevante na comparação de strings, por isso as strings Python (sem espaço em branco) e Python (com espaço em branco no final) são diferentes, apesar de ser apenas por um único caracter em branco. Por esta razão tivemos frase.endswith("Python") = False

  1. Verificação de caracteres

Todos estes métodos abaixo verificam se a string contém apenas:

  • str.islower(): caracteres minúsculos.

  • str.isupper(): caracteres maiúsculos

  • str.isalnum(): caracteres alfanuméricos.

  • str.isalpha(): caracteres alfabéticos.

  • str.isdigit(): dígitos.

  • str.isspace(): espaços em branco.

minusculo = "olá"
print(f"{minusculo} é todo minúsculo? {minusculo.islower()}")

maiusculo = "MUNDO"
print(f"{maiusculo} é todo maiúsculo? {maiusculo.isupper()}")

alfanumerico = "olá123"
print(f"{alfanumerico} é alfanumérico? {alfanumerico.isalnum()}")

alfabetico = "ola, eu sou um texto puramente alfabético #sqn..."
print(f"{alfabetico} é alfabético? {alfabetico.isalpha()}")

digitos = "12345"
print(f"{digitos} é dígito? {digitos.isdigit()}")

espacos = "   "
print(f"'{espacos}' é espaço em branco? {espacos.isspace()}")
olá é todo minúsculo? True
MUNDO é todo maiúsculo? True
olá123 é alfanumérico? True
ola, eu sou um texto puramente alfabético #sqn... é alfabético? False
12345 é dígito? True
'   ' é espaço em branco? True

Nota (caracteres alfabéticos)

O espaço em branco não é considerado um caracter alfabético, assim como #, , e .. Por outro lado, caracteres com acento, como á, são considerados alfabéticos.

  1. Divisão e junção

  • str.split(): Divide a string em uma lista, utilizando um separador especificado.

  • str.join(lista): Junta uma lista de strings utilizando um separador especificado.

Nota (listas)

Lista é uma outra estrutura de dados, que veremos em mais detalhes logo no capítulo seguinte.

nome_completo = "Lucas Ferreira de Almeida Gomes"

lista_de_strings = nome_completo.split()
print(lista_de_strings)

print("-".join(lista_de_strings))
['Lucas', 'Ferreira', 'de', 'Almeida', 'Gomes']
Lucas-Ferreira-de-Almeida-Gomes

Por padrão, o split() usa um ou mais espaços em branco para separar a string, mas é possível passar outro caracter separador. Vejamos outros exemplos:

cpf = "123.456.789-10"

# Dividindo o CPF através do `.`
print(cpf.split("."))

#Dividindo o CPF através do `-`
print(cpf.split("-"))
['123', '456', '789-10']
['123.456.789', '10']

Reforço prático - métodos vs funções#

Anteriormente, abordamos a diferença entre métodos e funções de maneira teórica. Agora, vamos revisitar essa diferença com uma abordagem mais prática!

Nós vimos vários métodos acima. E eu faço a seguinte pergunta agora: como calculamos o tamanho ou comprimento de uma string? Em outras palavras, quantos caracteres tem uma dada string?

Bom, você pode se lembrar que não vimos todos os métodos disponíveis, e ir procurar na documentação oficial na lista completa com todos os métodos de string. Lá, você vai encontrar algo com len, que vem de length (comprimento) do inglês. Ótimo! Este método len deve servir então! Vamos testar?

nome_comprido = "João Antônio da Silva Sauro de Souza Silva Santos"

# Contando a quantidade de caracteres deste nome enorme
print(nome_comprido.len()) # ... ops!
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[17], line 4
      1 nome_comprido = "João Antônio da Silva Sauro de Souza Silva Santos"
      3 # Contando a quantidade de caracteres deste nome enorme
----> 4 print(nome_comprido.len()) # ... ops!

AttributeError: 'str' object has no attribute 'len'

Na verdade, de fato o str.len() não existe mesmo, pois ele não está na lista de métodos da documentação oficial.

O que aconteceu afinal de contas? O erro é bem informativo: AttributeError: 'str' object has no attribute 'len', ou seja, strings não possuem o len.

Porém, para medirmos o comprimento de uma string, podemos fazer da seguinte forma:

nome_comprido = "João Antônio da Silva Sauro de Souza Silva Santos"

# Contando a quantidade de caracteres deste nome enorme
print(len(nome_comprido)) # ... agora sim!
49

É aqui que começamos a entender a diferença entre métodos e funções. Quando estamos acessando str.len() estamos acessando o método len de uma string, que não existe. O que existe é a função len() que recebe uma string.

Percebam que não existe str.len() na lista de métodos de uma string, e existe a função len() na lista de funções nativas do Python.

Porque len() é uma função e não um método de string? De acordo com a definição que foi dada: métodos são funções que estão associadas a um determinado objeto. Notem agora que eu consigo medir o comprimento de outras coisas, como uma lista de itens, por exemplo.

lista_de_numeros = [10, 50, 40, 65, 90, 70, 30, 156]
print(len(lista_de_numeros))
8

Ou seja, len() é uma função que mede o comprimento de uma dada sequencia qualquer. Como strings são sequencia de caracteres, eu consigo medir o comprimento de uma string unsando a função len(). Mas notem que len() não é exclusivo de strings. Métodos são exclusivos de um determinado tipo de objeto, como é o caso de str.upper() ou str.lower().

Conseguiram compreender agora porque usamos len(string) e não string.len()? Fez sentido?

Fatiamento (slicing)#

Fatiamento (slicing, em inglês) significa literalmente fatiar, pegar partes de uma string. Podemos pegar um único caracter isolado ou uma substring da nossa string principal. Para isso, usamos os colchetes [ ] logo após a variável para aplicar a operação de fatiamento.

Antes de ir para o fatiamento em si, vamos compreender melhor os índices de uma string.

Indexação de strings#

O fatiamento se dá pela posição das letras, também chamada de índice. Vamos compreender melhor através da tabela abaixo, usando a string Python como exemplo:

Caracter

P

y

t

h

o

n

Índices (+)

0

1

2

3

4

5

Índices (-)

-6

-5

-4

-3

-2

-1

Atenção (indexação começa por 0)

Chamo a atençao de vocês aqui para o fato de que a indexação em Python começa por zero! Ou seja, o primeiro caracter de uma string não é o índice 1, mas sim o índice 0. Esse detalhe da indexação começar por zero é a fonte origem de muitos erros comuns que percebo, portanto, fique atento!

Reparem também que podemos ter índices negativos, que são os caracteres na ordem inversa de leitura, da direita para esquerda.

Caracter isolado#

Vamos entender melhor com exemplos, primeiro buscando por um caracter isolado em uma string. Buscando uma explicação mais visual, tenha em mente a tabela abaixo:

Caracter

L

u

c

a

s

A

l

m

e

i

d

a

M

o

n

t

e

i

r

o

Índices (+)

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

Índices (-)

-22

-21

-20

-19

-18

-17

-16

-15

-14

-13

-12

-11

-10

-9

-8

-7

-6

-5

-4

-3

-2

-1

nome = "Lucas Almeida Monteiro"

primeiro_caracter = nome[0]
segundo_caracter = nome[1]
terceiro_caracter = nome[2]

primeir_espaco_em_branco = nome[5]
segundo_espaco_em_branco = nome[13]

print(f"O primeiro caracter do nome é a letra {primeiro_caracter}.")
print(f"O segundo caracter do nome é a letra {segundo_caracter}.")
print(f"O terceiro caracter do nome é a letra {terceiro_caracter}.")
print(f"Os caracteres de índice 5 e 13 são espaços em branco: {primeir_espaco_em_branco}, {segundo_espaco_em_branco}")
O primeiro caracter do nome é a letra L.
O segundo caracter do nome é a letra u.
O terceiro caracter do nome é a letra c.
Os caracteres de índice 5 e 13 são espaços em branco:  ,  

Conforme explicado, é possível usar ínndices negativos no fatiamento.

nome = "Lucas Almeida Monteiro"

ultimo_caracter = nome[-1]
penultimo_caracter = nome[-2]
antepenultimo_caracter = nome[-3]

print(f"O último caracter do nome é a letra {ultimo_caracter}.")
print(f"O penúltimo caracter do nome é a letra {penultimo_caracter}.")
print(f"O antepenúltimo caracter do nome é a letra {antepenultimo_caracter}.")
O último caracter do nome é a letra o.
O penúltimo caracter do nome é a letra r.
O antepenúltimo caracter do nome é a letra i.

Grupo de caracteres em sequencia#

Podemos usar a mesma notação de [ ] para capturar não somente um único caracter mas sim um grupo de caracteres da string. Para isso, precisamos entender a forma como é feito manipulando os índices do nosso fatiamento.

variavel[start:stop]: esta é a sintaxe básica de buscar por uma substring dentro de uma string. Eu preciso definir em qual caracter eu começo meu fatiamento através do start e em qual caracter eu termino meu fatiamento no stop.

Atenção (índice final não é inclusivo)

O caracter do índice final não é incluído na saída do fatiamento!

Vejamos alguns exemplos para entender melhor como ler esta sintaxe:

nome = "Lucas Almeida Monteiro"

primeiro_nome = nome[0:5]
print(f"O primeiro nome é {primeiro_nome}")
O primeiro nome é Lucas

Obs: o caracter de índice 5 no exemplo é um espaço em branco! Selecionem todo o texto da saída com o mouse e percebam que ele não contém espaço em branco

  • O índice inicial começa no caracter de índice 0 (inclusivo)

  • O índice final finaliza no caracter de índice 5 (exclusivo)

  • Como o caracter de índice 5 é exclusivo, ele não é incluído no resultado final do fatiamento

  • Como resultado, teremos apenas os caracteres de índice 0 até 4.

Quando o caracter inicial for o 0, podemos omití-lo.

nome = "Lucas Almeida Monteiro"

primeiro_nome = nome[:5]
print(f"O primeiro nome é {primeiro_nome}")
O primeiro nome é Lucas

Vamos isolar o sobrenome agora.

nome = "Lucas Almeida Monteiro"

ultimo_sobrenome = nome[6:13]
print(f"O sobrenome é {ultimo_sobrenome}")
O sobrenome é Almeida
  • O índice inicial começa no caracter de índice 6 (inclusivo)

  • O índice final finaliza no caracter de índice 13 (exclusivo)

  • Como o caracter de índice 13 é exclusivo, ele não é incluído no resultado final do fatiamento

  • Como resultado, teremos apenas os caracteres de índice 6 até 12.

E, finalmente, o último sobrenome:

nome = "Lucas Almeida Monteiro"

ultimo_sobrenome = nome[14:21]
print(f"O sobrenome é {ultimo_sobrenome}")
O sobrenome é Monteir

Aqui temos algo interessante. O último índice da string é o 21. Mas já sabemos que o índice final do fatiamento é exclusivo. Da forma como fizemos acima, teríamos que colocar um índice a mais para incluir o último caracter.

nome = "Lucas Almeida Monteiro"

ultimo_sobrenome = nome[14:22]
print(f"O sobrenome é {ultimo_sobrenome}")
O sobrenome é Monteiro

Porém, apesar de funcionar, fica estranho, dado que o último índice é 21. Quando desejamos extrair tudo, até o final da string, com o último caracter incluso, podemos omitir o stop.

nome = "Lucas Almeida Monteiro"

# Leia-se: tudo do caracter 14 até o final da string incluindo o último caracter.
ultimo_sobrenome = nome[14:]
print(f"O sobrenome é {ultimo_sobrenome}")
O sobrenome é Monteiro

Podemos usar índices negativos também. Para uma maior clareza, vou trazer novamente a tabela com os índices:

Caracter

L

u

c

a

s

A

l

m

e

i

d

a

M

o

n

t

e

i

r

o

Índices (+)

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

Índices (-)

-22

-21

-20

-19

-18

-17

-16

-15

-14

-13

-12

-11

-10

-9

-8

-7

-6

-5

-4

-3

-2

-1

nome = "Lucas Almeida Monteiro"

# Leia-se: tudo do caracter -8 até o final da string incluindo o último caracter.
ultimo_sobrenome = nome[-8:]
print(f"O último sobrenome é {ultimo_sobrenome}")
O último sobrenome é Monteiro

Os índices negativos em Python começam a contagem a partir do final da string para o início. O índice -1 refere-se ao último caractere, -2 ao penúltimo, e assim por diante. No entanto, esses índices não invertem a ordem dos caracteres. Eles simplesmente fornecem uma maneira conveniente de referenciar caracteres a partir do final da string. Portanto, podemos dizer que os índices 14 e -8 referem-se ao mesmo caracter da string, a letra M.

Em ambos os casos, o índice especifica a posição inicial da substring e continua até o final da string, sem alterar a ordem dos caracteres. É por isso que tanto nome[14:] quanto nome[-8:] produzem a mesma substring Monteiro.

Grupo de caracteres pulando de n em n#

Vamos tentar ir devagar para não confundir a cabeça neste momento. Até agora, vimos que a sintaxe variavel[start:stop] traz todos os caracteres do start até o stop excluindo o caracter do indice_final. Certo?

Podemos acrescentar algo nesta sintaxe para trazer os caracteres pulando os índices de 2 em 2, por exemplo. É o que chamamos de step, ou passo. A sintaxe fica, então variavel[start:stop:step] no qual:

  • start: Índice onde a fatia começa. Se omitido, a fatia começa no início da sequência no índice 0.

  • stop: Índice onde a fatia termina, mas não inclui o elemento neste índice. Se omitido, a fatia vai até o final da sequência.

  • step: Número que determina o intervalo entre os índices. Assume o valor padrão 1. Se for negativo, a sequência será percorrida de trás para frente.

Se quisermos trazer todos os caracteres de índice par, por exemplo:

nome = "Lucas Almeida Monteiro"

caracteres_indice_par = nome[::2]
print(f"Somente os caracteres de índice par: {caracteres_indice_par}")
Somente os caracteres de índice par: LcsAmiaMner

Da mesma forma, para trazermos todos os caracteres de índice ímpar:

nome = "Lucas Almeida Monteiro"

caracteres_indice_par = nome[1::2]
print(f"Somente os caracteres de índice ímpar: {caracteres_indice_par}")
Somente os caracteres de índice ímpar: ua led otio

Podemos pular os caracteres de 3 em 3:

nome = "Lucas Almeida Monteiro"

caracteres_3_em_3 = nome[::3]
print(f"Caracteres pulados de 3 em 3: {caracteres_3_em_3}")
Caracteres pulados de 3 em 3: LaAeaoeo

O step também pode assumir um valor negativo, fato que inverte a ordem da string no fatiamento:

nome = "Lucas Almeida Monteiro"

nome_inverso = nome[::-1]
print(f"Nome invertido: {nome_inverso}")
Nome invertido: orietnoM adiemlA sacuL

Trazendo os caracteres na ordem inversa pulando de 2 em 2:

nome = "Lucas Almeida Monteiro"

nome_inverso_de_2_em_2 = nome[::-2]
print(f"Nome inverso de 2 em 2 caracteres: {nome_inverso_de_2_em_2}")
Nome inverso de 2 em 2 caracteres: oito del au

Prática#

Agora você já é capaz de resolver os exercícios 6, 7 e 8 da lista! O exercício 8 é um desafio! Encara? Boa prática!

Conclusão#

Vimos o básico (sim, somente o básico) de manipulação de strings! Ao longo deste capítulo, você ouviu falar brevemente de listas, e é esta estrutura que vamos aprender no próximo capítulo do livro!