Menu fechado

Python3 04 – Sequências: Strings e Tuplas

O Python é uma linguagem muito rica e repleta de peculiaridades, de forma que escrever algo sobre ele acaba se tornando um tanto quanto desafiador, dada a diversidade tópicos e aspectos interessantes para abordar.

Como o meu objetivo inicial era fazer uma revisão dos conceitos e definições do Python 3, estava para criar uma coletânea de textos focando mais nas peculiaridades da linguagem do que na apresentação de algo mais formal. No entanto, o material tem ganhado volume e achei mais conveniente organizar os textos de forma mais didática.

Neste texto, inicio uma série de dois artigos focados nas estruturas de Dados Sequenciais, que envolvem strings, tuplas e listas, explorando suas particularidades e adentrando um pouco mais nas “funções embutidas” (métodos e atributos das classes). Na sequência, devo falar de Dicionários e mais alguns tópicos complementares interessantes.

1. Estruturas de Dados Sequenciais

As estruturas de dados sequenciais do Python são aquelas que possuem as características:

  • suporte ao operador in, a função len(),
  • suporte a slices (fatiamento [])
  • e pode ser um iterável.

As estruturas que satisfazem a estes critérios são bytearray, bytes, strings, tuplas e listas. Por mera escolha pessoal, deixarei bytearray e bytes para apresentar em alguma outra ocasião, focando os próximos textos nas quatro seguintes.

1.1. Strings

No Python, uma string é uma sequência de caracteres e, como todas as demais sequências que serão apresentadas, cada elemento (no caso caractere) pode ser indexado por colchetes [], como feito em um vetor em outras linguagens.

Sequências também podem ser endereçadas de forma reversa, sendo -1 o índice do último elemento da sequência, -2 o penúltimo, e assim por diante. A Figura 01 apresenta todo o indexamento para os elementos da string sobrenome, de forma direta e reversa.

O primeiro elemento de uma sequência é sempre o de índice zero e o último, o de índice -1. Todas as formas de sequências em Python obedecem ao mesmo padrão de indexamento dos elementos, assim como o fatiamento de sequências (slicing), apresentados a seguir.

1.1.1. Slicing/Fatiamento de Sequências

Sequências podem ser fatiadas passando o início e o fim do fatiamento entre colchetes, como a sintaxe abaixo:

seq[início:fim]
seq[início:fim:step]

Onde [início] marca o índice do primeiro elemento a ser tomado, e o fim o índice de stop, ou seja, o fatiamento termina com no elemento de índice [fim-1]. O step é o incremento aplicado ao índice a cada fatiamento. Veja algumas aplicações a seguir:

O primeiro comando pega do elemento [1] até o elemento [5-1] da sequência, sendo este o elemento de índice [4] da sequência sobrenome. No comando seguinte, é impresso do elemento [2] até o elemento [4-1]. No terceiro comando, é passada a opção step, a qual declara o intervalo entre os elementos tomados como 2. Desta forma, será impresso o elemento [1] e, em seguida, o elemento [3] (1+step) da sequência. O elemento [3+2] alcança o stop e, por isto, não é impresso.

As duas últimas linhas mostram que, quando o início não é definido, o índice 0, primeiro elemento da sequência, é tomado como padrão e, quando o fim não é definido, o índice -1, último elemento da sequência, é tomado como padrão. Portanto o sobrenome[2:] imprime do elemento de índice 2 até o final da string, enquanto que o sobrenome[:-2], imprime do início da sequência até o penúltimo elemento da sequência.

Um algorítimo para o fatiamento, slicing(), foi apresentado no texto anterior Python3 03 – Controle de Fluxo e Funções, na última seção, em Funções. Embora a função não funcione com endereçamento reverso, serve para ilustrar o funcionamento do fatiamento.

1.1.2. Strings são Imutáveis

Retomando o foco à variável sobrenome, observe que não se pode sobrescrever o conteúdos de um de seus elementos:

Como dito anteriormente, uma string no Python é um imutável e não pode ter seu valor alterado. No entanto, é perfeitamente possível referenciar um novo objeto string 'Ribeiro' para o apontador sobrenome.

Embora as “variáveis” em Python sejam apenas apontadores para objetos, aqui e ao longo dos demais textos devo chamá-las apenas de variáveis, uma vez que na maioria dos casos estas diferenças se tornam transparentes e os apontamentos funcionam praticamente como as variáveis tradicionais. Haverá ocasiões em que ter consciência do status mutável/imutável de um apontamento se tornará essencial para compreender o comportamento do seu código. Nestes casos, pretendo relembrar tais diferenças.

1.1.3. Funções Embutidas – Métodos e Atributos de String

Uma grande vantagem da orientação a objetos para programadores iniciantes está nas funções embutidas no próprio objeto, mais conhecidas como métodos e atributos do objeto. Mesmo que o programador não conheça nada de orientação a objetos, ele pode iniciar seus programas com uma gama enorme de funções e atributos essenciais para manipulação das informações em suas variáveis. Estes métodos e atributos podem ser consultados com o comando dir(objeto), o qual gera uma lista com todos os métodos e atributos do objeto em questão.

Não pretendo explorar todos os métodos e atributos da classe string, mas vou apresentar os métodos mais interessantes e essenciais na manipulação de strings.

Alguns métodos, cujos nomes possuem o formato ‘__método/atributo__‘, são para acesso a algum atributo da classe, como o seu comprimento (__len__), valor impresso (__str__), ou operadores para os quais a classe possui suporte, como:

  • __add__ adição (+), concatenação de strings
  • __mul__ multiplicação por inteiro
  • Comparadores suportados:

  • __eq__ igualdade (==)
  • __ge__ maior ou igual a (>=)
  • __gt__ maior que (>)

além de outros operadores. O quadro abaixo apresenta alguns exemplos:

Observe que os resultados, quando usados os métodos __len__() e __contains__() em uma string, são os mesmos ao empregar a função len() e o operador in.

A função len(nome) somente acessa o método __len__() do objeto nome e, este sim, retorna o seu comprimento da string. A ideia é bem simples: se o objeto não possuir o método __len__(), isto significa que ele não possui a propriedade comprimento. O mesmo serve para os métodos __contains__, __add__, __mul__, __le__ e tantos outros.

As linhas a seguir ilustram este ponto.

Lembre-se de que o comando dir(objeto) retorna uma lista com todos os métodos e atributos do objeto passado. Portanto, '__len__' in dir(objeto) retornará verdadeiro (True) somente se o método '__len__' estiver contido na lista retornada por dir(objeto).

Como o método __len__ não está contido na lista gerada pelos métodos/atributos do inteiro 10, este não possui a propriedade comprimento e, por isto, uma tentativa de acessar seu comprimento gera o erro TypeError. Já o comando len(nome) retorna sem problemas o comprimento da string apontado por nome.

Em geral, os atributos e métodos iniciados por ‘_’ ou ‘__’ não devem ser acessados diretamente, mas sim por intermédio de outras funções ou por meio de operadores. Já os métodos nomeados são e devem ser usados. Seguem alguns métodos bem úteis em strings:

  • capitalize() – retorna uma string com caixa alta no primeiro caractere;
  • center(width[, fillchar]) – retorna uma string centralizada em comprimento width. O fillchar (caractere de preenchimento) é opcional, mas preenche os espaços vazios da centralização com o caractere de preenchimento;
  • count(sub[, start[, end]]) – retorna o número de ocorrências da substring sub na string. start e end são opcionais, mas denotam as posições de início e final da busca;
  • find(sub[, start[, end]]) – retorna o menor índice onde a substring sub é encontrada. start e end denotam as posições de início e final da busca;
  • format(*args, **kwargs) – permite formatar a saída. Numa apresentação básica, o format substitui os argumentos nos campos emntre chaves na string de formatação, respeitando seu nome ou a ordem passada como os exemplos a seguir:

    Numa abordagem mais detalhada é possível formatar a saída com mais refino, mas isso ficará para textos futuros;

  • isalnum/isalpha/isdecimal/is… – uma série de testes na string que retorna verdadeiro se o teste for bem sucedido. Essencialmente, nome.islower() retorna True se todos os caracteres forem caixa baixa, nome.isdigit() retorna True se todos os caracteres forem números, …;
  • lower()/upper() – retorna uma string em caixa baixa/alta;
  • split(sep=None) – retorna uma lista de palavras usando sep como caractere separador. O separador padrão é o espaço;
  • join(interable) – retorna uma string que é a concatenação das strings no interable. Veja exemplo a seguir;
  • strip/rstrip/lstrip() – remove espaços vazios ao redor, à direita e à esquerda da string, respectivamente;
  • title() – coloca o texto no formato de título, com caixa alta nos primeiros caracteres;

1.2. Tuplas

Uma tupla é uma sequência ordenada de zero ou mais objetos imutáveis, de qualquer tipo. Embora não seja necessário, tuplas podem ser iniciadas com o parênteses, ou pela função tuple(). Veja alguns exemplos a seguir:

Como pode observar, as três tuplas possuem o mesmo valor. Pessoalmente, prefiro a primeira sintaxe, da variável a, já que a segunda me parece um tanto excessiva em redundâncias. A terceira é bem útil para iniciar diversas variáveis simultaneamente e, para este fim, é a forma preferida da maioria dos programadores. Veja o exemplo:

Como todas as sequências, tuplas também podem ser usadas com slices e não aceitam alteração nos elementos, como dito anteriormente.

Mas é possível criar uma nova tupla com os elementos desejados e apontá-la com ao mesmo identificador:

Desta forma, é possível “alterar” o primeiro elemento da tupla a… Na verdade, o que se fez foi criar uma nova tupla com o elemento (4,) mais os elementos do [1] ao último elemento da tupla a. Por fim, a nova tupla é apontada para o identificador a.

Obviamente, tuplas não são feitas para serem “alteradas”, uma vez que existe outra estrutura mais adequada para isto, que será vista no próximo texto: as listas. Mas esta simplicidade das Tuplas tem algumas vantagens, como serem mais rápidas que lista e outras estruturas de sequência em Python justamente por possuírem um estrutura mais simples.

Observe que a tupla (4,), especificamente com a vírgula, deve ser adicionada para se construir a tupla desejada. Sem esta estrutura, o comando vai retornar um TypeError, pois o interpretador vai entender o (4) como uma expressão matemática, ou seja, um quatro entre parênteses, e não como uma tupla.

Dada a sua simplicidade, as Tuplas possuem apenas dois métodos: count e index.

  • count(value) – retorna o número de elementos value na tupla;
  • index(value, [start, [stop]]) – retorna o índice de value na tupla. Slices podem ser utilizados para limitar o intervalo de busca, como em strings.

Os comandos dir() e help() são formas rápidas de consultar os métodos de uma classe, os quais é sempre bom lembrar.

Como discutido em strings anteriormente, os métodos iniciados por um duplo underline são suportes a operadores como ‘+‘, ‘*‘, in, …, operadores de comparação e acesso a atributos internos como o __len__, para o comprimento da tupla. Existem ainda nesta lista o construtor e destrutor padrão do objeto __init__ e __delattr__, bem como outros atributos específicos da classe tupla. Neste momento, não vou adentrar nestas especifidades de classes ou mesmo em discussões sobre orientação a objetos. Pretendo abordar estes aspectos em textos específicos mais adiante.

Para terminar com as tuplas, apenas um código simples para empregá-la como iterador em um laço de repetição for:

Neste laço for, a variável i assume os diferentes valores dos elementos da tupla a, empregando-a como um iterável. Os elementos são impressos na tela pelo comando print(i).

O mesmo pode ser feito com o laço de repetição a seguir:

O código acima faz exatamente o mesmo do código anterior, apenas de uma forma menos elegante. A função range(inteiro) retorna um iterador usando o comprimento de a (len(a)) como argumento, ou seja, como o comprimento de a é 4, ele retorna um iterador sobre uma tupla (ou equivalente) da lista de inteiros (0, 1, 2, 3). A cada consulta ao iterador, ele retorna um inteiro que será usado como índice para acessar os elementos da tupla a. Esta é uma das grandes belezas do Python: a possibilidade de simplificar ainda mais um código sem perder a sua legibilidade.

2. Considerações

“Iteradores e geradores” é mais um dos tópicos que pretendo detalhar mais adiante. No próximo texto, trago uma discussão sobre listas, uma estrutura de dados bem parecida com tuplas, mas com uma pequena divergência que faz toda a diferença.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

This site uses Akismet to reduce spam. Learn how your comment data is processed.