Python: Classes, variáveis do usuário

Programação Orientada a Objetos

Apesar de que o Python é uma linguagem multiparadigma ele dá suporte para a maioria das técnicas empregadas na Programação Orientada a Objetos, POO. Os tipos de dados que encontramos no Python, tais como strings, números inteiros, listas e dicionários são objetos. Eles possuem propriedades e métodos próprios, pré-programados. É frequente, no entanto, que coisas da vida real, sobre as quais queremos usar métodos de computação e análise, exijam uma modelagem mais complexa. Para isso podemos criar objetos que são os tipos definidos pelo usuário, denominados classes. Além de ser uma ferramenta útil para quem cria um programa, essa possibilidade foi explorada por outros programadores que disponibilizam seu código através de módulos disponíveis em um grande número de bibliotecas que expandem o poder do Python.

A programação orientada a objetos faz uso das seguintes técnicas:

  • Encapsulamento de dados : a restrição de que dados e métodos só possam ser acessados de dentro do objeto, com acesso vedado a chamadas externas.
  • Herança : a possibilidade de reutilizar e estender a definição de uma classe, alterando-a para especializações.
  • Polimorfismo : a possibilidade de que várias classes usem os mesmos nomes de métodos gerais.

No Python o encapsulamento de dados não é obrigatório nem automático, mas pode ser implementado. Herança e polimorfismo são partes naturais de sua sintaxe.

Assim como em outras partes desse texto, os exemplos dados são simples e pouco realistas. Casos complexos são compostos de um grande número de partes simples, portanto entender o simples é um grande passo para o gerenciamento dos casos gerais. Além disso nossas classes não são devidamente documentadas para diminuir o tamanho do texto e facilitar a leitura, o que deve ser evitado na prática.

Classes: Tipos de dados criados pelo usuário

Suponha que desejamos elaborar um programa para controle de uma escola. Um elemento básico desse programa seria, por ex., a descrição dos alunos. Um aluno pode ser descrito por uma série de dados de tipos diversos, como uma string para armazenar seu nome, inteiros para sua idade, floats para suas notas, datas para a data de nascimento, etc. É claro que podemos configurar listas ou dicionários complexos que contenham toda essa informação. No entanto temos uma ferramenta mais sofisticada e poderosa: as classes.

Definições

Uma classe é uma abstração de alguma entidade que se deseja modelar. Um objeto é um caso particular da entidade representada pela classe. Dizemos que o objeto é uma instância da classe. A classe possui atributos que podem ser propriedades ou métodos. Propriedades são o conjunto de valores (dados) que descrevem a entidade. Métodos são funções definidas na classe. Esses métodos contém instruções para as tarefas que esperamos que sejam executadas por aquela entidade.

Os métodos mais frequentes são aqueles que realizam operações CRUD (Create, Read, Update e Delete, ou seja, criar, ler, atualizar e apagar). Mas eles não se retringem a isso e podem fazer operações com os dados armazenados em propriedades, retornar resultados, imprimí-los ou executar qualquer tarefa disponível para a máquina que executa o código.

Uma classe é definida com a palavra chave class seguida de seu nome (por convenção iniciado por maiúscula). A partir da classe objetos são instanciados, ou seja, criados sob o molde de sua classe geradora. Um objeto instanciado de uma classe possui as suas propriedades e métodos.

A classe mais simples é aquela que não contém em sua definição nenhuma propriedade nem método. A palavra chave pass marca a posição, sem executar nenhuma tarefa. Propriedades podem ser inseridas, alteradas e lidas com a notação de ponto, objeto.propridade.

# Uma classe simples, sem propriedades ou métodos
» class Simples:
»     pass

# instanciando um objeto da classe
» s1 = Simples()
# inserindo 2 propriedades
» s1.propriedade1 = 'A solução para a questão da vida, do universo e tudo mais'
» s1.propriedade2 = 42

# examinando o estado das propriedades
» print(s1.propriedade1, s1.propriedade2)
↳ A solução para a questão da vida, do universo e tudo mais 42

# atributos são listados com dir
» print(dir(s1))
↳ ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', ↳ '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
↳ '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
↳ 'propriedade1', 'propriedade2']

# uma propriedade pode ser removida
» del s1.propriedade2

# a classe (modelo para os objetos) pode ser alterada após sua definição
» Simples.valor = 9
» s2 = Simples()
» s2.valor
↳ 9
» s1.valor
↳ 9

: A função dir lista os atributos da classe ou dos objetos, entre elas as que definimos acrescidas de muitas outras geradas automaticamente. Voltaremos a isso mais tarde. Vemos acima que uma alteração na classe gera alterações nas instâncias, mesmo aquelas criadas antes dessa alteração.

Propriedades podem ser definidas na classe, sendo herdadas por suas instâncias.

» class Aluno():
»     nome = ''
»     nota = 0

# instanciamos 2 objetos da classe
» aluno1 = Aluno()
» aluno2 = Aluno()

# as propriedades podem ser acessadas para leitura e alteração
» aluno1.nome = 'Ana Anésia'
» aluno1.nota = 79

» print(aluno1.nome, aluno1.nota) 
↳ Ana Anésia  79

# a propriedade __class__ indica a que classe pertence esse objeto
» aluno1.__class__
↳ __main__.Aluno

» aluno2.nome = 'Pedro Paulo Passos'
» aluno2.nota = 45

# propriedades podem ser usadas como variáveis
» print('Média dos alunos: ',  (aluno1.nota + aluno2.nota)/2)
↳ Média dos alunos:  62.0

Uma alteração na classe Aluno altera as propriedades dos objetos dela derivados e que ainda não foram modificados. Propriedades já modificadas em código não são alteradas. Novas instâncias da classe também são criadas com a nova propriedade.

# uma alteração na classe    
» Aluno.nota = 50
» aluno3 = Aluno()
» print(aluno3.nota)
↳ 50

» Aluno.nota = 65
» print(aluno3.nota)
↳ 65

# propriedades já alteradas não são afetadas
» print(aluno1.nota)
↳ 79


O parâmetro self serve como uma referência para o próprio objeto (como veremos, para cada uma das instâncias da classe) e é usado para acessar os atributos daquele objeto. Qualquer nome de variável pode ser usado, embora seja uma convenção chamá-lo de self. Não é recomendado usar outro nome se queremos que nosso código seja de fácil leitura para outros programadores. Ele deve ser sempre o primeiro parâmetro de todas as funções e mesmo os métodos que não recebem outros parâmetros devem conter self.

Além de propriedades uma classe pode ter métodos que são funções executadas dentro da classe. A classe Calculadora que se segue contém apenas um método e nenhuma propriedade acessada pelo usuário. Ela recebe dois números e o tipo de operação desejada, + – * / e retorna uma string com a operação e seu resultado.

» class Calculadora:
»     def calcular(self, x, y, operacao = '+'):
»         if operacao == '+':   resultado = '%.1f + %.1f = %.1f' % (x, y, x + y)
»         elif operacao == '-': resultado = '%.1f - %.1f = %.1f' % (x, y, x - y)
»         elif operacao == '*': resultado = '%.1f x %.1f = %.1f' % (x, y, x * y)
»         elif operacao == '/':
»             if y == 0:  resultado = 'Erro: divisão por zero!'
»             else:       resultado = '%.1f / %.1f = %.1f' % (x, y, x / y)
»         else:
»             resultado = 'Nenhuma operação conhecida (+ - * /) foi requisitada!'
»         print(resultado)

# instancia um objeto
» calc = Calculadora()
# a operação de soma é default
» calc.calcular(123,123)
↳ 123.0 + 123.0 = 246.0

» calc.calcular(213,432, '-')
↳ 213.0 - 432.0 = -219.0

» calc.calcular(213,43, '*')
↳ 213.0 x 43.0 = 9159.0

» calc.calcular(213,0, '/')
↳ Erro: divisão por zero!

» calc.calcular(213,23, 'r')
↳ Nenhuma operação conhecida foi definida!

# calc é uma variável do tipo
» type(calc)
↳ __main__.Calculadora

Os Métodos __init__() e __str__()


Todas as classes possuem um método chamado __init __(), mesmo que não explicitamente definido, que é sempre executado durante sua inicialização. O nome __init__, iniciado e terminado por 2 sublinhados (em inglês chamado de dunder, double underscore), é chamado de construtor da classe.

Também existe o método __repr__, similar ao __str__, tratado no capítulo Métodos especiais.

Esse método é usado para fazer as atribuições de valor às propriedades do objeto e executar outras operações que sejam necessárias quando o objeto está sendo criado. É considerada uma boa prática de programação não definir propriedades fora da inicialização, exceto quando propriedades de classe são necessárias. Após a inicialização o método não deve ser usado novamente.

Ao ser instanciado um objeto os parâmetros de __init__ devem ser fornecidos ou um erro é lançado.

» class Aluno:
»     def __init__(self, nome, nota):
»         self.nome = nome
»         self.nota = nota        
    
# Inicializamos um objeto com seus valores iniciais
» aluno1 = Aluno('Joana Paraíba', 67)
» print(a1.nome, a1.nota)
↳ Joana Paraíba  67

# como antes, a propriedade pode ser alterada
» aluno1.nome = 'Joana Pessoa'
» aluno1.nome
↳ 'Joana Pessoa'

# um atributo pode ser removido do objeto
» del aluno1.nome
» print(aluno1.nome)
↳ AttributeError: type object 'a1' has no attribute 'nome'

Após a definição da classe dois objetos foram instanciados: aluno1 e aluno2 têm as mesmas propriedades que a classe Aluno. Essas propriedades podem ser modificadas com a notação de ponto, objeto.propriedade, e acessadas como variáveis comuns, como foi feito no cálculo da média das notas.

Como no caso da calculadora acima, classes contém propriedades e métodos, ou funcionalidades. Métodos do objetos são funções definidas dentro de um objeto. Na nossa classse Aluno, vamos inserir vários métodos: get_sobrenome(), que retorna a última string após partir o nome nos espaços, set_nota() que insere as notas do aluno, get_media() que calcula e retorna a média das 3 notas, e o método especial __str()__ que retorna uma representação de string contendo os dados que julgamos representativos do objeto.

» class Aluno:
»     def __init__(self, nome, nota1=0, nota2=0, nota3=0):
»         self.nome = nome
»         self.nota1 = nota1
»         self.nota2 = nota2
»         self.nota3 = nota3

»     def get_sobrenome(self):
»         return self.nome.split()[-1]

»     def set_nota(self, n, nota):
»         if n==1:
»             self.nota1=nota
»         if n==2:
»             self.nota2=nota
»         if n==3:
»             self.nota3=nota

»     def get_media(self):
»         return (self.nota1 + self.nota2 + self.nota3)/3
        
»     def aprovado(self):
»         return self.get_media() >= 50

»     def __str__(self):
»         txt = 'Nome: ' + self.nome
»         txt += '\nNotas: %2.1f, %2.1f, %2.1f' % (self.nota1, self.nota2, self.nota3) 
»         txt += '\nMédia: %2.1f' % self.get_media()
»         txt +=  '  ** %s **\n' % ('aprovado' if self.aprovado() else 'reprovado')
»         return txt

Acessar um método de instância, objeto.metodo() é o mesmo que chamar o método da classe passando o próprio objeto (a instância) como argumento self.

# instanciando um aluno e suas notas
» a1 = Aluno('Mário Leibniz', 34, 45, 76)
# usar o método de instância
» a1.get_media()
↳ 51.666666666666664

# é o mesmo que chamar o método
» Aluno.get_media(a1)
↳ 51.666666666666664

# 1 aluno inicializado com as notas default    
» pedro = Aluno('Pedro Álvarez')
# pedro tem a propriedade nome
» print(pedro.nome)
↳ Pedro Álvarez

# as notas de pedro podem ser alteradas
» pedro.set_nota(1,84)
» pedro.set_nota(2,89)
» pedro.set_nota(3,97)
# e sua média pode ser calculada
» pedro.get_media()
↳ 90.0

Uma classe pode ter em suas propriedades dados de qualquer tipo. No exemplo seguinte criamos a classe Escola contendo apenas nome e uma lista de alunos, inicialmente por uma lista vazia e depois preenchida com objetos da classe Aluno.

# usando 2 alunos inicializados
» a1 = Aluno('Mário Leibniz', 34, 45, 76)
» a2 = Aluno('Joana Frida', 10, 28, 16)    
    
# definimos a class Escola
» class Escola:
»     def __init__(self, nome):
»         self.nome = nome
»         self.alunos = []

»     def insere_aluno(self, aluno):
»         self.alunos.append(aluno)

»     def __str__(self):        
»         txt = 'Nome: ' + self.nome
»         txt += '\nTem %d alunos' % len(self.alunos)
»         txt += '\nAlunos:\n'
»         for a in self.alunos:
»             txt += '-' * 38 + '\n'
»             txt += str(a)
»         return txt    

# criamos uma instância da classe
» escola1 = Escola('Caminhos da Luz Albert Einstein')

# inserimos os dois alunos já definidos
» escola1.insere_aluno(a1)
» escola1.insere_aluno(a2)

# usamos o método Escola.__str__ para listar o estado de escola1
» print(escola1.__str__())
↳ Nome: Caminhos da Luz Albert Einstein
↳ Tem 2 alunos
↳ Alunos:
↳ --------------------------------------
↳ Nome: Mário Leibniz
↳ Notas: 34.0, 45.0, 76.0
↳ Média: 51.7   **aprovado**
↳ --------------------------------------
↳ Nome: Joana Frida
↳ Notas: 10.0, 28.0, 16.0
↳ Média: 18.0   ** reprovado**

Embora não obrigatório, é uma convenção útil usar o nome da classe com primeira letra maiúscula. Esse código define a classe Aluno que tem as propriedades nome, nota1, nota2 e nota3, e os métodos set_nota(), que atribue valor a uma das 3 notas; get_sobrenome(), que retorna o último nome registrado; get_media(), que retorna a média das 3 notas; aprovado() que retorna um booleano (False ou True) informando se a aluno foi aprovado. O conjunto das propriedades de um objeto consistem em seu estado. Métodos são as funções que o objeto pode executar, em geral associadas ao seu estado.

Um método retorna um valor de tipo especificado ou None se é terminado por return vazio ou mesmo sem nenhum return.

Nas classes Aluno e Escola usamos também o método especial __str__(self) que retorna uma representação de string do objeto. Ele definido internamente de modo a ser acionado quando fazemos uma chamada a print(objeto). São equivalentes as instruções:
print(objeto.__str__()), print(str(objeto)), print(objeto).

» print(pedro)        # ou print(str(pedro)) ou  print(pedro.__str__())
↳ Nome: Pedro Álvarez
↳ Notas: 84.0, 89.0, 97.0
↳ Média: 90.0

# idem
» print(escola1)
↳ Nome: Caminhos da Luz Albert Einstein
↳ Tem 2 alunos
↳ Alunos:
↳ --------------------------------------
↳ Nome: Mário Leibniz
↳ Notas: 34.0, 45.0, 76.0
↳ Média: 51.7   **aprovado**
↳ --------------------------------------
↳ Nome: Joana Frida
↳ Notas: 10.0, 28.0, 16.0
↳ Média: 18.0   ** reprovado**

Vemos, dessa forma, que uma classe é um modelo geral que pode ser usado para a criação de várias casos particulares dos objetos que ela modela. Uma instância da classe, como pedro, tem todas as propriedades e métodos da classe.

Encapsulamento

Uma discussão mais completa de escopo e namespaces pode ser lida na seção Variavéis de Classe e de Instância e no artigo seguinte, neste site, Escopos e namespaces.

Encapsulamento é uma forma de trabalhar com uma variável dentro de um bloco, como um objeto ou função, sem tornar aquela variável disponível para o restante do código. Isso pode ser necessário por motivos de segurança ou para realizar uma verificação antes que uma alteração seja feita a essa variável. Por ex., se um campo de CPF vai ser modificado é sempre bom testar se um número válido está sendo inserido.

Se uma propriedade é definida no método de inicialização com dois sublinhados como __variavel ela não pode ser acessada fora do objeto diretamente por objeto.__variavel. Nesse caso métodos devem ser definidos para o seu acesso. Métodos privados, construídos com a mesma técnica, também só podem ser acessados de dentro da classe.


A classe seguinte representa uma porta que pode estar fechada e trancada. Métodos são definidos para abrir, fechar, trancar, destrancar a porta. Todos eles verificam o parâmetro fornecido em relação ao estado da porta para decidir qual é procedimento correto. Por ex., uma porta trancada não pode ser aberta. (Claro que uma classe mais enxuta poderia ser escrita mas seria, provavelmente, um pouco mais díficil de ler. Da mesmo forma o método __geraTexto() está aí apenas para demonstrar o funcionamento da função privada, pois poderia ser incorporado ao __str__().)

» class Porta:
»     def __init__(self, fechada, trancada):
»         self.__fechada = trancada or fechada
»         self.__trancada = trancada
 
»     def abre(self):
»         if self.__trancada:
»             print('A porta está trancada e não pode ser aberta!')
»         else:
»             print('A porta foi aberta!' if self.__fechada else 'A porta já está aberta!')
»             self.__fechada = False
 
»     def fecha(self):
»         print('A porta já está fechada!' if self.__fechada else 'A porta foi fechada!')
»         self.__fechada = True
 
»     def tranca(self):
»         if not self.__fechada:
»             print('A porta está aberta, não pode ser trancada!')
»         else:
»             print('A porta já está trancada!' if self.__trancada else 'A porta foi trancada!')
»             self.__trancada = True        
 
»     def destranca(self):
»         print('A porta foi destrancada!' if self.__trancada else 'A porta já está destrancada!')
»         self.__trancada = False
 
»     def __geraTexto(self):
»         txt = 'A porta está ' + ('fechada' if self.__fechada else 'aberta')
»         txt += (' e trancada' if self.__trancada else ' mas destrancada') if self.__fechada else ''
»         return txt
     
»     def __str__(self):
»         return self.__geraTexto()


Na inicialização a linha self.__fechada = trancada or fechada impede que o estado da porta seja definida como aberta e trancada simultaneamente. Lembrando, foi usada a forma de if else ternário: variavel = valor1 if condicao else valor2, que significa que variavel assume o valor1 se condicao for verdadeira, caso contrário, o valor2.

Observe também que nos métodos, como em destranca(), a mensagem foi gerada antes da troca de valor do estado, pois depende do valor antigo e não no novo.

Agora podemos inicializar um objeto porta e interagir com seus métodos (mas não com seus atributos diretamente). Os atributos privados não podem ser acessados diretamente, nem o método __geraTexto() que só pode ser chamado de dentro da classe, no caso de __str__.

# um atributo privado não pode ser acessado diretamente, nem o método __geraTexto()
» print(porta.__fechada)
↳ AttributeError: 'Porta' object has no attribute '__fechada'

» porta = Porta(True, True)
» print(porta)
↳ A porta está fechada e trancada

» porta.abre()
↳ A porta está trancada e não pode ser aberta!

» porta.destranca()
↳ A porta foi destrancada!

» porta.abre()
↳ A porta foi aberta!

» print(porta)
↳ A porta está aberta

» porta.tranca()
↳ A porta está aberta, não pode ser trancada!

» print(porta)
↳ A porta está aberta

Com encapsulamento conseguimos fazer com que o efeito de um método ou a atribuição de valores às propriedades seja dependente do estado do objeto. É o caso do método porta.abre() que impede que a porta seja aberta se estiver trancada.

A interface de uma classe ou de um módulo são as partes expostas ao usuário do objeto, sejam dados ou métodos.

O encapsulamento favorece a criação de classes que expõe uma interface simples com o código externo (e portanto para o programador), sem a necessidade de exibir possíveis complicações internas. Isso torna esse código mais fácil de reutilizar, em acordo com o princípio DRY do Python (don’t repeat yourself). Ele contribui para a legibilidade do código, pois expõe com clareza o que deve ser fornecido e o que pode ser extraído na classe, impedindo o acesso acidental à informações que não se pretendia tornar acessíveis.

Herdando propriedades e métodos

Além de permitir a criação de objetos como instâncias dessa classe, também é possível construir outras classes herdando e modificando suas propriedades e métodos. Isso é útil quando se quer escrever uma classe que é uma especialização de outra já definida. Quando uma classe herda de outra ela contém todos os métodos e propriedades da primeira classe e pode ter seus atributos alterados e ampliados.

A sintaxe para criar classes derivadas de outra já existente é a seguinte:

# ex. 1: herdando de classe base
» class NomeDaClasseFilha(NomeDaClasseBase):
»     <propriedades e métodos modificados>

# ex. 2: Classe base em outro módulo        
» class NomeDaClasseFilha(Modulo.NomeDaClasseBase):
»     <propriedades e métodos modificados>

Todas as classes herdam de alguma outra. Se NomeDaClasseBase não é especificado a classe herda de object. A classe básica deve estar no mesmo escopo que sua derivada ou, caso contrário, seu módulo deve ser citado, como no exemplo 2.

Na modelagem das pessoas associadas à uma escola encontramos funcionários, professores e alunos. Todos eles partilham características que reuniremos na classe Pessoa.

» class Pessoa(object):
»     def __init__(self, nome, cpf):
»         self.nome=nome
»         self.cpf=cpf
         
»     def __str__(self):
»         return 'Nome: %s; CPF: %s' %(self.nome, self.cpf)
 
» p1 = Pessoa('Ricardo Dalquins','123-456-789-00')
» print(p1)
↳ Nome: Ricardo Dalquins; CPF: 123-456-789-00

As declarações class Pessoa(object):, class Pessoa(): e class Pessoa: são equivalentes.

Os alunos, além de serem pessoas, possuem características especificas como número de matrícula, notas, situação de regularidade quanto às mensalidades, etc. Para demonstrar a criação de uma subclasse usaremos apenas a matrícula.

# definindo uma subclasse    
» class Aluno(Pessoa):
»     def __init__(self, nome, cpf, matricula):
»         super().__init__(nome, cpf)
»         self.matricula = matricula
 
»     def __str__(self):
»         txt =  'Nome: %s \nCPF: %s' %(self.nome, self.cpf)
»         txt += '\nMatricula ' + self.matricula
»         return txt

# inicializando instância da subclasse Aluno
» p2 = Aluno('Uiliam Cheiquispir', '321-654-987-55', '321.654')
 
» print(p2)
↳ Nome: Uiliam Cheiquispir 
↳ CPF: 321-654-987-55
↳ Matricula 321.654

super() é uma função especial que faz a conexão entre a classe mãe e a filha. Ela representa a classe mãe, de onde a classe atual é derivada. Desta forma super().__init__() chama a inicialização de Pessoa e preenche os atribulos nela existentes. O nome super vem da convenção muito usada de chamar a classe mãe de superclasse e a filha de subclasse. A subclasse, por sua vez, pode servir de base para a geração de outra subclasse, abaixo dela. Encontramos também a nomeclatura classe base e classe derivada.

Vamos lembrar que print(p2) aciona o método __str()__ que existe na super e na subclasse. Como ele foi definido na subclasse Aluno este método é executado e o método na superclasse é ignorado. Esse processo, de subreescrever o método da superclasse se chama overriding.

Na prática, quando tentamos modelar um objeto de uso do mundo real, é muito possível que o número de atributos se torne muito grande gerando classes longas e de difícil manuseio. Nesses casos pode ser interessante a criação de classes auxiliares que são usadas como variáveis dentro da classe principal. No caso dos alunos o tratamento de suas notas pode ser gerenciado à parte.

# define uma classe para as notas de um aluno    
» class Nota:
»     def __init__(self, matricula):
»         self.notas = []
»         self.matricula = matricula
»         
»     def insereNota(self,nota):
»         self.notas.append(nota)
»         
»     def getMedia(self):
»         return sum(self.notas)/len(self.notas)
» 
# instancia objeto
» n2 = Nota('321.654')
# insere 3 notas
» n2.insereNota(89)
» n2.insereNota(78)
» n2.insereNota(90)

# acessa propriedade de n2
» n2.getMedia()
↳ 85.66666666666667

É claro que na caso real deve haver um tratamento para evitar erros e conflitos. Se o método Nota.getMedia() for chamado antes da inserção de uma nota um erro será lançado. Isso pode ser contornado capturando essa exceção ou com uma modificação do código.

# modificando o método getMedia()
»     def getMedia(self):
»         if not self.notas:
»             return 0
»         else:
»             return sum(self.notas)/len(self.notas) 

if not self.notas retorna True se a lista estiver vazia. É o mesmo que if len(self.notas)==0 ou simplesmente if len(self.notas) uma vez que if 0 retorna False.

Modificamos agora a classe Aluno para conter uma propriedade Aluno.notas que armazena um objeto Nota que, por sua vez, contém a matricula do alunos e uma lista com suas notas.

# Altera a classe aluno para usar a classe Nota
» class Aluno(Pessoa):
»     def __init__(self, nome, cpf, matricula):
»         super().__init__(nome, cpf)
»         self.matricula = matricula
»         self.notas = Nota(matricula)

»     def __str__(self):
»         txt =  'Nome: %s \nCPF: %s' %(self.nome, self.cpf)
»         txt += '\nMatricula ' + self.matricula
»         txt += '\nMédia de Notas: %.1f' % self.notas.getMedia()
»         return txt

# cria um aluno específico
» bob = Aluno('Roberto Gepeto', '741-852-963-55', '654.987')
# insere suas notas
» bob.notas.insereNota(86)
» bob.notas.insereNota(88)
» bob.notas.insereNota(87)

# acessa bob.__str__() 
» print(bob)
↳ Nome: Roberto Gepeto 
↳ CPF: 741-852-963-55
↳ Matricula 654.987
↳ Média de Notas: 87.0

Na linha bob.notas.insereNota(86) estamos acessando o objeto bob.notas que é uma instância de Nota e que, portanto, possui o método insereNota().

Polimorfismo e Overloading

A palavra polimorfismo significa literalmente “muitas formas”. No Python polimorfismo significa que uma função pode receber parâmetros de tipos diferentes e ainda assim agir como esperado com esses tipos. A função len(), por exemplo, pode receber parâmetros diversos desde que sejam sequências (string, bytes, tupla, lista or range) ou a coleções (dicionário, conjunto, …). Idem, o operador + pode agir sobre objetos de tipos diferentes, gerando resultados diferentes.

# string
» len('Entrou mudo e saiu calado.')
↳ 26
# lista
» len([12,23,34,45,56,67])
↳ 6
# dicionário
» len({1:'1', 2:'2'})
↳ 2

# operador "+"
» 345 + 543
↳ 888
» 'ama' + 'ciante'
↳ 'amaciante'

Classes diferentes podem ter métodos com o mesmo nome. Ao ser chamado em um objeto o método específico de sua classe é executado.

» class Cavalo:
»     def som(self):
»         print('relincha')
 
» class Galinha:
»     def som(self):
»         print('cacareja')
 
» giselda = Galinha()
» corisco = Cavalo()

» print(giselda.som())
↳ cacareja

» print(corisco.som())
↳ relincha       

Overloading de operadores

Operadores pré-definidos em tipos criados pelo usuário, tal como o operador de soma + podem ser sobrescritos, overloaded (ou sobrecarregados).

No exemplo seguinte, definimos uma classe para um “vetor” com dois componentes, (x, y) e a soma de dois vetores, compatível com a soma matemática de vetores, que consiste em somar os componentes x, y, separadamente de cada vetor, e retornar outro vetor (que é instanciado dentro do método __add__().

Para definir essa soma sobreescrevemos o método __add__() que é internamente usado quando se opera com +. Observe que na definição de __add__() nos referimos ao segundo operando da soma como other (outro). Se esta definição não for feita explicitamente a soma entre dois vetores não estará definida.

Também inserimos o overloading do método __eq__ que define como se testa dois objetos para a igualdade, de forma que dois vetores sejam considerados iguais se seus dois componentes são iguais.

# nova definição de vetor
» class Vetor:
»     def __init__(self, x, y):
»         self.x = x
»         self.y = y
     
»     def __add__(self, other):
»         return Vetor(self.x + other.x, self.y + other.y)
 
»     def __eq__(self, other):
»         return self.x == other.x and self.y == other.y
 
»     def __str__(self):
»         return 'Objeto da Classe Vetor: (%d, %d)' % (self.x, self.y)
 
# instanciamos 2 vetores
» v1 = Vetor(34,74)
» v2 = Vetor(16, 26)

# somamos e exibimos a soma
» v3 = v1 + v2
» print(v3)
↳ Objeto da Classe Vetor: (50, 100)

# v3 é uma instância de Vetor
» isinstance(v3, Vetor)
↳ True

# para verificar o teste de igualdade
» v3 = Vetor(3,7)
» v4 = Vetor(3,7)
» print(v3 == v4)
↳ True

A classe Vetor, definida dessa forma, mostra que um objeto pode retornar outro objeto de seu próprio tipo. De fato funções e métodos podem receber e retornar parâmetros que são objetos de qualquer tipo, inclusive os tipos de usuário. A função interna isinstance(objeto, classe) retorna True se objeto é membro da classe, caso contrário retorna False.

Um outro exemplo mostra o overloading de len().

# controle de compras online
» class Compra:
»     def __init__(self, cesta, usuario):
»         self.cesta = cesta
»         self.usuario = usuario
        
»     def __len__(self):
»         return len(self.cesta)

» compra = Compra(['sapato', 'camisa', 'gravata'], 'Pedro Paulo Pizo')
» print('%s possui %d itens em sua cesta' % (compra.usuario, len(compra)))
↳ Pedro Paulo Pizo possui 3 itens em sua cesta

Observe que o “comprimento” de um objeto de Compra não estaria definido se não inseríssemos a definição de __len__() na classe.

Para fazer overloading em uma classe (ou função) definida pelo usuário é necessário testar que tipo de argumento foi passado para a função. No caso de class Ola o método trata diferentemente argumentos passados como None (o que ocorre se o parâmetro for omitido) ou passados como string.

# overloading na classe
» class Ola:
»     def digaOla(self, nome=None):
»         if nome is None:
»             print('Insira o seu nome ')
»         else:
»             print('Olá ' + nome)

» o1 = Ola()
» o1.digaOla()
↳ Insira o seu nome 

» o1.digaOla('Olavo')
↳ Olá Olavo

isinstance() foi usada na classe MontaTexto que espera receber uma string com itens separados por ; ou uma lista. Ela trata cada uma delas de modo diferente. Naturalmente este requisito deveria estar documentado junto à definição da classe.

» class MontaTexto:
»     def montaTexto(self, obj):
»         conta = 0
»         txt = ''
»         if isinstance(obj, str):
»             partes = obj.split(';')
»         elif isinstance(obj, list):
»             partes = obj
»         for item in partes:
»             conta +=1
»             txt += 'Item %d: %s\n' % (conta, item)
»         print(txt)            
    
» txt = ['abóbora', 'brócolis', 'couve', 'alface']
» texto = MontaTexto()
» texto.montaTexto(txt)
↳ Item 1: abóbora
↳ Item 2: brócolis
↳ Item 3: couve
↳ Item 4: alface

» txt = 'lâmpada;fio;alicate;parafuso'
» texto.montaTexto(txt)
↳ Item 1: lâmpada
↳ Item 2: fio
↳ Item 3: alicate
↳ Item 4: parafuso

Polimorfismo

Figura 1

Em termos de classes, polimorfismo signica que podemos, em uma subclasse, usar o mesmo nome de método que já existe na superclasse, alterando seu comportamento. Uma subclasse é sempre um caso particular da superclasse e, portanto, possui características comuns com ela (ou não seria boa ideia herdar da classe mãe). No entanto ela pode modificar as faixas válidas de valores das propriedades e o resultado da atuação de métodos, além de inserir novos atributos. Essa tipo de herança com customização favorece a reutilização de código pronto, alterando classes prontas para especializá-las ao caso desejado.

Considerando as classes herdadas de superclasses, polimorfismo signica que ao chamar um atributo em um objeto da classe 3 (na figura 1) esse atributo será procurado primeiro em (3), depois em (2) e finalmente em (1), e ler ou executar o que encontrar primeiro.

Uma classe pode herdar de mais de uma classe, carregando os atributos de ambas.

# define classes C1 e C2
» class C1:
»     c0='C1.c0'
»     c1='C1.c1'
    
» class C2:
»     c0='C2.c0'
»     c2='C2.c2'
»     c3='C2.c3'
   
# define classe C3 que herda de C1 e C2
» class C3(C1, C2):    
»     c3='C3.c3'
 
# instancia objeto de C3 e imprime suas propriedades
» obj3 = C3()
» print('%s - %s - %s - %s' % (obj3.c0, obj3.c1, obj3.c2, obj3.c3))
↳ C1.c0 - C1.c1 - C2.c2 - C3.c3

Como se vê no resultado impresso, a variável c0, que não existe em C3 foi lida em C1 pois essa classe está inserida antes (à esquerda) de C2.

No código abaixo queremos modelar as pessoas que trabalham em uma empresa. A classe mais geral, Pessoa, possui atributos que todas as pessoas na empresa (ou fora dela) possuem. O único método de nosso exemplo é __str__().

Dessa classe se deriva Funcionario que tem a propriedade extra, salario, e um método, dar_aumento(porcento). Funcionario.__str__() busca a representação de string de super() e acrescenta a informação do salário.

A última classe é um caso especial de funcionário que são os gerentes. A classe Gerente, além de receber os aumentos regulares de todos os funcionários recebe um bônus. O método dar_aumento(porcento, bonus) sobreescreve o método de mesmo nome na superclasse para adicionar um bônus. Para isso ele primeiro dá o aumento regular de todos os funcionários, super().dar_aumento(porcento) (que altera o self.salario) para depois somar a ele o bônus.

# define uma classe geral
» class Pessoa:
»     def __init__(self, nome, cpf):
»         self.nome = nome
»         self.cpf = cpf
»     def __str__(self):
»         return 'Nome: %s\nCPF: %s' % (self.nome, self.cpf)

» p1 = Pessoa('Maria Quiri', '555-444-888-99')

» print(p1)
↳ Nome: Maria Quiri
↳ CPF: 555-444-888-99
 
# define classe especializada de Pessoa
» class Funcionario(Pessoa):
»     def __init__(self, nome, cpf, salario):
»         super().__init__(nome, cpf)
»         self.salario = salario
»     def dar_aumento(self, porcento):
»         self.salario *= (1 + porcento/100)

»     def __str__(self):
»         return '%s \nSalário: %.2f '  % (super().__str__(), self.salario)

» f1 = Funcionario('Joana Darcos', '111-222-333-44', 1000)

» print(f1)
↳ Nome: Joana Darcos
↳ CPF: 111-222-333-44 
↳ Salário: 1000.00

» f1.dar_aumento(10)
» print(f1)
↳ Nome: Joana Darcos
↳ CPF: 111-222-333-44 
↳ Salário: 1100.00 

# define um tipo especial de Funcionario
» class Gerente(Funcionario):
»     def __init__(self, nome, cpf, salario):
»         super().__init__(nome, cpf, salario)
     
»     def dar_aumento(self, porcento, bonus):
»         super().dar_aumento(porcento)
»         self.salario += bonus

» g1 = Gerente('Isaque Nilton','999-888-777-22',2000)

» print(g1)
↳ Nome: Isaque Nilton
↳ CPF: 999-888-777-22 
↳ Salário: 2000.00 

» g1.dar_aumento(10, 800)
» print(g1)
↳ Nome: Isaque Nilton
↳ CPF: 999-888-777-22 
↳ Salário: 3000.00 

Como vemos, classes podem herdar atributos de uma superclasse, alterá-los para seu funcionamento específico ou inserir novos atributos.

Frequentemente esses dados, no caso de uma empresa real, estão gravados em bancos de dados que são lidos e armazenados nas propriedades dessas classes. Também existem formas de gravar o estado de um objeto em disco para reutilização posterior, como veremos.

Importando Classes

Uma vez que as classes estão bem definidas e funcionando adequadamente, temos a opção de armazená-las em módulos e utilizá-las como fazemos com qualquer outra classe importada do Python. Podemos gravar um arquivo com o nome escola.py que contém as definições das classes Pessoa, Aluno e Nota já definidas.

# arquivo escola.py    
» class Pessoa:
»     def __init__(self, nome, cpf):
»         self.nome=nome
»         self.cpf=cpf
        
»     def __str__(self):
»         return 'Nome: %s; CPF: %s' %(self.nome, self.cpf)

# classe aluno
» class Aluno(Pessoa):
»     def __init__(self, nome, cpf, matricula):
»         super().__init__(nome, cpf)
»         self.matricula = matricula
»         self.notas = Nota(matricula)
        
»     def __str__(self):
»         txt =  'Nome: %s \nCPF: %s' %(self.nome, self.cpf)
»         txt += '\nMatricula ' + self.matricula
»         txt += '\nMédia de Notas: %.1f' % self.notas.getMedia()
»         return txt

# classe nota
» class Nota:
»     def __init__(self, matricula):
»         self.notas = []
»         self.matricula = matricula
        
»     def insereNota(self,nota):
»         self.notas.append(nota)

»     def getMedia(self):    
»         if not self.notas:
»             return 0
»         else:
»             return sum(self.notas)/len(self.notas)         

Para usar essas classes temos que importar o módulo e as classes necessárias. Para a importação temos que fornecer o caminho completo de onde está o módulo, caso ele não esteja na pasta em uso ou no PATH do Python.

# importa módulo e classes
from escola import Aluno
# inicializa aluno
» novoAluno = Aluno('Homero Poeta', '234-456-656-56', '346.345')
#insere suas notas
» novoAluno.notas.insereNota(34)
» novoAluno.notas.insereNota(45)
» novoAluno.notas.insereNota(41)

» print(novoAluno)
↳ Nome: Homero Poeta 
↳ CPF: 234-456-656-56
↳ Matricula 346.345
↳ Média de Notas: 40.0

A importação de Aluno tornou disponível Pessoa e Nota que são por ela utilizados, uma vez que estão todas no mesmo escopo, de modo que não é necessário importar as 3 classes.

Você pode importar todos as classes de um módulo simultaneamente de duas formas.

» from escola import *
» novoAluno = escola.Aluno('Homero Poeta', '234-456-656-56', '346.345')

O primeiro método apenas estabelece um caminho de busca para as classes usadas, e não onera o código em termos de memória, durante sua execução. No entanto ele não é recomendado pois é útil poder ver nas primeiras linhas do código as classes que serão usadas. Também, ele pode gerar confusão com os nomes do código e os do módulo caso você importe outro módulo com nomes coincidentes. Isso pode gerar erros difíceis de serem encontrados e corrigidos.

A segunda abordagem é mais interessante: o módulo é importado sem menção às classes ou funções usadas. Em seguida classes e funções são acessadas como componentes desse módulo.

# importamos o modulo que contém classe nomeDaClasse e função fc()
» import modulo

# classes e funções são chamadas
» objeto = modulo.nomeDaClasse
» h = modulo.fc(parametro)


Desta forma os nomes de classes não estarão listados no topo do código mas aparecem claramente no corpo do programa, onde as classes e funções são usadas.

Se o número de classes for muito grande elas podem ser gravadas em arquivos diferentes e importadas dentro do módulo que necessita de outro módulo. Suponha que gravamos Aluno e Pessoa nos arquivos aluno.py e pessoa.py. Como a classe Aluno necessita de Pessoa para sua definição ela deve conter em seu cabeçalho a linha import pessoa from Pessoa.

Gravar os módulos e suas classes em arquivos separados é uma maneira eficaz de manter um código limpo e mais fácil de gerenciar. Cada classe carrega a lógica relativa aquele objeto e o programa central fica encarregado de juntar as partes e executar a lógica principal.


No Jupyter Notebook podemos reinicializar o kernel limpando todas as definições prévias. Para isso selecione menu | Kernel | Restart ou pressione 0, 0 na célula, modo de controle.Além disso podemos escrever o nome na variável seguido de um ponto, e apertar tab. Isso faz com com métodos e propriedades sejam exibidos, como mostrado na figura.

Variavéis de Classe e de Instância

Getters e Setters

Métodos que visam ler o estado de alguma propriedade do objeto, sem realizar alterações, são denominados getters (assessors ou leitores). Já os que alteram propriedades são setters, (mutators ou definidores).

Diferente de outras linguagens POO, variáveis ou métodos no Python não podem ser declaradas como públicas ou privadas, que são aqueles que só podem ser acessados de dentro da classe. Depois de definido em objeto instância de Aluno (da classe já definida) podemos acessar suas propriedades e métodos livremente no objeto. Podemos definir ana.nome='Ana Marta' e recuperar esse nome através de print(ana.nome).

Apesar disso há uma convenção seguida pela maioria dos programadores: um nome (de variável ou método) prefixado com um sublinhado (como _variavel) deve ser tratado como parte não pública daquele bloco de código que pode ser uma função, um método ou uma classe. Além disso um duplo sublinhado (como __variavel) tem o efeito de tornar privada aquela variável. Sublinhados possuem signficados especiais no Python, que já exploraremos.

Como vimos, existe no Python o chamado ocultamento de nome (name muting) que permite a definição de nomes válidos para membros de uma classe privada, o que é útil para evitar conflitos de nomes com outros nomes definidos em subclasses (classes que se baseiam nesta para sua construção). Um identificador (nome de variável ou função) com dois sublinhados iniciais (pode ter um sublinhado final), como por ex. __nome é substituído por _nomeDaClasse.__nome, onde nomeDaClasse é o nome da classe onde está o objeto, com um sublinhado removido. Esse ocultamento é útil para permitir que as subclasses sobrescrevam (override) os métodos sem impedir que os métodos da classe mãe continuem funcionando.

Vejamos o exemplo seguinte para entender como esse duplo sublinhado funciona:

» class Publico:
»     __privadaDeClasse = 66
»     def __init__(self):
»         self.__privadaDeInstancia = 17;

»     def __metodoPrivado(self):
»         print('Dentro do método privado')
»     def metodoPublico(self):
»         print('Essa classe tem uma variável privada com valor: ', self.__privadaDeInstancia)

»     def getPrivada(self):
»         print(self.__privadaDeClasse)

»     def setPrivada(self, k):
»         self.__privadaDeClasse = k

# instancia um objeto da classe
» caso = Publico()

# usando um método público
» caso.metodoPublico()
↳ Essa classe tem uma variável privada com valor:  17

# tentativa de usar um método privado
» caso.metodoPrivado()
↳ AttributeError: 'Publico' object has no attribute 'metodoPrivado'

# tentativa de ler diretamente uma variável privada
» caso.__privadaDeInstancia
↳ AttributeError: 'Publico' object has no attribute '__privadaDeInstancia'

# a variável privada só pode ser acessada de dentro do objeto
» caso.getPrivada()
↳ 66

# para alterar a variável privada usamos método público
» caso.setPrivada(9)
» caso.getPrivada()
↳ 9

A propriedade __privadaDeClasse, além se ser privada, é chamada de variável de classe. Embora ela possa ser alterada após a criação todos os objetos instanciados terão esse valor inicial. Da mesma forma métodos privados só podem ser chamados de dentro do objeto. Dessa forma se impede que partes do código não sejam expostas ao módulo mais geral. Essas partes não aparecem nos chamados à help(), nem nas caixas dropdown do Jupyter Notebook. Ao atribuir um valor a uma variável se pode, por ex., fazer testes de verificação se um tipo correto foi enviado e se o valor está dentro da faixa esperada, enviando mensagens apropriadas de erro, quando for o caso.

A classe a seguir define uma variável quantos que pertence à definição da classe. Ela pode ser alterada e todo objeto instanciado a partir dela terá essa mesma propriedade. Já a variável id pertence à cada objeto individual e não é compartilhada com outros membros da classe.

Essa classe está definida da seguinte forma: a cada nova inicialização, que corresponde à inserção de um novo funcionário, a variável quantos é incrementada de 1, e a variável id copia esse valor. Na criação de novo Funcionario o atributo de classe quantos é alterado para todos os funcionários, enquanto seu id permanece o mesmo.

Observe que, dentro de __init__, a referência foi feita à Funcionario.quantos e self.id respectivamente. Lembrando, self é uma forma de referenciar o objeto instância da classe.

# declare uma classe
» class Funcionario:
»     # atributo de classe
»     quantos = 0
    
»     def __init__(self):
»         Funcionario.quantos += 1
»         self.id = Funcionario.quantos
    
»     def __str__(self):
»         return 'Funcionário %d de %d' % (self.id, Funcionario.quantos) 

A variável quantos é de classe, enquanto id é uma variável de instância.

O método __self__() retorna uma string com o id do funcionário e o número de cadastrados.

# cria um Funcionario
» f1 = Funcionario()
# use print acionar o método __str__ e ver atributos de instância e de classe
» print(f1) 
↳ Funcionário  1 de 1

# novos funcionários
» f2 = Funcionario()
» f3 = Funcionario()

# agora existem 3 funcionários
» print(f2) 
↳ Funcionário  2 de 3

» print(f3) 
↳ Funcionário  3 de 3

# o estado de f1 foi alterado
» print(f1)
↳ Funcionário  1 de 3

# o estado da classe também foi alterado
» Funcionario.quantos
↳ 3

Esse exemplo também ilustra o fato de que uma variável de classe pode ser acessada e modificada de dentro de cada instância. A modificação da classe geradora modifica suas instâncias, o que mostra que a execução de cada instância acessa o codigo da classe.

🔺Início do artigo


Classes, variáveis do Usuário

Bibliografia

Consulte a bibliografia no final do primeiro artigo dessa série.

Python: Arquivos e pastas


Nesse artigo, e em todos os demais no site, usamos as palavras pasta e diretório como sinônimos. A expressão diretório (directory) é mais antiga e foi usada no Unix, no Linux e no Windows/DOS, que mais tarde criou o termo pasta (ou folder).

Quase sempre um programa de computador precisa recuperar dados ou informações previamente existentes, processá-los de algum modo e retornar um resultado que deve ficar armazenado para uso futuro. Esses dados podem ser obtidos de várias formas diferentes: lidos em papel impresso (através de um softaware de OCR), digitados pelo usuário, coletados ne internet ou captados por algum instrumento de medição, entre outras. O resultado final também pode ser impresso ou exibido na tela. Mas, como maior frequência, a informação inicial e final é lida e gravada em disco, em arquivos de texto ou binários, planilhas e bancos de dados (entre outros muitos formatos).

O objeto arquivo (file)

Abrir e ler um arquivo

No Python existem diversas funções no módulo básico para abrir, manipular e escrever em arquivos. Um arquivo aberto é um objeto que pode ser atribuído à uma variável e, por meio dessa variável, as propriedades e métodos do objeto podem ser acessadas. A função open() estabelece uma conexão com o arquivo no disco.

obj = open('caminho_para_arquivo', modo)

Os modos são listados abaixo. Suponha, por exemplo, que existe na pasta de trabalho ativa um arquivo com nome alunos.txt contendo informações sobre alunos de um curso. Seu conteúdo pode ser exibido da seguinte forma:

» objeto_arquivo = open('alunos.txt', 'r')
» texto = objeto_arquivo.read()
» objeto_arquivo.close()
» print(texto)

↳ id, nome     , cidade         , idade , nota
↳ 10, Pedro    , São Paulo      , 34    , 83.0
↳ 11, Maria    , São Paulo      , 23    , 59.0
↳ 12, Janaina  , Rio de Janeiro , 32    , 86.0
↳ 13, Wong     , Brasília       , 43    , 89.0
↳ 14, Roberto  , Salvador       , 38    , 98.0
↳ 15, Marco    , Curitiba       , 31    , 61.0
↳ 16, Paula    , Belo Horizonte , 34    , 44.0

O que é um arquivo no Python? Um arquivo é um conjunto de informações ou dados gravados no dispositivos de armazenamento do computador, como o disco rígido ou um ssd. Ele também pode estar armazenado em memória rom, pronto para a manipulação. O nome do arquivo é uma referência a uma tabela que informa onde estão estes dados. A função interna do Python open() é usada para abrir um arquivo. Ela retorna um objeto arquivo, também chamado de identificador ou handle, usado para ler ou modificar o arquivo. O método read() lê o arquivo e retorna para a variável texto.

Após o uso, o arquivo deve ser fechado com o método close(). O fechamento do objeto libera recursos da máquina e permite que o arquivo seja acessado por outro bloco de código. Em alguns casos pode ocorrer que alterações feitas pelo código não sejam gravadas em disco até que o arquivo seja fechado. Além disso, um problema técnico, como a falta de energia, pode ocasionar em perda de dados.

Estritamente dizendo o mais adequado para lidar com arquivos seria um bloco de código do seguinte tipo.

» try:
»     f = open("data.txt", "w")
»     # código com as operações necessárias usando o arquivo f
» finally:
»     f.close()

Isso evita a possibilidade de que algum erro faça com que o comando de fechamento seja pulado. Uma forma mais compacta de escrever isso, e que evita o problema citado, usa o comando with. No Python a declaração with é usada para manipular exceções e tornar o código mais enxuto e de fácil leitura.

# exemplo de código usando with
» with open('data.txt', 'r') as f:
»     <codigo usando o arquivo f>
»     # (pronto!)

O fechamento nesse caso é automático. O comando with facilita a escrita do código que envolve recursos que devem ser finalizados. Essa finalização significa liberação de recursos de memória e evita que erros desnecessários sejam introduzidos.

Ler linhas de um arquivo

Também é possível ler o arquivo com readlines() que retorna uma lista de linhas que podem ser percorridas depois.

# todas as linhas do arquivo são impressas dessa forma    
» with open('alunos.txt', 'r') as f:
»     linhas = f.readlines()
» for linha in linhas:
»     print(linha, end ='')
# as linhas acima são impressas (mas foram aqui omitidas)

# como readlines() retorna uma lista de linhas ele pode
# ser usado para contar quantas linhas existem no arquivo
» with open('alunos.txt', 'r') as f:
»     print(len(f.readlines()))
↳ 8    

Uma forma alternativa consiste em ler o arquivo uma linha de cada vez, processando as linhas na medida em que são lidas.

» with open('alunos.txt', 'r') as f:
»     print('1-', f.readline(), end='')
»     print('2-', f.readline(), end='')

↳ 1- id, nome     , cidade         , idade , nota
↳ 2- 10, Pedro    , São Paulo      , 34    , 83.0

Uma linha é o texto que termina a cada um sinal de quebra linha.

Quebras de linhas (end of line, EOL) são caracteres especiais inseridos em arquivos para indicar o final de uma linha e o início de outra. Esses caracteres, em geral, não são vistos nos editores de texto mas marcam os finais de cada linha. Alimentação de linha (line feed, LF) e retorno de carro (carriage return, CR) são usados, dependendo do sistema operacional. LF é representado por \n, 0x0A em hexadecimal ou 10 decimal. CR é representado por \r, 0x0D em hexadecimal ou 13 decimal. O Linux (e outros sistemas derivados do UNIX) usa \n. O Windows usa \r\n e o OSX usa \r.

A cada uso do método .readline() uma nova linha é recuperada, até o fim do arquivo. A operação pode ser repetida até que uma string nula seja retornada. Ao final o iterador fica vazio e deve ser lido novamente caso outra iteração seja necessária. Com essa forma de leitura as linhas são lidas uma de cada vez e a requisição de memória é menor.

» with open('alunos.txt', 'r') as f:
»     while True:
»         linha = f.readline()
»         if not linha:
»             break
»         print(linha, end='')

# Observe que readline() é o método default do objeto arquivo.
# Isso significa que o mesmo resultado pode ser obtido com as linhas

» with open('alunos.txt', 'r') as f:
»     for linha in f:
»         print(linha, end='')


Em ambos os casos o arquivo dos alunos é exibido. O teste de final de arquivo pode ser excluído porque o iterador se esgota ao final e o laço for é abandonado.

Observe que a linha if not linha: tem o mesmo efeito que if linha == '':. Ao final da iteração uma string vazia é retornada e strings vazias são avaliadas como False em testes booleanos.

Gravar em um arquivo

No código abaixo um arquivo é aberto para gravação.

» texto = (
»     'Texto a ser gravado em disco\n'
»     'pode conter quantas linhas se desejar\n'
»     'novas linhas são inseridas com \'newline\''
» )
» arquivo = open('teste_gravar.txt', 'w')
» arquivo.write(texto)
» arquivo.close()

# a mesma operação usando with
» with open('teste_gravar.txt', 'w') as arquivo:
»     arquivo.write(texto)

Após a execução dessas linhas um arquivo teste_gravar.txt será encontrado na pasta de trabalho. No texto a ser inserido novas linhas são criadas após o sinal de newline (\n).

Gravação incremental em um arquivo

Para acrescentar linhas ao arquivo, sem apagar as já existentes, usamos o parâmetro ‘a’ (de append):

# para acrescentar um novo aluno ao arquivo alunos.txt:
» novo_aluno = '17, Ana      , Belo Horizonte , 21    , 78.5\n'
» with open('alunos.txt', 'a') as f:
»     f.write(novo_aluno)
# a nova aluna, Ana e seus dados, fica acrescentada ao final do texto.

Observe que write() não insere quebras de linha automaticamente. Por isso terminamos a linha com o sinal de nova linha \n. Dessa forma novo texto inserido posteriormente já se inicia em linha própria.

Outra forma de iterar sobre as linhas de um arquivo consiste em tratar o objeto de arquivo o como um iterador em um laço for. O ex. mostra que readlines() retorna um iterável que pode ser percorrido dentro de laço for:

» with open('alunos.txt', 'r') as f:
»     for linha in f.readlines():
»         print(linha)
# o arquivo inteiro é exibido (mas omitido aqui)

Todo o texto lido em um arquivo dessa forma é uma string, mesmo que composto de dígitos. Se inteiros e números de ponto flutuante estão no texto e precisam ser usados como números, por ex. para um cálculo, eles devem ser convertidos usando-se as conversões int() e float(), respectivamente.

Um exemplo disso segue abaixo. Lembrando que o arquivo alunos.txt tem o conteúdo:

  id, nome     , cidade         , idade , nota
  10, Pedro    , São Paulo      , 34    , 83.0
  11, Maria    , São Paulo      , 23    , 59.0
  12, Janaina  , Rio de Janeiro , 32    , 86.0
  13, Wong     , Brasília       , 43    , 89.0
  14, Roberto  , Salvador       , 38    , 98.0
  15, Marco    , Curitiba       , 31    , 61.0
  16, Paula    , Belo Horizonte , 34    , 44.0

usamos o arquivo dos alunos para ler as idades e as notas, que são convertidas para inteiro e flutuante, respectivamente.

» with open('alunos.txt', 'r') as f:
»     maior, nota_media, n = 0, 0, 0
»     f.readline()
»     for linha in f:
»         palavras = linha.split(',')
»         n +=1
»         nota_media += float(palavras[4])
»         idade = int(palavras[3])
»         if idade > maior:
»             maior = idade
»             nome = palavras[1]
» print('%s é o aluno mais velho, com %d anos.' % (nome.strip(), maior))
» print('A nota média entre %d alunos é %2.1f' % (n, nota_media/n))

↳ Wong é o aluno mais velho, com 43 anos.
↳ A nota média entre 7 alunos é 74.3

O método split(',') quebra a linhas em palavras, partindo cada uma nas vígulas e retornando uma lista. palavra[3] é a idade, palavra[4] a nota. A maior idade e o nome do aluno mais velho são armazenados dentro do teste final.

Propriedades de arquivos

writelines() pode receber uma lista contendo linhas de texto. No exemplo abaixo um novo arquivo com 3 linhas é gravado.

» lista = ['Esta é a linha 1\n',
»          'Esta é a linha 2\n',
»          'Esta é a linha 3'
»         ]
» with open('novo.txt', 'w') as f:
»     f.writelines(lista)
» with open('novo.txt', 'r') as f:
»     print(f.read())

↳ Esta é a linha 1
↳ Esta é a linha 2
↳ Esta é a linha 3

Um objeto de arquivo tem diversas propriedades, entre elas file.name, o nome do arquivo, file.closed que é True se o arquivo está fechado (embora a variável continue apontando para o objeto), e file.mode que contém o modo em que ele foi aberto.

» with open('novo.txt', 'r') as f:
»     info = 'O arquivo "'+ f.name + '" está ' + ('fechado' if f.closed else 'aberto')
»     info += ' no modo %s' % f.mode
»     print(info)
» print('Agora o arquivo está ' + ('fechado' if f.closed else 'aberto'))

↳ O arquivo "novo.txt" está aberto no modo r
↳ Agora o arquivo está fechado

Leitura incremental


A abertura de um arquivo com muitas linhas, ou uma linha muito longa sem quebras pode esgotar os recursos de memória de seu computador. Para evitar esse problema tanto readline ou readlines aceitam um argumento opcional estabelecendo quanto dado deve ser lido de cada vez. É possível ler um arquivo de modo incremental com arquivo.read(n) onde n é o número de bytes lidos (que resulta em um caracter simples). Como a primeira linha contém 16 caracteres (‘Esta é a linha 1’) ela é esgotada em duas iterações.

» with open('novo.txt', 'r') as f:
»     print(f.read(8))
»     print(f.read(8))

↳ Esta é a
↳  linha 1

Após as 2 leituras no código anterior o ponteiro (cursor) está posicionado sobre o byte 17. Essa posição pode ser lida e alterada, usando-se os métodos tell() e seek(). Nas linhas abaixo 17 bytes são lidos e impressos na linha 1. A linha 2 mostra a posição do ponteiro, 18, obtida com seek().

» with open('linhas.txt', 'r') as f:
»     print('1 : ', f.read(17), end='')
»     print('2 : ', f.tell())
»     f.seek(0, 0)
»     print('3 : ', f.read(17), end='')
»     f.seek(36, 0)
»     print('4 : ',  f.read(17), end='')

↳ 1 :  Esta é a linha 1
↳ 2 :  18
↳ 3 :  Esta é a linha 1
↳ 4 :  Esta é a linha 3

No exemplo seguinte a primeira linha é lida inteira e apenas 10 caracteres da segunda linha.

» with open('alunos.txt', 'r') as f:
»     txt1 = f.readline()
»     txt2 = f.readline(10)
» print(txt1, end = '')
» print(txt2)

↳ id, nome     , cidade         , idade , nota
↳ 10, Pedro    , São Paulo

Método open()

Método Descrição
open(arquivo, mode) Abre o arquivo (descrição dos parâmetros abaixo)

Os seguintes parâmetros são usados com open():

Parâmetro Descrição
arquivo caminho completo e nome do arquivo
mode Uma string que define em que modo o arquivo será aberto

O parâmetro mode pode ser:

Parâmetro Descrição
r para leitura – O ponteiro é colocado no início do arquivo. Default.
r+ para leitura e gravação. O ponteiro fica no início do arquivo.
w apenas para gravação. Substitui arquivo existente, cria novo se o arquivo não existir.
w+ escrita e leitura. Substitui arquivo existente, cria novo se arquivo não existir.
rb para leitura em formato binário. O ponteiro fica no início do arquivo.
rb+ para leitura e escrita em formato binário.
wb+ para escrita e leitura em formato binário. Substitui o arquivo existente. Cria novo se não existir.
a para anexar. Conteúdo existente fica inalterado e o ponteiro fica no final do arquivo. Cria novo se não existir.
ab um arquivo para anexar em formato binário. O ponteiro fica no final do arquivo. Cria novo se não existir.
a+ para anexar e ler. O ponteiro fica no final do arquivo, se arquivo existir. Cria novo se não existir.
ab+ anexar e ler em formato binário. O ponteiro no final do arquivo. Cria novo se não existir.
x cria novo arquivo lançando erro se já existir.

São métodos do objeto file

Método Descrição
close() fecha o arquivo; sem efeito se arq. já está fechado
detach() retorna o fluxo bruto separado do buffer
fileno() retorna número inteiro diferente para cada arquivo aberto
flush() descarrega no disco as alterações no buffer
isatty() retorna True se o fluxo de arquivo é interativo
next(arquivo) itera sobre arquivo, lançando erro no final
read() retorna o conteúdo do arquivo
readable() retorna True se o fluxo do arquivo pode ser lido
readline() retorna uma única linha do arquivo
readlines() retorna lista com todas as linhas do arquivo
search() localiza item no arquivo e retorna sua posição
searchable() retorna True se o arquivo é pesquisável, por ex. com seek()
tell() retorna a posição atual do arquivo
truncate([tamanho]) redimensiona (truncando) o arquivo para um tamanho especificado
writable() retorna True se o arquivo pode receber gravações
write() grava a string especificada no arquivo
writelines() escreve uma sequência no arquivo. Qualquer objeto iterável composto por strings pode ser usado

Módulo os


Vimos que existem diversas funções e outros objetos no módulo básico que são carregados juntamente com o próprio Python. No entanto muitos outros módulos podem sem anexados no código, inclusive aqueles da chamada biblioteca padrão. Também existem módulos desenvolvido por terceiros que podem ser baixados e usados. Além disso o usuário pode criar seus próprios módulos e importá-los em seus aplicativos.

De particular interesse para a manipulação de arquivos é o módulo os que contém as funcões básicas de interação com o sistema operacional. Para habilitar o uso de módulos instalados usamos import nome_do_modulo.

Listar pastas e arquivos

Por exemplo, após a importação de os podemos usar o método os.listdir(pasta) que retorna uma lista com o conteúdo da pasta especificada.

» import os
» pasta ='/home/guilherme/Projetos/Python'
» lista_arquivos = os.listdir(pasta)
» print(lista_arquivos)
↳ ['arquivos', 'wallpaper.py', 'turtle.py', 'numpy_02.py', 'NLP', 'output', 'osDir.py',
↳  'lerShelfFile.py', 'guessNumber.py', ...]

Embora esse método ainda esteja disponível, no Python 3.5 foi inserido uma nova forma de obter esse resultado por meio de os.scandir() que retorna um iterável. Os elementos desse iterável possuem diversas propriedades, entre elas name, usada para recuperar o nome do arquivo ou pasta.

» arquivos = os.scandir(pasta)
» for arquivo in arquivos:
»     print(arquivo.name)
↳ wallpaper.py
↳ turtle.py
↳ numpy_02.py
↳ NLP
↳ output
↳ osDir.py
↳ ...

Observação (Windows): Como o caracter \ tem significado especial (escape) no Python é necessário escrever caminhos no Windows de uma forma especial, de uma das duas formas:

» caminho = r'C:\Windows\Temp'
# ou
» caminho = 'C:\\Windows\\Temp'

No código abaixo usamos os.path.join(local, t) que faz a concatenação correta do caminho com o nome do arquivo, agindo de forma diferente de acordo com o sistema operacional. O método os.path.split age de forma inversa, retornando uma tupla que contém a caminho e o nome do arquivo. Depois de extraído o nome do arquivo podemos obter uma tupla com nome separado da extensão usando os.path.splitext.

# como funciona os.path.join (no linux ou OSX)
» os.path.join('caminho_completo', 'nome_do_arquivo')
↳ 'caminho_completo/nome_do_arquivo'

# separando caminho do arquivo
» os.path.split('/home/usuario/Documentos/Artigo.txt')
↳ ('/home/usuario/Documentos', 'Artigo.txt')

# separando a extensão
» os.path.splitext('image.jpeg')
↳ ('image', '.jpeg')

# no windows uma string diferente seria retornada com join
» os.path.join('caminho_completo', 'nome_do_arquivo')
↳ 'caminho_completo\\nome_do_arquivo'

Filtrar pastas e arquivos

Para investigar se o elemento retornado por listdir é um arquivo podemos usar os.path.isfile(), e os.path.isdir() para pastas (diretórios).

# para filtrar os arquivos
» local = '.'
» for t in os.listdir(local):
»     if os.path.isfile(os.path.join(local, t)):
»         print(t)
↳ arquivo001.txt
↳ arquivo002.txt
↳ jupyter001.ipynb
↳ teste.csv

# para filtrar as pastas
» for t in os.listdir(local):
»     if os.path.isdir(os.path.join(local, t)):
»         print(t)
↳ .ipynb_checkpoints
↳ dados

# para listar arquivos dentro da pasta 'dados'
» local = './dados'
» for t in os.listdir(local):
»     if os.path.isfile(os.path.join(local, t)):
»         print(t)
↳ nums.csv
↳ alunos.pkl

A mesma operação feita usando os.scandir()

# usando scandir
» local = '.'
» with os.scandir(local) as arqs:
»     for arq in arqs:
»         if arq.is_file():
»             print('arquivo:', arq.name)
»         elif arq.is_dir():
»             print('pasta:  ', arq.name)
↳ arquivo: pandas001.ipynb
↳ arquivo: python001.ipynb
↳ arquivo: teste_gravar
↳ arquivo: alunos.txt
↳ arquivo: teste_novo.csv
↳ pasta:   .ipynb_checkpoints
↳ arquivo: teste.py
↳ arquivo: alunos.csv
↳ pasta:   dados
↳ ...            

Claro que essa mesma lista pode ser obtida através de uma compreensão de lista, o que ilustra mais uma vez o poder de concisão dessa construção.

# para arquivos
» lst_arquivos = [t.name for t in os.scandir(local) if t.is_file()]
» lst_arquivos
↳ [pandas001.ipynb, python001.ipynb, teste_gravar, alunos.txt,
↳  teste_novo.csv, teste.py, alunos.csv]

# para as pastas
» [t.name for t in os.scandir(local) if t.is_dir()]
↳ ['.ipynb_checkpoints', 'dados']

A recuperação dos nomes dos arquivos, associada a testes de string com esses nomes, permite uma filtragem mais específica de arquivos retornados. No exemplo abaixo usamos .endswith para verificar se os arquivos possuem uma extensão determinada (no caso ‘.csv’). No segundo exemplo usamos compreensão de lista e testamos a substring após o último ponto.

# usando .endswith()
» print('Arquivos com extensão csv:')
» with os.scandir(local) as arqs:
»     for arq in arqs:
»         if arq.is_file() and  arq.name.endswith('.csv'):
»             print(arq.name)
Arquivos com extensão csv:
↳ teste_novo.csv
↳ alunos.csv

# alternativamente, usando compreensão de lista e .split('.')
» [arq.name for arq in os.scandir(local) if arq.is_file() if arq.name.split('.')[-1]=='txt']
↳ ['alunos.txt']

O método os.scandir() não recupera apenas os nomes dos arquivos. Cada objeto do ScandirIterator possue o método .stat() que retorna informações sobre o arquivo ou pasta ao qual se refere, como tamanho do arquivo. O atributo st_mtime, por ex., contém a hora da última alteração.

No exemplo seguinte fazemos uma iteração sobre os arquivos e pastas em local e imprimimos a data de sua última alteração.

# atributo de arquivos: tempo desde última alteração
» with os.scandir(local) as arquivos:
»     for arq in arquivos:
»         info = arq.stat()
»         print(info.st_mtime)
↳ 1612654770.8688447
↳ 1615833776.4532673
↳ 1611327215.6848917
↳ ...

O retorno é dado em segundos desde a época, uma medida de tempo também chamada de Unix time que consiste no número de segundos decorridos desde Zero Horas do dia 1 de Janeiro de 1970 (UTC, longitude 0°) e pode ser convertido em uma data mais legível através de diversas funções de tratamento de datas e horas.

No código seguinte lemos os arquivos na pasta local e retornamos seus nomes e datas de última modificação, convertidos em forma mais legível usando uma função do módulo datetime. Veremos mais tarde outras funcionalidades desse módulo.

» from datetime import datetime

» def converter_data(timestamp):
»     d = datetime.utcfromtimestamp(timestamp)
»     return d.strftime('%d/%m/%Y')

» def ler_arquivos():
»     arquivos = os.scandir(local)
»     for arq in arquivos:
»         if arq.is_file():
»             info = arq.stat()
»             print(f'{arq.name}\t\t Modificado em: {converter_data(info.st_mtime)}')

↳ Pandas001.ipynb      Modificado em: 06/02/2021
↳ Python001.ipynb      Modificado em: 15/03/2021
↳ teste_gravar         Modificado em: 15/03/2021
↳ alunos.txt           Modificado em: 16/03/2021
↳ teste_novo.csv       Modificado em: 16/11/2020
↳ ...

Os argumentos passados para strftime() são: %d o dia do mês, %m o número do mês e %Y o ano, com 4 dígitos. Várias outras formatações são possíveis.

O módulo os contém os métodos os.getcwd() para ler a pasta ativa no momento, e os.chdir(‘nova_pasta’) para trocar para uma nova pasta.

» import os
» os.getcwd()
↳ '/home/guilherme/Projetos/Phylos.net'

» os.chdir('/home/guilherme/Music')
» os.getcwd()
↳ '/home/guilherme/Music'

Também podemos criar novas pastas. Para isso usamos os.mkdir() (cria uma pasta) e os.makedirs() (cria várias pastas). Podemos criar, dentro da pasta atual, a pasta exemplo e exemplo/textos.

» import os
» os.mkdir('exemplo')
» os.mkdir('exemplo/textos')

# a tentativa de criar uma pasta existente resulta em erro
» os.mkdir('exemplo')
↳ FileExistsError: [Errno 17] File exists: 'exemplo'
Figura 1

Várias pastas e subpastas podem ser criadas simultaneamente. O comando abaixo cria três pastas.

» os.makedirs('2021/03/22')

A estrutura de pastas criada na pasta atual é ilustrada na figura 1. Os seguintes comandos ilustram operações feitas com métodos de os:

# armazena pasta de trabalho atual, antes da modificação
» pasta_trabalho = os.getcwd()
# altera pasta atual para outra
» os.chdir('/home/guilherme/Music')
# cria subpastas na pasta atual (resultado semelhante ao da figura 1)
» os.makedirs('subpasta10/subpasta11/subpasta12')

# retorna para a pasta de trabalho
» os.chdir(pasta_trabalho)

# cria sub pasta
» os.mkdir('teste')

# muda pasta atual ('.' é atalho para pasta atual)
» os.chdir('./teste')
# verifica qual é a pasta atual
» os.getcwd()
↳ '/home/guilherme/Projetos/Artigos/teste'

# volta para pasta no nível acima ('..' é atalho para pasta 'mãe')
» os.chdir('..')

# renomeia pasta
» os.rename('teste', 'novo')

# os.remove só apaga arquivos
» os.remove('novo') # só pode apagar arquivo
↳ IsADirectoryError: [Errno 21] Is a directory: 'novo'

» os.remove('aluno.txt') # só pode apagar arquivo
# o arquivo 'aluno.txt' foi apagado (se existe)

# para apagar uma pasta use rmdir
» os.rmdir('novo')
# a pasta 'novo' foi apagada

O método os.remove(arquivo) lança um erro se o arquivo não existe ou se é uma pasta. Para evitar essa possibilidade usamos um try ou testamos previamente o arquivo. Para apagar pastas e subpastas podemos percorrer cada uma delas (veja método abaixo) ou usar o módulo shutil.

» arquivo_apagar = 'home/data.txt'
# testa a existência da arquivo
» if os.path.isfile(arquivo_apagar):
»     os.remove(arquivo_apagar)
» else:
»     print(f'O arquivo: {data_file} não existe ou é uma pasta')

Para apagar uma pasta podemos usar os.rmdir() ou shutil.rmtree().

» apagar_pasta = 'documentos/pasta'
» try:
»     os.rmdir(apagar_pasta)
» except OSError as e:
»     print(f'Error: {apagar_pasta} : {e.strerror}')

# apagar pastas e subpastas, mesmo que não estejam vazias
» import shutil
» apagar_pasta = 'documentos/pasta'
» try:
»     shutil.rmtree(apagar_pasta)
» except OSError as e:
»     print(f'Error: {apagar_pasta} : {e.strerror}')  

Percorrendo a árvore de pastas

Não é rara a necessidade de percorrer as pastas e subpastas em uma estrutura de diretórios, eventualmente executando uma tarefa nos arquivos no disco ou fazendo buscas. O método os.walk() pode auxiliar nessa tarefa, percorrendo a árvore tanto da pasta raiz para as subpastas (de cima para baixo, top down) quanto no sentido inverso (bottom up). Por default os.walk(pasta) faz várias iterações em pasta e subpastas, até esgotar a árvore, de mãe para filhos. Em cada iteração retorna:

Figura 2
  • uma string com o nome da pasta atual
  • uma lista de suas subpastas
  • uma lista dos arquivos da pasta atual

Depois a iteração passa para uma subpasta (no modo de cima para baixo) ou para a pasta mãe. Para percorrer a árvore no sentido de baixo para cima usamos o parâmetro os.walk(pasta, topdown=False). No código que se segue percorremos a árvore (lembrando que ‘.’ simboliza pasta ativa atual). O teste if not sub_pastas resulta True se a lista está vazia. O resultado exibido supõe uma estrutura de pastas como na figura 2.

# usando os.walk() para listar arquivos e pastas
» for pasta, sub_pastas, arquivos in os.walk('.'):
»     if not sub_pastas:
»         print('A pasta:', pasta, 'não possui subpastas')
»     else:    
»         print('A pasta:', pasta, 'possui as subpastas:')
»         for sub in sub_pastas:
»             print('\t\t',sub)
»     if not arquivos:
»         print('\t não possui arquivos')
»     else:    
»         print('\t e os arquivos:')    
»         for nome_arquivo in arquivos:
»             print('\t\t', nome_arquivo)

↳ A pasta: . possui as subpastas:
↳        pasta_1
↳        pasta_2
↳    e os arquivos:
↳        texto.txt
↳ A pasta: ./pasta_1 não possui subpastas.
↳    e os arquivos:
↳        arquivo1.py
↳        arquivo2.py         
↳        arquivo3.py
↳ A pasta: ./pasta_2 não possui subpastas.
↳    e os arquivos:
↳        arquivo4.py
↳        arquivo5.py         
↳        arquivo6.py

Informações do sistema operacional

O módulo os possui muitos outros métodos. Por exemplo, os.uname() retorna o sistema operacional, nome de usuário e dados da versão do sistema usado.

» for t in os.uname():
»     print(t)
↳ Linux
↳ guilherme-Lenovo
↳ 5.8.0-45-generic
↳ #51-Ubuntu SMP Fri Feb 19 13:24:51 UTC 2021
↳ x86_64

O módulo os possui algumas propriedades úteis, entre elas os.environ, que contém informação sobre o sistema operacional e seu ambiente (environment), armazenadas em um dicionário.

» os.system
↳ <function posix.system(command)>
        
» os.environ
↳ environ{'QT_SCALE_FACTOR': '1',
        'LANGUAGE': 'en_US',
        'SHELL': '/bin/bash',
        'LC_NUMERIC': 'pt_BR.UTF-8',
        'LC_PAPER': 'pt_BR.UTF-8',
        'MATE_DESKTOP_SESSION_ID': 'this-is-deprecated',
        'JPY_PARENT_PID': '9099',
        'TERM': 'xterm-color',
        ... [truncado]
        'MPLBACKEND': 'module://ipykernel.pylab.backend_inline'}

» # essa propriedade é um tipo dicionário. por ex.:
» os.environ['USER']
↳ guilherme

Os seguintes testes foram realizados no prompt do python, em sessão do Anaconda.

>>> import os
>>> os.times()
↳ posix.times_result(user=0.03, system=0.0, children_user=0.0, children_system=0.0, elapsed=17180795.37)
>>> os.system('date')
↳ sáb 05 jun 2021 14:19:27 -03

# supondo a existência de um programa (ou atalho) de nome caja
# o aplicativo caja é executado (no caso um gerenciador de arquivos)
>>> os.system('caja')

O método os.walk(top, topdown=True, onerror=None, followlinks=False) retorna tuplas com pasta atual, subpastas e arquivos nelas contidos. Por default ele percorre as pastas iniciando em top e descendo para as substastas nele localizadas.

» import os
» path = '/home/guilherme/Temp'
» for (root, dirs, files) in os.walk(path):
»     print('Pasta atual: %s' % root)
»     print('Com as subpastas:\n', dirs)
»     print('Arquivos presentes:\n', files)
»     print('\n--------------------------------\n')

# saida (truncada)
↳ Pasta atual: /home/guilherme/Temp
↳ Com as subpastas:
↳  ['RevelationSpace', 'pandas']
↳ Arquivos presentes:
↳  ['Livro1', 'modal.html', 'FileZilla.xml']
↳ 
↳ Pasta atual: /home/guilherme/Temp/RevelationSpace
↳ Com as subpastas:
↳  []
↳ Arquivos presentes:
↳  ['01-RevealSpace.mp3', '02-RevealSpace.mp3', '03-RevealSpace.mp3', '04-RevealSpace.mp3', '05-RevealSpace.mp3']
↳ 
↳ Pasta atual: /home/guilherme/Temp/pandas
↳ Com as subpastas:
↳  ['Lambda', 'Pandas']
↳ Arquivos presentes:
↳  ['Lambda.html', 'Pandas.html']

Métodos de OS

Segue uma lista de alguns dos métodos do módulo os:

Método significado
chdir(“novaPasta”) muda a pasta ativa para novaPasta. Se novaPasta = “..” vai para pasta mãe
os.environ() obtenha o ambiente dos usuários,
os.getcwd() retorna pasta de trabalho atual,
os.getgid() retorna id de grupo real do processo atual,
os.getuid() retorna ID do usuário do processo atual,
os.getpid() retorna ID de processo real do processo atual,
os.umask(mascara) define o umask numérico atual e retorne o umask anterior,
os.uname() retorna informações que identificam o sistema operacional atual,
os.chroot(caminho) altera pasta raiz do processo atual para caminho,
os.listdir(caminho) retorna lista de arquivos e pastas no caminho,
os.mkdir(caminho) cria pasta caminho,
os.makedirs(sequencia) criação recursiva de pastas na sequencia,
os.remove(caminho_arquivo) remove arquivo em caminho_arquivo,
os.removedirs(caminho) remove pastas recursivamente,
os.rename(‘nomeAntigo’,’nomeNovo’) renomeia arquivo/pasta ‘nomeAntigo’ para ‘nomeNovo’dst,
os.rmdir(caminho) remove pasta em caminho,
os.system(comando) executa comando na shell do sistema operacional,
os.uname() retorna dados sobre o sistema operacional, usuário e máquina usada
os.walk(caminho) retorna tuplas com pasta, subpastas e arquivos em cada uma.
🔺Início do artigo


Classes, variáveis do Usuário

Bibliografia

Consulte a bibliografia no final do primeiro artigo dessa série.

Python: Compreensão de listas


Sobre tuplas e Zips

Algumas propriedades extras sobre tuplas são listadas aqui. Além disso usamos a fução zip() para unir objetos iteráveis de modo a poderem ser percorridos simultaneamente.

# Uma tupla não precisa de parenteses
» t = 1, 2, 3, 4
» t
↳ (1, 2, 3, 4)
» type(t)
↳ tuple
# podemos usar tuplas para declarações múltiplas
» x, y = 45, 78
» print(x,y, x+y)
↳ 45 78 123
# e percorrer uma tupla (ou lista) em um loop for
» t = [('a', 0, True), ('b', 1, False), ('c', 2, True)]
» for letra, num, bool in t:
»     if bool: print (num, letra)
↳ 0 a
↳ 2 c

# qualquer sequência pode ser usada no construtor de tuplas
» t = tuple('palavras')
» t
↳ ('p', 'a', 'l', 'a', 'v', 'r', 'a', 's')

# uma tupla de 1 elemento deve conter (,)
» t = ('a',)
» t
↳ ('a',)
» type(t)
↳ tuple

# tuplas para 'swap' de variáveis
» a = 'both'
» b = 'ambos'
print(a,b)
↳ both ambos
» a, b = b, a
» print(a,b)
↳ ambos both

Funções retornam uma variável ou None. No entanto essa variável pode ser uma tupla, um dicionário ou outro objeto qualquer. Dessa forma podemos ter funções que retornam mais de um valor. Por exemplo, divmod(a,b) retorna uma tupla com o resultado inteiro da divisão e o resto. Funções do usuário podem fazer o mesmo, inclusive retornando outra função, como veremos.

» divmod(10,3)
↳ (3, 1)

» div, resto = divmod(10,3)
» print(div)
↳ 3

» print(resto)
↳ 1

# A função abaixo recebe uma sequência e retorna uma tupla com seu mínimo e máximo
» def min_max(seq):
»     return min(seq), max(seq)
»
» seq = (23,45,23,78,1,23,0,-34)
» min_max(seq)
↳ (-34, 78)

» seq = {23,45,23,78,1,23,0,-34}
» min_max(seq)
↳ (-34, 78)

# funções com n argumentos
» def imprima_tudo(*args):
»     print(args)

» imprima_tudo(1,2, '3', True)
↳ (1, 2, '3', True)

# sem * temos um erro
» def imprima(args):
»     print(args)
» imprima(1,2)
↳
---------------------------------------------------------------------------
TypeError: imprima() takes 1 positional argument but 2 were given

Função zip()

Uma função interna interessante e útil é zip(). Ela recebe como argumentos duas ou mais sequências e as compacta em um objeto zip que é um iterador de tuplas, onde cada tupla contem um elemento de cada sequência usada como parâmetro. No objeto retornado a i-ésima tupla contém o i-ésimo elemento de cada uma das sequências ou iteráveis fornecidos em seu argumento. Se as sequências não tem o mesmo comprimento o iterador retornado tem o comprimento da menor. Se nenhum argumento for fornecido ela retorna um iterador vazio. Com um único argumento iterável, ele retorna um iterador de tuplas de um elemento.

» seq1 = 'Blade'
» seq2 = 'Runner'
» zipado = zip(seq1, seq2)
» print(zipado)
↳ <zip object at 0x7f27c9f77500>

# Para visualizar esse objeto zip (um iterador) podemos transform-a-lo em uma tupla
» print(tuple(zipado))
↳ (('B', 'R'), ('l', 'u'), ('a', 'n'), ('d', 'n'), ('e', 'e'))
# ou percorrê-lo em seus elementos
» for letra1, letra2 in zipado:
»     print(letra1, letra2)
↳ B R
↳ l u
↳ a n
↳ d n
↳ e e

# um objeto zip pode ser passado como argumento na construção
# de uma lista gerando uma lista de tuplas
» s1 = [1, 2, 3]
» s2 = ['a', 'b', 'c']
» list(zip(s1, s2))
↳ [(1, 'a'), (2, 'b'), (3, 'c')]

# várias sequências podem ser fornecidas
» s1 = 'Margem'
» s2 = 'direita'
» s3 = 'do rio'
» zip123 = zip(s1, s2, s3)
» for a,b,c in zip123:
»     print(a, b, c)
↳ M d d
↳ a i o
↳ r r
↳ g e r
↳ e i i
↳ m t o

No útimo exemplo a sequência s2 tem maior comprimento que as demais. A letra final ‘a’ foi ignorada.

Suponha que desejamos analisar duas sequências e verificar se elas possuem elementos iguais em uma mesma posição. No exemplo abaixo fazemos este teste usando zip() e definindo uma função que retorna True se houver uma coincidência. Observe que a função é abandonada quando ocorre a primeira coincidência.

Pode ocorrer que precisamos saber qual é a posição desse (ou de vários elementos comuns). Para isso usamos enumerate(). Ela tem a seguinte forma:

enumerate(iteravel, inicio=0)

onde iteravel é uma sequência ou qualquer objeto iterável. Ela retorna pares iteráveis contendo o elemento e sua posição, começando em início (com default 0) e o elemento da sequência.

# função que retorna True se houver coincidência de elementos (ou False se não houver)
» def mesmo_elemento(t1, t2):
» for i, j in zip(t1, t2):
»     if i == j:
»         return True
» return False

» t1 = [1,2,3,4,5,6,7,8,9]
» t2 = [9,8,7,6,5,4,3,2,1]
» mesmo_elemento(t1,t2)
↳ True

# Para informar a posição da coincidência usamos enumerate
» for index, element in enumerate('abc'):
»     print(index, element)
↳ 0 a
↳ 1 b
↳ 2 c

» type(enumerate('abdc'))
↳ enumerate

# Repetindo o exemplo anterior, queremos descobrir se duas sequências
# possuem elementos comuns na mesma posição, e que posição é essa

» def posicao_elemento(t1, t2):
»     ''' retorna dictionary com indice e elemento onde elemento de t1 é igual ao de t2 '''
»     dict = {}
»     if len(t2) < len(t1):
»         (t1, t2) = (t2, t1)
»     for index, i in enumerate(t1):
»         if i == t2[index]:
»             dict[index] = i
»     return dict

» t1 = [9,8,7,4,3,4,5,6,7,8,9,0,23,45]
» t2 = [1,2,3,4,5,6,7,6,9]
» posicao_elemento(t1,t2)
↳ {3: 4, 7: 6}

» t1 = 'josefina'
» t2 = 'gasolina cara'
» posicao_elemento(t1,t2)
↳ {2: 's', 5: 'i', 6: 'n', 7: 'a'}

As linhas if len(t2) < len(t1): (t1, t2) = (t2, t1) garante que t2 tenha o maior comprimento. Caso contrário o teste i == t2[index] poderia poderia resultar em erro ao tentar obter elemento inexistente de t2.

Compreensão de Listas (List Comprehensions)


Suponha que queremos uma lista com os quadrados de todos os números de 0 até 10. Podemos conseguir isso usando um laço for:

» quadrados = []
» for i in range(11):
»     quadrados.append(i**2)
»
» quadrados
↳ [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Há uma forma alternativa no Python para conseguir o mesmo resultado chamada compreensão de listas (list comprehensions). Sua sintaxe básica é

[expressão(item) for item in lista if condição].

Com isso conseguimos o mesmo resultado acima de forma mais compacta:

» quads = [i**2 for i in range(11)]
» quads
↳ [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Essa forma permite que se escreva um código mais compacto, legível e eficiente (portanto de mais rápida execução). A expressão pode ser uma função do usuário. No exemplo usamos a função já definida anteriormente para o cálculo de fatoriais:

» def fatorial(n):
»     if n <= 1: return 1
»     else: return n*fatorial(n-1)
»
# exibindo os fatoriais de ímpares de 1 a 9
» fats = [fatorial(i) for i in [1, 3, 5, 7, 9]]
» fats
↳ [1, 6, 120, 5040, 362880]

# alternativamente, usando a condicional
» ft = [fatorial(i) for i in range(10) if i %2 ==1]
» ft
↳ [1, 6, 120, 5040, 362880]

# atuando sobre strings
» palavras = ['Organização', 'Nações', 'Unidas']
» letras = [l[0] for l in palavras]
» letras
↳ ['O', 'N', 'U']

» tamanho = [len(p) for p in palavras]
» tamanho
↳ [11, 6, 6]

Uma ou mais condições pode ser incluída na lista a ser iterada.

[expressao(item) for item in lista if condição1 if condição2 ...]

No exemplo abaixo usamos i%2, que é o resto da divisão de i por 2. Se esse resto for 0 o número é par.

# ex.: lista dos números pares de 0 até 10, exclusive
# i%2 == 0 significa que o número é par
» num = [i for i in range(10) if i%2==0 ]
» print(num)
↳ [0, 2, 4, 6, 8]

# um exemplo com strings: apenas as palavras com comprimento maior que 5
» palavras = ['casa', 'aleatório', 'rudimentar', 'longo']
» [p for p in palavras if len(p) > 5 ]
↳ ['aleatório', 'rudimentar']

# Várias condições podem ser impostas simultaneamente
# São retornados os múltiplos de 2 e 3 simultaneamente, i.e. os múltiplos de 6.
» print([i for i in range(50) if i%2==0 if i%3==0])
↳ [0, 6, 12, 18, 24, 30, 36, 42, 48]

# a expressão na lista pode conter 'if else':
# a abaixo expressão retorna 'abacaxi' para números pares,
# 'laranja' para múltiplos de 3 e 'caqui' para todos os demais
» fruits = ['abacaxi' if i%2==0 else 'laranja' if i%3==0 else 'caqui' for i in range(10)]
» print(fruits)
↳ ['abacaxi', 'caqui', 'abacaxi', 'laranja', 'abacaxi', 'caqui', 'abacaxi', 'caqui', 'abacaxi', 'laranja']

# As listas podem ser aninhadas. Abaixo a lista externa percorre letras,
# a lista interna percorre números
» matriz = [[i+j for i in 'abcd'] for j in '1234']
» matriz
↳ [['a1', 'b1', 'c1', 'd1'],
   ['a2', 'b2', 'c2', 'd2'],
   ['a3', 'b3', 'c3', 'd3'],
   ['a4', 'b4', 'c4', 'd4']]

Um teste pode ser negado com o operador lógico not (que inverte o booleano). Usamos também o método string.isalpha() que retorna True se string for formado apenas de caracteres (sem dígitos ou pontuações).

# teste not in
# isalpha() retorna True se a string é composta de caracteres alfabéticos
» vogais = {'a', 'e', 'i', 'o', 'u'}
» texto = 'Aprenda todas as regras e transgrida algumas 1234.'
» letras = set(texto.lower())
» consoantes = {letra for letra in letras if letra not in vogais if letra.isalpha()}
» consoantes
↳ {'d', 'g', 'l', 'm', 'n', 'p', 'r', 's', 't'}

# na expressão seguinte um conjunto (set) de tuplas é gerado
{(i, j) for i in range(3, 5) for j in range(2)}
↳ {(3, 0), (3, 1), (4, 0), (4, 1)}

» print([i+j for i in 'abcd' for j in 'efgh'])
↳ ['ae', 'af', 'ag', 'ah', 'be', 'bf', 'bg', 'bh', 'ce', 'cf', 'cg', 'ch', 'de', 'df', 'dg', 'dh']

» substantivo = ['pão', 'pé', 'carro', 'bolo', 'mato']
» adjetivo = ['pequeno', 'bonito', 'bom', 'caro']
» ['%s %s' % (s, a) for s in substantivo for a in adjetivo if s[0] == a[0]]
↳ ['pão pequeno', 'pé pequeno', 'carro caro', 'bolo bonito', 'bolo bom']

# Podemos calcular o produto escalar de 2 vetores
» u = [1,3,4,5,6]
» v = [-1,0,9,-3,2]
» sum([a+b for a,b in zip(u,v)])
↳ 26

No cálculo do produto escalar zip(u,v) contém 5 pares que são somados formando uma lista de 5 elementos. A função sum(lista) soma esses 5 elementos.

Compreensão com dicionários

Compreensão de listas podem ser aplicadas a dicionários, alterando tanto suas chaves como valores. Lembrando dic.items() retorna uma tupla chave:valor do dicionário.

# uma compreensão para duplicar os valores em um dicionário
» dic1 = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
» duplo_dic1 = {k:v*2 for (k,v) in dic1.items()}
» duplo_dic1
↳ {'a': 2, 'b': 4, 'c': 6, 'd': 8, 'e': 10}

# alterar o texto nas chaves, acrescentando prefixo R
» R_dic1 = {'R'+k : v*2 for (k,v) in dic1.items()}
» R_dic1
↳ {'Ra': 2, 'Rb': 4, 'Rc': 6, 'Rd': 8, 'Re': 10}

» s_dic = {l.upper(): l*3 for l in 'dna'}
» print (s_dic)
↳ {'D': 'ddd', 'N': 'nnn', 'A': 'aaa'}

No exemplo seguinte queremos construir um dicionário onde a chave é um par entre 1 e 10, inclusive, e o valor é seu quadrado. Fazemos isso sem compreensão de lista e depois com.

# Obs. percorrer i = 1, ..., 5 e tomar seu dobro garante que usamos apenas os pares de 1 a 10
» dic_2 = {}
» for i in range(1,6):
»     dic_2[i*2] = (i*2)**2
» dic_2
↳ {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

# a mesma construção, usando compreensão
» dic_3 = {n:n**2 for n in range(1,11) if n%2 == 0}
» dic_3
↳ {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

# o mesmo resultado será obtido com
» dic_4 = {2*n:(2*n)**2 for n in range(1,6)}
» dic_4
↳ {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

# Podemos usar compreensões para filtrar elementos de um dicionário
» dic_4a = {k:v for (k,v) in dic_4.items() if v>36}
» dic_4a
↳ {8: 64, 10: 100}

» # selecionando apenas valores em um intervalo (2 condições)
» dic_4b = {k:v for (k,v) in dic_4.items() if v > 16 if v < 100}
» dic_4b
↳ {6: 36, 8: 64}

# duas condições, sobre chave e valor
» dic_4c = {k:v for (k,v) in dic_4.items() if k > 5 if v < 90}
» dic_4c
↳ {6: 36, 8: 64}

# o valor é (im)par para chave (im)par
» dic_par_impar = {i:('par' if i%2==0 else 'impar') for i in range(5)}
» dic_par_impar
↳ {0: 'par', 1: 'impar', 2: 'par', 3: 'impar', 4: 'par'}

Algumas vezes pode ser útil usar zip() para construir dicionários através de uma compreensão.

# Lists to represent keys and values
» keys = [101, 201, 301, 401, 501]
» values = ['inglês', 'francês', 'alemão', 'russo', 'espanhol']

# keys e values são colocados em um único iterável (um objeto zip)
» dic_5 = { k:v for (k,v) in zip(keys, values)}
» print(dic_5)
↳ {101: 'inglês', 201: 'francês', 301: 'alemão', 401: 'russo', 501: 'espanhol'}

# observe que o iterável poderia ser dado diretamente como parâmetro do construtor
» dic_6 = dict(zip(keys, values))
» print(dic_6)
↳ {101: 'inglês', 201: 'francês', 301: 'alemão', 401: 'russo', 501: 'espanhol'}

# dicionários aninhados
» dicionario = {i: {j: i*j for j in range(1, 6)} for i in range(2, 5)}
» print(dicionario)
↳ {2: {1: 2, 2: 4, 3: 6, 4: 8, 5: 10}, 3: {1: 3, 2: 6, 3: 9, 4: 12, 5: 15}, 4: {1: 4, 2: 8, 3: 12, 4: 16, 5: 20}}
# Nesse dicionário as chaves vão de 2 a 4 e seus valores são outros dicionários.

Compreensões de listas e de dicionários são formas elegantes e de fácil leitura. No entanto, se forem muito complexas elas podem tornar o código difícil de ler e de debugar (encontrar eventuais erros). Devemos nos lembrar que outras pessoas podem necessitar ler o nosso código, ou você mesmo, daqui a um tempo, quando dificilmente se lembrará do raciocínio que te levou à construção de bloco sofisticados e elegantes.

Função sorted()

A função

sorted(iteravel, key, reverse)

é usada para ordenar iteráveis (sequências ou coleções). Os parâmetros key e reverse são opcionais. keyé uma funcão comparadora de elementos usada para estabelecer a ordem usada e reverse = True promove a ordenação reversa. O default é False. A função retorna uma lista de elementos ordenados.

» a = ("bola", "almeja", "cara", "fraco", "dado", "errado", "gato")
» print(sorted(a))
↳ ['almeja', 'bola', 'cara', 'dado', 'errado', 'fraco', 'gato']

» print(sorted(a, reverse=True))
↳ ['gato', 'fraco', 'errado', 'dado', 'cara', 'bola', 'almeja']

# uma string é uma sequência
» x = "intrinsecamente"
» print(sorted(x))
↳ ['a', 'c', 'e', 'e', 'e', 'i', 'i', 'm', 'n', 'n', 'n', 'r', 's', 't', 't']

# um dictionário tem as suas chaves ordenadas
» x = {'p': 1, 'y': 2, 't': 3, 'h': 4, 'o': 5, 'n': 6}
» print(sorted(x))
↳ ['h', 'n', 'o', 'p', 't', 'y']

# os elementos de um conjunto podem ser ordenados
» x = {7, 1, 2, 9, 3, 5}
» print(sorted(x))
↳ [1, 2, 3, 5, 7, 9]

# a função interna len pode ser usada como critério de ordenação
» palavras = ['a', 'casa', 'da', 'mãe', 'joana', 'foi', 'atacada', 'por', 'soldados' ]
» print(sorted(palavras, key=len))
↳ ['a', 'da', 'mãe', 'foi', 'por', 'casa', 'joana', 'atacada', 'soldados']

Outras funções podem ser usadas, inclusive funções definidas pelo usuário. No exemplo abaixo a função ordenadora substitui o valor na lista por seu resto na divisão por nove.

# ordena lista de inteiros baseado em seu resto na divisão por 9
» def func(x):
»     return x % 9

» L = [9, 15, 31, 111, 7]

» print("Ordenação normal :", sorted(L))
» print("Ordenação baseada no resto por 9:", sorted(L, key=func))

↳ Ordenação normal : [7, 9, 15, 31, 111]
↳ Ordenação baseada no resto por 9: [9, 111, 31, 15, 7]

Função filter()


A função tem a forma de

filter(funcao, sequencia)

e constroi um iterador usando uma função booleana (que retorna True ou False) e uma sequência ou um iterável qualquer. Ela testa cada elemento da sequência usando a função e retorna apenas os elementos avaliados como True.

» palavras = ['ciência', 'latim', 'conhecimento', 'prática', 'sistemática', 'pesquisa']
# menores é True se a palavra tiver até 7 letras
» def menores(palavra):
»     if len(palavra) < 8:
»         return True
»     else:
»         return False

» menores_palavras = filter(menores, palavras)
» for m in menores_palavras:
»     print(m)
↳ ciência
↳ latim
↳ prática

# observe que a função poderia ser mais compacta:
» def menores(palavra):
»     return len(palavra) < 8

No próximo exemplo temos uma lista de dicionários. Cada entrada dessa lista é um dicionário contendo dados sobre um animal. O código define uma função pesquisa que recebe essa lista e procura quais de suas entradas contém algum valor com o substring busca.

» bicharada = [
»   {'nome': 'Asdrubal', 'especie': 'tubarão', 'peso': '290', 'habitat': 'oceano'},
»   {'nome': 'Ana Lee', 'especie': 'caranquejo', 'peso': '1.2', 'habitat': 'oceano'},
»   {'nome': 'Raul', 'especie': 'urso', 'peso': '180', 'habitat': 'floresta'},
»   {'nome': 'Joana', 'especie': 'raposa', 'peso': '21', 'habitat': 'floresta'},
»   {'nome': 'Omar Lee', 'especie': 'golfinho', 'peso': '120', 'habitat': 'oceano'},
»   {'nome': 'Tulio', 'especie': 'rato', 'peso': '1.4', 'habitat': 'doméstico'}
» ]

» def pesquisa(lista, busca):
»     def criterio(x):
»         for v in x.values():
»             if busca in v:
»                 return True
»         return False
»     return filter(criterio, lista)

» lista_filtrada = pesquisa(bicharada, 'tuba')
» for t in lista_filtrada:
»     print(t)
↳ {'nome': 'Asdrubal', 'especie': 'tubarão', 'peso': '290', 'habitat': 'oceano'}

» lista_filtrada = pesquisa(bicharada, 'Lee')
» for t in lista_filtrada:
»     print(t)
↳ {'nome': 'Ana Lee', 'especie': 'caranquejo', 'peso': '1.2', 'habitat': 'oceano'}
↳ {'nome': 'Omar Lee', 'especie': 'golfinho', 'peso': '120', 'habitat': 'oceano'}

Sugestão: altere o código acima para encontrar parte de um valor dentro de um campo especificado. Encontre o animal cujo habitat contém a substring 'ocea'.

Iteradores (iterators)

No Python um iterador é um objeto que contém um número contável de elementos que podem ser iterados, ou seja, que podem ser acessados e lidos um de cada vez. Além dos laços for e das compreensões de listas e sequências no Python podemos utilizar iteradores para percorrer sequências. Eles pode ser construídos com a função

nome_iterador = iter(sequencia, marcador).

O parâmetro marcador é opcional. O iterador possui o método _next()_ que fornece o próximo elemento. Uma exceção StopIteration é lançada no final, quando todos os elementos foram percorridos. O iterador é esgotado e, portanto, só pode ser percorrido uma vez.

Exemplos de uso estão no código abaixo. Listas são convertidas em iteradores, que são percorridos com next().
Se a iteração não é interrompida antes do fim um erro é lançado. Um erro é gerado no final e sua captura é usada para interromper o laço.

# Uma lista é convertida em um iterador
» lista = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
» it = iter(lista)
» for k in range(len(lista)):
»     print(next(it), end=' ')
↳ a b c d e f g h

# queremos separar números positivos e negativos de uma lista
# o erro no final da iteração é usado para interromper o laço
» original = [1, 6, -8, -3, 0, -2, 9, 76, -45]
» positivos = []
» negativos = []
» t = iter(original)
» try:
»     while True:
»         x = next(t)
»         if x >= 0:
»             positivos.append(x)
»         else:
»             negativos.append(x)
» except StopIteration:
»     print('São positivos:' , positivos, '\nSão negativos:' , negativos)

↳ São positivos: [1, 6, 0, 9, 76]
↳ São negativos: [-8, -3, -2, -45]

Embora a mesma funcionalidade possa ser conseguida de formas diferentes e mais compactas a transformação de um objeto iterável em um iterador é bastante útil para se escrever classes e métodos definidos pelo programador, como veremos.

Funções Lambda

Funções são boas formas de escrever código de fácil reutilização. No entanto, algumas vezes precisamos de definir um operador de forma compacta e que será utilizado apenas naquele ponto do código. As funções lambda (ou funções anônimas) permitem a criação de funções (que sequer recebem um nome) mas agem da forma desejada sobre seus argumentos.

Funções lambda tem uma sintaxe concisa:

lambda <arg1, ..., argn> : <expressão>

Elas só podem conter uma única expressão, nenhuma declaração, deve ser escrita em linha única e não comportam anotações. São exemplos simples de funções lambda:

# uma função bem simples é a identidade.
# a definição usual seria
» def id(x):
»     return x

# usando função lambda
» i = lambda x: x
» i(9)
↳ 9

# a função não precisa ser associada a uma variável
» (lambda x: abs(x))(-9)
↳ 9

# outros ex. Soma e multiplicação
» x = lambda a, b : a + b
» x(5,7)
↳ 12

» (lambda a, b : a * b)(5,9)
↳ 45

# múltiplos argumentos não são envoltos em parênteses na definição
» exp = lambda x,y : x**y
» exp(2,3)
↳ 8

» mix = lambda x,y,z : (x**y)/z
» mix(3,4,5)
↳ 16.2

# qualquer tipo de objeto pode ser passado como argumento
# no exemplo uma tupla é passada
» u = (4,7)
» (lambda x: x[0]*x[1])(u)
↳ 28

# uma função lamba que age sobre strings
# .title() torna o primeiro caracter maiúsculo
» nome_autor = lambda nome, sobrenome: f'Autor: {nome.title()} {sobrenome.title()}'
» nome_autor('richard','dawkins')
↳ 'Autor: Richard Dawkins'

A construção f'Texto {variavel}' permite a inserção de variáveis dentro de uma string.
Um exemplo de uso frequente consiste no uso de lambdas para fornecer um critério de ordenamento. Vimos que a função sorted(sequencia, key) pode ordenar uma sequência de acordo com um critério dado em key. No caso abaixo a cada tupla é associado o produto de seus pares, e a ordenação é feita nessa métrica.

» p = [(3, 3), (4, 2), (2, 2), (5, 2), (1, 7)] 
» sorted(p, key=lambda x: x[0]*x[1])
↳ [(2, 2), (1, 7), (4, 2), (3, 3), (5, 2)]

# dados vetores no espaço, ordená-los em ordem crescente de módulo
# a função lambda calcula o quadrado do módulo
» v =[(1,5,3), (1,8,2), (7,8,9), (2,4,3), (2,2,-1)]
» sorted(v, key=lambda x: x[0]**2+x[1]**2+x[2]**2)
↳ [(2, 2, -1), (2, 4, 3), (1, 5, 3), (1, 8, 2), (7, 8, 9)]

No exemplo seguinte definimos a função vezes_nque recebe o argumento n e opera sobre ele com a função lambda lambda a : a * n. Essa última, por sua vez, recebe outro argumento, no caso a. O exemplo também ilustra o fato de que uma função é um objeto e pode ser atribuída à uma variável.

# queremos uma função que multiplica seu argumento por um número especificado n
» # n pode ser visto como um parâmetro
» def vezes_n(n):
»     return lambda a : a * n

» funcao_duplicar = vezes_n(2)     # n = 2
» funcao_triplicar = vezes_n(3)    # n = 3
» funcao_x1000 = vezes_n(1000)     # n = 1000

» funcao_duplicar(23)              # n = 2, a = 23
↳ 46

» funcao_triplicar(15)             # n = 3, a = 15
↳ 45

» funcao_x1000(12.345)            # n = 1000, a = 12.345
↳ 12345.0

A minha_funcao abaixo é uma função lambda com dois argumentos sendo um deles outra função. No primeiro caso 2 + 2*10 = 22. No segundo caso 4 + (4)**3 -6 = 62.

» minha_funcao = lambda x, f: x + f(x)
» minha_funcao(2, lambda x: x*10 )     # aqui f(x) = x*10
↳ 22
» minha_funcao(4, lambda x: x**3-6)   # aqui f(x) = x**6 - 6
↳ 62

Função map()


Lambdas são também muito usadas junto com a função map() que tem a seguinte assinatura:

map(funcao, iteravel1, ..., iteraveln).

Pelo menos os argumentos funcao e um iterável são obrigatórios. A função percorre os iteráveis fornecidos usando a função especificada. Essa função deve usar um elemento de cada iterável. Ela usa um algoritmo otimizado para realizar sua transformação, o que a torna mais eficiente e rápida do que laços usuais do Python.

Ela retorna um objeto iterável map que pode ser convertido em lista com list(). Alguns exemplos:

# a operação abaixo concatena duas strings
» def concatena(a, b):
»     return (a + ' ' + b)
» iter1 = ('banana', 'laranja', 'uva')
» iter2 = ('nanica', 'bahia', 'syrah')
» x = map(concatena, iter1, iter2)

# para ver o resultado podemos percorrer a iterável gerada
# cada i no laço é uma única string (como 'banana nanica')
» for i in x:
»     print(i)
↳ banana nanica
↳ laranja bahia
↳ uva syrah

# a funcão pow(x,y) retorna x^y ou xy
# como pow age com 2 argumentos, duas sequências devem ser dadas em map
» y = map(pow, [6,7,8],[3,2,1])
» for i in y:
»     print(i, end=' - ')
↳ 216 - 49 - 8 -

# a função pode ser uma lambda
# duplicar cada elemento da lista
» lista = [10, 25, 17, 9, 30, -5]
» lista_2 = map(lambda n : n*2, lista)
# para exibir a sequência retornada podemos transformá-la em uma lista
» list(lista_2)
↳ [20, 50, 34, 18, 60, -10]

O uso de funções lambda pode tornar mais compacto o uso de map.

# para retornar uma lista de quadrados
» def quadrado(n):
»     return n**2
» numeros = [1, 2, 3, 4, 5]
» quadrados = map(quadrado, numeros)
» list(quadrados)
↳ [1, 4, 9, 16, 25]

# o mesmo resultado pode ser conseguido usando lambdas
» list(map(lambda x:x**2, range(1,6)))
↳ [1, 4, 9, 16, 25]

# abs() é função interna, que retorna valor absoluto
» nums = [-5, -3, -1, 0, 1, 3, 5]
» absolutos = list(map(abs, nums))
» absolutos
↳ [5, 3, 1, 0, 1, 3, 5]

# somar e multiplicar com 3 listas
» list(map(lambda i, j, k: i+j*k, [2, 4], [1, 3], [7, 8]))
↳ [9, 28]

# strip() remove string dados nas extremidades da string
» traco = ['---12356.876-', '-casa-da-vó---', '-mil-', '---']
» list(map(lambda s: s.strip("-"), traco))
↳ ['12356.876', 'casa-da-vó', 'mil', '']

# função recebe um número e retorna tuplas de 3 elementos
» def pot_1_2_3(x):
»     return x, x ** 2, x ** 3
» numeros = [1, 2, 3, 4]
» list(map(pot_1_2_3, numeros))
↳ [(1, 1, 1), (2, 4, 8), (3, 9, 27), (4, 16, 64)]

# a mesma coisa usando lambda
» list(map(lambda x: (x, x**2, x**3), numeros))
↳ [(1, 1, 1), (2, 4, 8), (3, 9, 27), (4, 16, 64)]

Em muitos casos uma compreensão de lista faz o mesmo que uma iteração via map(). No entanto é útil conhecer a função não apenas para poder ler e compreender código escrito por outras pessoas mas também para eventuais situações onde ela pode ser mais simples ou mais eficaz.

🔺Início do artigo


Arquivos e pastas

Bibliografia

Consulte a bibliografia no final do primeiro artigo dessa série.

Python: Resumos


Conteúdo

  • Métodos de Conjuntos (sets)
  • Métodos de Dicionários (dictionaries)
  • Métodos de Arquivos (files)
  • Métodos da biblioteca os
  • Exceções
  • Palavras reservadas

    Palavras reservadas (ou keywords) que são nomes que fazem parte da sintaxe da linguagem e não podem ser utilizadas para nomes de variáveis.

    Palavras reservadas do python
    and except lambda with
    as finally nonlocal while
    assert false None yield
    break for not
    class from or
    continue global pass
    def if raise
    del import return
    elif in True
    else is try

    Funções Internas

    Segue uma lista de funções internas ou pré-programadas do Python. Muitas outras podem ser acrescentadas à uma sessão através da importação de módulos externos.

    Função ação (retorna)
    abs() valor absoluto de um número
    all() True se todos os itens em um objeto iterável forem verdadeiros
    any() True se qualquer item em um objeto iterável for verdadeiro
    ascii() uma versão legível de um objeto (trocando caracteres não ascii por caracteres de escape)
    bin() versão binária de um número
    bool() valor booleano do objeto especificado
    bytearray() uma matriz de bytes
    bytes() um objeto bytes
    callable() True se o objeto especificado pode ser chamado, caso contrário, False
    chr() um caractere do código Unicode especificado.
    classmethod() converte um método em um método de classe
    compile() fonte especificada como um objeto, pronto para ser executado
    complex() um número complexo
    delattr() exclui o atributo especificado(propriedade ou método) do objeto
    dict() um dicionário(Array)
    dir() uma lista das propriedades e métodos do objeto
    divmod() quociente e resto quando o argumento1 é dividido pelo argumento2
    enumerate() pega uma coleção(por exemplo, uma tupla) e retorna como um objeto enumerado
    eval() avalia e executa uma expressão
    exec() executa o código(ou objeto) especificado
    filter() usa uma função de filtro para excluir itens em um objeto iterável
    float() um número de ponto flutuante
    format() formata um valor especificado
    frozenset() um objeto frozenset
    getattr() o valor do atributo especificado (propriedade ou método)
    globals() a tabela de símbolos global atual como um dicionário
    hasattr() True se o objeto especificado tem o atributo especificado(propriedade / método)
    hash() o valor hash de um objeto especificado
    help() exibe ajuda embutido
    hex() converte um número em seu valor hexadecimal
    id() o id de um objeto
    input() permite entrada do usuário
    int() um número inteiro
    isinstance() True se o objeto é instância de outro objeto especificado
    issubclass() True se a classe é subclasse de objeto especificado
    iter() um objeto iterador
    len() o comprimento de um objeto
    list() uma lista
    locals() um dicionário atualizado da tabela de símbolos local atual
    map() o iterador com a função especificada aplicada a cada item
    max() o maior item em um iterável
    memoryview() um objeto de visualização de memória
    min() o menor item em um iterável
    next() o próximo item em um iterável
    object() um novo objeto
    oct() converte um número em um octal
    open() abre um arquivo e retorna objeto de arquivo
    ord() converte um inteiro que representa o Unicode do caractere especificado
    pow() o valor de x à potência de y
    print() imprime no dispositivo de saída padrão
    property() obtém, define ou exclui uma propriedade
    range() uma sequência de números, começando em 0 e incrementos em 1 (por padrão)
    repr() uma representação legível de um objeto
    reversed() um iterador reverso
    round() arredonda um número
    set() um objeto de conjunto
    setattr() define um atributo (propriedade/método) de um objeto
    slice() um objeto de fatia
    sorted() uma lista ordenada
    @staticmethod() Converte um método em um método estático
    str() um objeto de string
    sum() soma os itens de um iterador
    super() um objeto que representa a classe pai
    tuple() uma tupla
    type() o tipo de um objeto
    vars() a propriedade __dict__ de um objeto
    zip() um iterador, de dois ou mais iteradores

    Métodos de Strings

    Método descrição
    capitalize() converte 1º caracter em maiúsculo
    casefold() converte string em minúsculas
    center() retorna string centralizada
    count() retorna número de ocorrências de um valor especificado na string
    endswith() retorna True se string termina com valor especificado
    find() busca por valor especificado na string e retorna the posição se encontrado
    format() Formata de acordo com valores especificados
    index() busca por valor especificado na string e retorna the posição se encontrado
    isalnum() retorna True se todos os caracteres são alfa-numéricos
    isalpha() retorna True se todos os caracteres são alfabéticos
    isdecimal() retorna True se todos os caracteres são decimais
    isdigit() retorna True se todos os caracteres são dígitos
    islower() retorna True se todos os caracteres são minúsculos
    isnumeric() retorna True se todos os caracteres são numéricos
    isspace() retorna True se todos os caracteres são espaços
    istitle() retorna True se a string segue regra de títulos
    isupper() retorna True se todos os caracteres são maiúsculos
    join() reune elementos de um iterável no final da string
    ljust() retorna a string justificada à esquerda
    lower() converte a string para minúsculas
    lstrip() retorna a string sem espaços à esquerda
    partition() retorna tuple partindo a string em 3 partes
    replace() substitui trecho da string por outro especificado
    rfind() busca trecho especificado value e retorna última posição
    rindex() busca trecho especificado value e retorna última posição
    rjust() retorna string justificada à direita
    rsplit() quebra a string no separador especificado, retornando lista
    rstrip() retorna a string sem espaços à direita
    split() quebra a string no separador especificado, retornando lista
    splitlines() quebra a string nas quebras de linha, retornando lista
    startswith() retorna True se string começa com valor especificado
    strip() retorna a string sem espaços laterais
    swapcase() inverte minúsculas e maiúsculas
    title() converte em maiúscula o 1º caracter de cada palavra
    upper() converte a string em maiúsculas
    zfill() preencha com número de zeros especificado, no início

    Métodos de Listas (lists)

    Método ação
    append() insere elementos na lista
    clear() remove todos os elementos na lista
    copy() retorna uma cópia da lista
    count() returns número de elementos com valor especificado
    extend() insere os elementos de outra lista (ou iterável) ao final da lista
    index() returna o índice do 1º elemento com valor especificado
    insert() insere elemento em posição especificada
    pop() remove elemento em posição especificada
    remove() remove elemento em posição especificada por índice
    reverse() inverte a ordem da lista
    sort() ordena a lista

    Além desses as seguintes funções são úteis para se tratar com sequências:

    Função Descrição
    cmp(x, y) compara dois valores
    len(seq) retorna o comprimento da sequência
    list(seq) converte uma sequência em lista
    max(args) retorna o valor máximo na sequência
    min(args) retorna o valor mínimo na sequência
    eversed(seq) permite a iteração sobre valores na sequência
    sorted(seq) retorna lista ordenada dos elementos na sequência
    tuple(seq) converte a sequência em uma tuple

    Métodos das Tuplas (tuples)

    Método Descrição
    count() retorna quantas vezes um valor especificado ocorre na tupla
    index() procura por valor especificado e retorna sua posição

    Métodos de Conjuntos (sets)

    Método Descrição
    add() insere elemento no set
    clear() remove todos os elementos do set
    copy() retorna cópia do set
    difference() retorna um set com a diferença entre 2 ou mais sets
    difference_update() remove elementos incluidos no segundo set
    discard() remove item especificado
    intersection() retorna o set interseção de 2 sets
    intersection_update() remove items do set não presentes no segundo set especificado
    isdisjoint() retorna True se os 2 sets são disjuntos
    issubset() retorna True se o set é subconjunto do segundo set
    issuperset() retorna True se o set contém o segundo set
    pop() remove (e retorna) um elemento arbitrário do set
    remove() remove o elemento especificado
    symmetric_difference() retorna o set com a diferença simétrica de dois sets
    symmetric_difference_update() insere a diferença simétrica desse set em outro
    union() retorna um set com a união dos sets
    update() atualiza o primeiro set com sua união com um ou mais sets

    Métodos de Dicionários (dictionaries)

    Método Descrição
    clear() remove todos os elementos from the dictionário
    copy() retorna uma cópia do dicionário
    fromchaves() retorna dicionário com chaves e valores especificados
    get() retorna o valor relativo a chave dada, ou valor default dado
    items() retorna uma lista contendo uma tupla para cada par chave:valor
    chaves() retorna lista com as chaves do dicionário
    pop() remove o elemento relativo à chave especificada
    popitem() remove o último par chave:valor inserido
    setdefault() retorna o valor relativo à chave dada. Se a chave não existe insere chave:valor
    update() Atualiza o dicionário com pares chave:valor dados
    valors() retorna uma lista com os valores do dicionário

    Métodos de Arquivos (files)

    Método Descrição
    open(arquivo, modo) Abre o arquivo (descrição dos parâmetros abaixo)

    Os seguintes valores são válidos para modo.

    Parâmetro Descrição
    r para leitura – O ponteiro é colocado no início do arquivo. Default.
    r+ para leitura e gravação. O ponteiro fica no início do arquivo.
    w apenas para gravação. Substitui arquivo existente, cria novo se o arquivo não existir.
    w+ escrita e leitura. Substitui arquivo existente, cria novo se arquivo não existir.
    rb para leitura em formato binário. O ponteiro fica no início do arquivo.
    rb+ para leitura e escrita em formato binário.
    wb+ para escrita e leitura em formato binário. Substitui o arquivo existente. Cria novo se não existir.
    a para anexar. O ponteiro fica no final do arquivo, se o arquivo existir. Cria novo se existir.
    ab um arquivo para anexar em formato binário. O ponteiro fica no final do arquivo. Cria novo se não existir.
    a+ para anexar e ler. O ponteiro fica no final do arquivo, se arquivo existir. Cria novo se não existir.
    ab+ anexar e ler em formato binário. O ponteiro no final do arquivo. Cria novo se não existir.
    x cria novo arquivo lançando erro se já existir.
    Método Descrição
    close() fecha o arquivo; sem efeito se arq. já está fechado
    detach() retorna o fluxo bruto separado do buffer
    fileno() retorna número inteiro diferente para cada arquivo aberto
    flush() descarrega no disco as alterações no buffer
    isatty() retorna True se o fluxo de arquivo é interativo
    next(arquivo) itera sobre arquivo, lançando erro no final
    read() retorna o conteúdo do arquivo
    readable() retorna True se o fluxo do arquivo pode ser lido
    readline() retorna uma única linha do arquivo
    readlines() retorna lista com todas as linhas do arquivo
    search() localiza item no arquivo e retorna sua posição
    searchable() retorna True se o arquivo é pesquisável, por ex. com seek()
    tell() retorna a posição atual do arquivo
    truncate([tamanho]) redimensiona (truncando) o arquivo para um tamanho especificado
    writable() retorna True se o arquivo pode receber gravações
    write() grava a string especificada no arquivo
    writelines() escreve a sequencia no arquivo. Qualquer objeto iterável composto por strings pode ser usado

    Métodos de OS

    Método Descrição
    chdir(“novaPasta”) mudar a pasta ativa para novaPasta. Se novaPasta = “..” vai para pasta mãe
    getcwd() ler diretório (pasta) atual
    listdir(“pasta”) lista arquivos e subpastas de “pasta”
    makedirs(sequencia) criar pastas com nomes em sequencia
    mkdir(“nomeDaPasta”) criar uma pasta
    remove(arquivo) apagar arquivo
    removedirs(caminho) apagar pastas recursivamente
    rmdir(“pastaRemover”) apagar pasta “pastaRemover”. Não pode ser pasta atual ou estar em uso por ouro processo
    rename(“nomeAntigo”,”nomeNovo”) renomear pasta “nomeAntigo” para “nomeNovo”
    uname() retorna dados sobre o sistema operacional, usuário e máquina usada

    Exceções

    Todas as exceções no Python se derivam da classe base BaseException. As exceções built-in que não provocam uma queda do sistema são todas derivadas de Exception, incluive as definidas pelo usuário. A tabela abaixo mostra uma hierarquia de classes das exceções, cada indentação representando um grupo de derivadas da classe acima. Por exemplo, a classe ZeroDivisionError tem a seguinte linha hierárquica:

    BaseException ⟶ Exception ⟶ ArithmeticError ⟶ ZeroDivisionError


    BaseException
    SystemExit
    KeyboardInterrupt
    GeneratorExit
    Exception
    StopIteration
    StopAsyncIteration
    ArithmeticError
    FloatingPointError
    OverflowError
    ZeroDivisionError
    AssertionError
    AttributeError
    BufferError
    EOFError
    ImportError
    ModuleNotFoundError
    LookupError
    IndexError
    KeyError
    MemoryError
    NameError
    UnboundLocalError
    OSError
    BlockingIOError
    ChildProcessError
    ConnectionError
    BrokenPipeError
    ConnectionAbortedError
    ConnectionRefusedError
    ConnectionResetError
    FileExistsError
    FileNotFoundError
    InterruptedError
    IsADirectoryError
    NotADirectoryError
    PermissionError
    ProcessLookupError
    TimeoutError
    ReferenceError
    RuntimeError
    NotImplementedError
    RecursionError
    SyntaxError
    IndentationError
    TabError
    SystemError
    TypeError
    ValueError
    UnicodeError
    UnicodeDecodeError
    UnicodeEncodeError
    UnicodeTranslateError
    Warning
    DeprecationWarning
    PendingDeprecationWarning
    RuntimeWarning
    SyntaxWarning
    UserWarning
    FutureWarning
    ImportWarning
    UnicodeWarning
    BytesWarning
    ResourceWarning
    🔺Início do artigo

    Bibliografia

    Consulte a bibliografia no final do primeiro artigo dessa série.

    Python: Sequências e Coleções


    Introdução

    Sequências: no Python sequências são conjuntos de elementos ordenados que podem ser acessados em sua ordem (o que chamamos de iteráveis. Já vimos que strings são sequências. Por ex., print('Casa da Mãe Joana'[3]) resulta em 'a'.

    range(), na verdade não é uma função mas uma classe geradora de um iterador, como veremos mais tarde. Ela fornece objetos que produzem os elementos sob demanda, sem precisar guardar todos na memória.

    Outra sequência de uso comum é range, que é o tipo de objeto retornado pela “função” range(). Ela tem a seguinte sintaxe ou assinatura:

    range(inicio, fim, passo)

    onde inicio e passo são opcionais. Ela gera uma sequência de inicio até fim (exclusivo) com intervalo de passo.

    » faixa = range(2, 20, 3)
    » print('o objeto é', faixa)
    ↳ o objeto é range(2, 20, 3)
    » print('o tipo do objeto é', type(faixa))
    ↳ o tipo do objeto é <class 'range'>
    » print('o segundo elemento é', faixa[1])
    ↳ o segundo elemento é 5
    
    # podemos iterar pelos itens de faixa
    » for t in faixa:
    »     print(t, end=' ')
    ↳ 2 5 8 11 14 17 
    
    Conteineres” são estruturas de dados que organizam e agrupam dados de acordo com seu tipo.

    Coleções são conteineres de dados. No módulo básico do Python existem quatro tipos de dados em coleções:

    • Lista (list): ordenada e mutável, pode ter membros duplicados.
    • Tupla (tuple): ordenada e imutável, pode ter membros duplicados.
    • Conjunto (set): não ordenada e não indexada, sem membros duplicados.
    • Dicionário (dictionary): não ordenada, mutável, sem membros duplicados.

    Observe que sets são mutáveis mas não admitem objetos mutáveis como seus elementos. Podemos ter, portanto, conjuntos de inteiros, strings ou tuplas, mas não de listas ou dicionários.

    Existe um módulo chamado collections com outros tipos de conteineres com estruturas de dados mais específicas como, por exemplo, uma tupla nomeada e um dicionário ordenado.

    Listas

    Além dos objetos já vistos, muitos outros são pre-programados no núcleo básico do Python. A listas (lists) são sequências ordenadas de objetos que podem ser acessados por meio de seu índice (ou index), um marcador de sua posição. Os objetos de uma lista não precisam ser do mesmo tipo, são delimitados por colchetes e separados por vírgulas.

    lista = [valor_0, …, valor_n]

    As listas são mutáveis, podem ser encolhidas ou expandidas e ter seus elementos substituídos. Exemplos de listas são dados a seguir:

    » lista1 = ['Maria', 25]
    » lista2 = ['José', 27]
    » lista3 = [lista1, lista2]
    » print(lista1[0], lista1[1])
    ↳ Maria 25
    
    # lista3 é uma lista de listas
    » print(lista3)
    ↳ [['Maria', 25], ['José', 27]]
    
    # o 2º elemento da 2ª lista é
    » print(lista3[1][1])
    ↳ 27
    

    Da mesma forma que sequências de caracteres (nas strings), elementos em listas e tuplas podem ser acessados por meio de seus índices. Fatias (ou slices) lista[i:j] se iniciam no i-ésimo elemento, até o j-ésimo, exclusive, de forma que len(lista[i:j]) = j-i. Índices negativos contam a partir do final.

    » lista4 = [10, 23, 45, 56, 67, 78, 89, 90]
    » lista4[1:3]
    ↳ [23, 45]
    
    # omitindo o 1º índice
    » lista4[:2]
    ↳ [10, 23]
    
    # omitindo o 2º índice
    » lista4[4:]
    ↳ [67, 78, 89, 90]
    
    # o último elemento
    » lista4[-1]
    ↳ 90
    
    » lista4[-4:-2]
    ↳ [67, 78]
    
    # len fornece o número de elementos na lista
    » len(lista4[3:7])
    ↳ 4
    
    # um 3º parâmetro indica o "passo"
    » lista4[::2]
    ↳ [10, 45, 67, 89]
    
    # um passo negativo indica contagem do fim para o início
    » lista4[::-1]
    ↳ [90, 89, 78, 67, 56, 45, 23, 10]
    


    Vimos que a ausência do 1º índice assume o início, a ausência do 2º assume o final. O 3º indica para pular um número de elementos.

    Uma função que retorna uma lista (ou outra sequência qualquer) pode ser diretamente indexada. Por ex., podemos construir uma função que retorna o mês abreviado em 3 letras. O índice do mês pode ser atribuído diretamente ao retorno da função:

    » def mes_abrev():
    »     m = ['jan','fev','mar','abr','mai','jun','jul','ago','set','out','nov','dez',]
    »     return m
    
    # para pegar o segundo mês:
    » mes_abrev()[1]
    ↳ 'fev'
    
    # para pegar o 1º trimestre:
    » mes_abrev()[:3]
    ↳ ['jan', 'fev', 'mar']
    
    # para pegar o último trimestre:
    » mes_abrev()[-3:]
    ↳ ['out', 'nov', 'dez']
    

    Assim como ocorre com strings, listas podem ser somadas (concatenadas) e multiplicadas por um número (repetidas). O efeito é o mesmo.

    » lista5 = [0, 1, 2, 3, 4]
    » lista6 = [10, 11, 12, 13, 14]
    » lista5 + lista6
    ↳ [0, 1, 2, 3, 4, 10, 11, 12, 13, 14]
    
    » lista5 * 2
    ↳ [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
    

    Uma lista pode ser inicializada vazia ou com um número especificado de itens.

    » lista_vazia = []      # lista vazia    
    » lista_vazia
    ↳ []
    
    # elementos podem ser inseridos com o método append
    » lista_vazia.append(12)
    » lista_vazia.append(13)
    » lista_vazia
    ↳ [12, 13]
    
    # o mesmo efeito seria obtido com concatenação
    » lista_vazia.clear()     # a lista volta a ser vazia
    » lista_vazia += [12, 13]
    » lista_vazia
    ↳ [12, 13]
    
    # lista com 5 entradas
    » lista_none = [None]*5
    » lista_none
    ↳ [None, None, None, None, None]
    

    Sendo mutáveis listas podem ser alteradas in place tendo qualquer de seus valores trocados sem a necessidade de criação de nova lista. count(item) retorna quantas vezes item aparece na lista. index(item) retorna o índice onde item aparece.

    » palavras = ['palha', 'grande', 'casa', 'dado', 'pequeno',
                  'coisa', 'gado', 'fato', 'gato', 'lá' ]
    » print('A terceira palavra é ---| %s |---' % palavras[2] )
    ↳ A terceira palavra é ---| casa |---
    
    » palavras[2] = 'house'
    » print('A terceira palavra é ---| %s |---' % palavras[2] )
    ↳ A terceira palavra é ---| house |---
    
    # inserindo mais uma 'house'
    » palavras[3] = 'house'
    » palavras.count('house')
    ↳ 2
    
    # o índice de 'coisa'
    » palavras.index('coisa')
    ↳ 5
    
    # index retorna a 1ª ocorrência, quando existem mais de 1
    » palavras.index('house')
    ↳ 2
    

    Objetos podem ser inseridos em uma lista, na n-ésima posição, com lista.insert(n, obj). Um membro do lista pode ser removido com lista.pop(n). Esse método retorna o objeto removido. lista.remove(obj) também faz a remoção, sem retornar o objeto.

    # inserir um objeto na posição 3
    » palavras.insert(3, 'estrela')
    » print(palavras)
    ↳ ['pequeno', 'palha', 'lá', 'estrela', 'house', 'house', 'grande', 'gato', 'gado', 'fato', 'coisa']
    
    # pop extrai e retira elemento no índice dado
    » saiu = palavras.pop(3)
    » print(saiu)
    ↳ estrela
    
    # a lista fica sem esse elemento
    » print(palavras)
    ↳ ['pequeno', 'palha', 'lá', 'house', 'house', 'grande', 'gato', 'gado', 'fato', 'coisa']
    
    # se nenhum índice for fornecido o último elemento é removido (e retornado)
    » palavras.pop()
    ↳ coisa
    » print(palavras)
    ↳ ['pequeno', 'palha', 'lá', 'house', 'house', 'grande', 'gato', 'gado', 'fato']
    
    # remove não retorna o item removido
    » palavras.remove('house')
    » print(palavras)
    ↳ ['pequeno', 'palha', 'lá', 'house', 'grande', 'gato', 'gado', 'fato', 'coisa']
    
    # o item deve estar na lista ou exceção será lançada
    » palavras.remove('pedra')
    ↳ ValueError: list.remove(x): x not in list
    

    O método .sort() ordena itens de uma lista. Ele admite os parâmetros opcionais key, reverse que podem ser usados para fazer ordenamentos diferentes que o default. Por ex., se key = len o ordenamento se dará por comprimento das palavras, da menor para a maior. Se, além disso reverse = True o ordenamento se dará no sentido contrário. Também se pode definir uma função customizada para fazer essa ordenação.

    # ordenar
    » palavras.sort()
    » print(palavras)
    ↳ ['coisa', 'fato', 'gado', 'gato', 'grande', 'house', 'house', 'lá', 'palha', 'pequeno']
    
    # outra form de ordenar item em qualquer sequência é a função sorted()
    # que retorna a lista palavras ordenada sem alterar a lista original
    » sorted(palavras)    # nesse caso a lista já estava ordenada
    ↳ ['coisa', 'fato', 'gado', 'gato', 'grande', 'house', 'house', 'lá', 'palha', 'pequeno']
    
    # inverter a ordenação
    » palavras.reverse()
    » print(palavras)
    ↳ ['pequeno', 'palha', 'lá', 'house', 'house', 'grande', 'gato', 'gado', 'fato', 'coisa']
    
    » palavras.sort(key= len)
    » palavras
    ↳ ['lá', 'fato', 'gado', 'gato', 'coisa', 'house', 'house', 'palha', 'grande', 'pequeno']
    
    » palavras.sort(key= len, reverse=True)
    ↳ ['pequeno', 'grande', 'coisa', 'house', 'house', 'palha', 'fato', 'gado', 'gato', 'lá']
    

    Métodos das listas (lists)

    Método Descrição
    append() insere elementos na lista
    clear() remove todos os elementos na lista
    copy() retorna uma cópia da lista
    count() retorna o número de elementos com valor especificado
    extend() insere os elementos de outra lista (ou iterável) ao final da lista
    index() retorna o índice do 1º elemento com valor especificado
    insert() insere elemento em posição especificada
    pop() remove elemento em posição especificada por índice e retorna esse elemento
    remove() remove elemento em posição especificada por índice
    reverse() inverte a ordem da lista
    sort() ordena a lista

    Além desses as seguintes funções são úteis para se tratar com listas e outras sequências:

    Função Descrição
    cmp(x, y) compara dois valores
    len(seq) retorna o comprimento da sequência
    list(seq) converte uma sequência em lista
    max(args) retorna o valor máximo na sequência
    min(args) retorna o valor mínimo na sequência
    reversed(seq) retorna um iterador com os valores na sequência
    sorted(seq) retorna lista ordenada dos elementos na sequência
    tuple(seq) converte a sequência em uma tupla

    É possível testar se um determinado elemento é membro da lista:

    » 'gado' in palavras
    ↳ True
    
    » 'pedra' in palavras
    ↳ False
    
    # definimos uma lista de listas (com nome e senha de usuários)
    » usuarios = [
    »   ['alberto', '1234'],
    »   ['mario', '6282'],
    »   ['maria', '5274'],
    »   ['joana', '9943']
    » ]
    » nome = input('Nome do usuário: ')
    » pin = input('Código PIN: ')
    » msg = 'Accesso liberado' if [nome, pin] in usuarios else 'Acesso negado'
    » print(msg)
    
    # uma entrada de dados que não corresponde a nenhuma entrada da lista
    ↳ Nome do usuário: lucas
    ↳ Código PIN: 1234
    ↳ Acesso negado
    
    # dados de usuário cadastrado
    ↳ Nome do usuário: joana
    ↳ Código PIN: 9943
    ↳ Accesso liberado
    

    Assim como existe uma função interna len() que retorna o comprimento de sequências, temos também max(), min() que retornam o maior e menor valor dentro da lista. Essas funções funcionam também com strings, considerada a ordem alfabética.

    » numeros = [100, 23, 987]
    » print(len(numeros))
    ↳ 3
    
    » print(max(numeros))
    ↳ 987
    
    » print(min(numeros))
    ↳ 23
    
    » print(max(2, 3))
    ↳ 3
    
    » print(min(9, 3, 2, 5))
    ↳ 2
    
    # min e max fazem comparações entre strings:
    » palavras
    ↳ ['pequeno', 'palha', 'lá', 'grande', 'gato', 'gado', 'fato', 'coisa']
    
    » min(palavras)
    ↳ 'coisa'
    
    » max(palavras)
    ↳ 'pequeno'
    

    Tuplas

    Tuplas (tuples) são sequências ordenadas e imutáveis de objetos, que podem ser acessados por meio de seu índice (index), um marcador de sua posição. Os objetos que a compõem não precisam ser mesmo tipo e são delimitados por parênteses e separados por vírgulas.

    lista = (valor_0, …, valor_n)

    Tuplas e listas se comportam de modo análogo, exceto em que tuplas são imutáveis (como as strings), não podem ser alteradas após sua criação. Existem razões técnicas para a existência de tuplas: operações com tuplas são mais rápidas que as com listas, e elas ocupam menos espaço na memória. Por isso não é raro que um método retorne uma tupla ou as demande como parâmetro. Daí a necessidade de conhecê-las.

    # é comum usar tuplas na formatação de strings:
    » print('Essa frase %s %d %s %s %s.' % ('lê', 4, 'palavras','na','tupla'))
    ↳ Essa frase lê 4 palavras na tupla.
    
    # elementos são acessados por índice
    » tupla = (0,1,2,3,4,'cinco')
    » tupla[5]
    ↳ 'cinco'
    
    # tuplas são imutáveis
    » tupla[5] = 5
    ↳ TypeError: 'tuple' object does not support item assignment
    
    # um erro é lançado se o índice não existe no objeto 
    » tupla[6]
    ↳ IndexError: tuple index out of range
    
    # tuplas são iteráveis
    » for t in tupla:
    »     print(t, end=', ')
    ↳ 0, 1, 2, 3, 4, cinco,
    
    # a função len funciona para tuplas
    » print(tupla[len(tupla) - 1])
    ↳ cinco
    
    # é o mesmo que
    » print(tupla[-1])
    ↳ cinco
    
    # tuplas podem conter outras tuplas como elemento
    » a = ('primeiro', 'segundo', 'terceiro')
    » b = (a, 'segundo elemento de b')
    » print('%s' % b[1])
    ↳ segundo elemento de b
    
    » print(b[0][0], b[0][1], b[0][2])
    ↳ primeiro segundo terceiro
    

    Métodos das Tuplas (tuples)

    Método Descrição
    count() retorna quantas vezes um valor especificado ocorre na tupla
    index() procura por valor especificado e retorna sua posição

    Conjuntos (sets)

    Conjuntos (sets) são coleções não ordenadas de objetos únicos e imutáveis. Conjuntos podem ser criados listando-se diretamente os elementos ou passando-se uma sequência pelo construtor set().

    conjunto = {e_1, …, e_n}

    conjunto = set(sequencia)

    Por exemplo, abaixo dois sets são criados e as operações de interseção, união e diferença são mostradas. Nenhuma dessa operações alteram os sets envolvidos.

    # dois sets são criados
    » X = {'a', 'b', 'c', 'd'}
    » Y = set('cdef')
    » print(X, Y)
    ↳ {'d', 'c', 'b', 'a'} {'f', 'c', 'd', 'e'}
    
    # a interseção é obtida com & (e comercial)
    » X & Y
    ↳ {'c', 'd'}
    
    # a união com |
    » X | Y
    ↳ {'a', 'b', 'c', 'd', 'e', 'f'}
    
    # a diferença de conjuntos
    » X-Y
    ↳ {'a', 'b'}
    
    # comprehension (veremos mais sobre esses métodos mais tarde)
    » Z = {i**2 for i in [1,2,3,4] }
    » Z
    ↳ {1, 4, 9, 16}
    
    » type(Z)
    ↳ set
    
    # os elementos podem ser percorridos um a um (mas não de forma ordenada)
    » for i in Z:
    »     print(i, end=', ')
    ↳ 16, 1, 4, 9, 
    
    # testes para pertinência
    » 16 in Z
    ↳ True
    » 5 in Z
    ↳ False
    

    Alternativamente, as operações de interseção, união e diferença podem feitas com métodos da classe. Nesse caso o conjunto X fica alterado na operação.

    » X = {'a', 'b', 'c', 'd'}
    » Y = {'c', 'd', 'e', 'f'}
    
    # a união de dois sets (fica armazenada em X)
    » X.union(Y)
    » print(X)
    ↳ {'a', 'b', 'c', 'd', 'e', 'f'}
    
    » X = {'a', 'b', 'c', 'd'}
    » Y = {'c', 'd', 'e', 'f'}
    
    # elementos que não são comuns
    » X.symmetric_difference_update(Y)
    » print(X)
    ↳ {'a', 'b', 'e', 'f'}
    
    » X = {'a', 'b', 'c', 'd'}
    » Y = {'c', 'd', 'e', 'f'}
    # elementos em ambos os sets (interseção)
    » X.intersection_update(Y)
    » print(X)
    ↳ {'c', 'd'}
    

    Outros métodos de sets são mostrados.

    # o comprimento é o número de elementos
    » Z = {1, 4, 9, 16}
    » len(Z)
    ↳ 4
    
    # novos elementos podem ser adicionados
    » Z.add(133)
    » Z
    ↳ {1, 4, 9, 16, 133}
    
    # qualquer sequência pode ser adicionada
    » qualquer =[1, 100, 'coisa']
    » Z.update(qualquer)
    » Z
    ↳ {1, 100, 133, 16, 4, 9, 'coisa'}
    
    # um elemento pode ser removido
    » Z.remove('coisa')
    » Z
    ↳ {1, 4, 9, 16, 100, 133}
    
    # um erro é lançado no uso de remove se elemento não existe
    » Z.remove(45)
    ↳ KeyError: 45
    
    # também se pode remover com discard
    » Z.discard(133)
    » Z
    ↳ {1, 4, 9, 16, 100}
    
    # nenhum erro é lançado com discard
    » Z.discard(45)
    
    # para limpar todos os elementos do set
    » Z.clear()
    » Z
    ↳ set()
    
    # para apagar a variável (válido para qualquer variável do Python)
    » del Z
    » Z
    ↳ NameError: name 'Z' is not defined
    

    Suponha que temos uma lista longa de elementos muitos dos quais podem ser repetidos e queremos que essa lista não contenha repetições. Para remover repetições podemos transformar a lista em set (que não contém repetições) e depois retornando os dados para uma lista, caso isso seja necessário.

    # queremos remover as repetições de
    » lista_original = [1,2,3,4,4,3,2,1,6,6,7,8,8,8,9]
    » conjunto = set(lista_original)
    » lista_nova= list(conjunto)
    » lista_nova
    ↳ [1, 2, 3, 4, 6, 7, 8, 9]
    

    Seguem mais algumas ilustrações de uso de métodos de sets.

    # criamos um set usando compreensão
    » nSet = {i**3 for i in range(4)}
    » nSet
    ↳ {0, 1, 8, 27}
    
    # o método pop()  extrai um elemento qualquer e o retorna †
    » print(nSet.pop())
    ↳ 0
    » print(nSet)
    ↳ {1, 27, 8}
    
    » print(nSet.pop())
    ↳ 1
    » print(nSet)
    ↳ {27, 8}
    
    # subset e superset
    » A = {1,2}
    » B = {1,2,3}
    » A.issubset(B)
    ↳ True
    
    » A.issuperset(B)
    ↳ False
    
    » B.issuperset(A)
    ↳ True
    


    Observe que set.pop() remove e retorna um elemento qualquer do conjunto, uma vez que ele não é ordenado.

    Métodos dos conjuntos (set)

    Método Descrição
    add() insere elemento no set
    clear() remove todos os elementos do set
    copy() retorna cópia do set
    difference() retorna um set com a diferença entre 2 ou mais sets
    difference_update() remove elementos incluidos no segundo set
    discard() remove item especificado
    intersection() retorna o set interseção de 2 sets
    intersection_update() remove items do set não presentes no segundo set especificado
    isdisjoint() retorna True se os 2 sets são disjuntos
    issubset() retorna True se o set é subconjunto do segundo set
    issuperset() retorna True se o set contém o segundo set
    pop() remove (e retorna) um elemento arbitrário do set
    remove() remove o elemento especificado
    symmetric_difference() retorna o set com a diferença simétrica de dois sets
    symmetric_difference_update() insere a diferença simétrica desse set em outro
    union() retorna um set com a união dos sets
    update() atualiza o primeiro set com sua união com um ou mais sets

    Dicionários (dictionaries)

    Dicionários (dictionaries) são coleções de dados armazenados em pares chave: valor (key: value). A coleção é mutável, ordenada (a partir de Python 3.7) e não admite valores duplicados. A chave de um dicionário funciona como um índice que permite a recuperação do valor a ele associado. Eles têm a forma geral de

    dict = {key_1:value_1, …, key_n:value_n}

    As chaves são ordenadas e podem ser de diversos tipos. Valores podem ser de qualquer tipo e podem ser alterados. Por exemplo:

    # inicializando um dicionário
    » dic = {'casa':'house', 'cachorro':'dog', 'caneta':'pencil','carro':'car'}
    » dic
    ↳ {'casa': 'house', 'cachorro': 'dog', 'caneta': 'pencil', 'carro': 'car'}
    
    » print(type(dic)) 
    ↳ <class 'dict'>
    
    # acessando o valor com chave = 'caneta'
    » dic['caneta']
    ↳ 'pencil'
    
    # a função len retorna quantos pares existem no dicionário
    » len(dic)
    ↳ 4
    
    # chaves duplicadas são substituídas (a anterior é removida)
    » dic2 = {"nome" : "Pedro",
    »         "sobrenome" : "Alvarez",
    »         "idade" : 23,
    »         "idade" : 27
    »         }
    » print(dic2['idade'])
    ↳ 27
    

    Um dicionário pode ser criado recebendo uma lista de tuplas em seu construtor, como se mostra no primeiro exemplo abaixo. No exemplo seguinte tuplas são usadas como chaves.

    # t é uma lista de tuplas
    » t = [(0, 'zero'),(1, 'um'),(2, 'dois'),(3, 'tres'),(4, 'quatro')]
    » d = dict(t)
    
    » print(d)
    ↳ {0: 'zero', 1: 'um', 2: 'dois', 3: 'tres', 4: 'quatro'}
    » d[3]
    ↳ 'tres'
    
    # usando tuplas como chaves (listas não podem ser usadas)
    » tele = dict()
    » tele['Newton', 'Isaac'] = 1643
    » tele['Curie','Marie'] = 1867
    » tele['Einstein','Albert'] = 1879
    » tele['Hawking','Stephen'] = 1942
    
    » print(tele)
    ↳ {('Newton','Isaac'): 1643, ('Curie', 'Marie'): 1867, ('Einstein', 'Albert'): 1879, ('Hawking', 'Stephen'): 1942}
    # o índice é uma tupla
    » print(tele['Curie', 'Marie']) # ou print(tele[('Curie', 'Marie')])
    ↳ 1867
    
    # o dicionário pode ser percorrido lendo-se os dois valores da tupla
    » for sobrenome, nome in tele:
    »     print(nome, sobrenome, "nasceu em", tele[sobrenome,nome])
    
    ↳ Isaac Newton nasceu em 1643
    ↳ Marie Curie nasceu em 1867
    ↳ Albert Einstein nasceu em 1879
    ↳ Stephen Hawking nasceu em 1942
    
    # o dicionário também pode ser percorrido lendo-se uma tupla de cada vez
    » for t in tele:
    »     print(t, tele[t] )
    
    ↳ ('Newton', 'Isaac') 1643
    ↳ ('Curie', 'Marie') 1867
    ↳ ('Einstein', 'Albert') 1879
    ↳ ('Hawking', 'Stephen') 1942
    

    : Observe que, no exemplo acima, na criação de um par key : vale usando tuplas os parênteses ficam subentendidos:

    » tele['Newton', 'Isaac'] = 1643
    # é o mesmo que
    » tele[('Newton', 'Isaac')] = 1643
    
    # isso também ocorre quando fazemos
    » a, b = 1, 2
    # que é idêntico a
    » (a, b) = (1, 2)
    

    Se as chaves são strings simples elas podem ser especificadas como nomes de argumentos nomeados. No código abaixo uma lista de IDHs dos estados do sudeste é fornecida ao construtor.

    » idh = dict(
    »     SP=0.833,
    »     RJ=0.832,
    »     ES=0.802,
    »     MG=0.800
    » )
    » idh
    ↳ {'SP': 0.833, 'RJ': 0.832, 'ES': 0.802, 'MG': 0.8}
    

    Embora objetos de qualquer tipo (imutável) possam ser usados como chave, não existe um índice numérico associado aos elementos dos dicionários, além das próprias chaves. Dicionários mantém a ordem em que foram criados e sempre retornam o mesmo valor para cada chave.

    # Não índices existem associados às chaves dos dicionários
    » idh[1]
    ↳ KeyError: 1
    
    » d = {0: 'a', 1: 'b', 2: 'c', 3: 'd'}
    » d[3]  # operação possível porque 3 é uma chave, não um índice
    ↳ 'd'
    

    Dicionários não são ordenados mas existe um objeto chamado OrderedDict que pode ser importado da classe collections que são.

    O dicionário pessoa criado abaixo tem uma lista associada ao valor irmaos e outro dicionário associado ao valor filhos.

    # o dicionário abaixo é inicializado vazio e preenchido de modo incremental
    » pessoa = {}
    » pessoa['nome'] = 'Edvaldo'
    » pessoa['sobrenome'] = 'Santos'
    » pessoa['idade'] = 41
    » pessoa['profissao'] = 'dentista'
    » pessoa['filhos'] = {'João':3, 'Ana':7, 'Marco':10 }
    » pessoa['irmaos'] = ['Paulo', 'Eliane']
    
    » pessoa
    ↳ {'nome': 'Edvaldo',
    ↳  'sobrenome': 'Santos',
    ↳  'idade': 41,
    ↳  'profissao': 'dentista',
    ↳  'filhos': {'João': 3, 'Ana': 7, 'Marco': 10},
    ↳  'irmaos': ['Paulo', 'Eliane']}
    
    # pelo que vimos acima poderíamos também usar a forma
    » pessoa['filhos'] = dict(João=3, Ana=7, Marco=10) 
    

    A idade do filho que se chama Marco é pessoa['filhos']['Marco'] e o primeiro irmão listado é pessoa['irmaos'][0].

    » pessoa['filhos']['Marco']
    ↳ 10
    » pessoa['irmaos'][0]
    ↳ 'Paulo'
    
    Você pode encontrar informação sobre Numpy: arrays e pandas: dataframes nesse site.

    Naturalmente que uma estrutura muito complexa de vários itens aninhados pode ser difícil de manipular, portanto o bom senso deve prevalecer na construção desses objetos. Para esse fim existem objetos pre-programados bem mais sofisticados, como os arrays do Numpy e os dataframes do pandas. Também, como veremos em seções posteriores, podemos definir objetos do usuário com estruturas bem mais complexas que essas.

    Os valores associdas às chaves são mutáveis, podem ser alterados após a criação do dicionário. Chaves e valores podem ser percorridos separadamente ou como tuplas.

    » dic2 = {"nome" : "Pedro",
    »         "sobrenome" : "Cabral",
    »         "idade" : 27
    »         }
    
    # os valores são mutáveis
    » dic2['sobrenome'] = 'Alves'
    » dic2
    ↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 27}
    

    Podem ser usados os métodos dos dicionários: dic.keys(), que retorna as chaves, dic.values(), que retorna os valores, e dic.itens(), que retorna as chaves e valores, todos eles como objetos iteráveis.

    # as chaves podem ser lidas como um objeto iterável
    » chaves = dic2.keys()
    » chaves
    ↳ dict_keys(['nome', 'sobrenome', 'idade'])
    
    » for chave in chaves:
    »     print(dic2[chave], end=' ')
    ↳ Pedro Alves 27
    
    # os valores podem ser lidos em um objeto iterável
    » valores = dic2.values()
    » valores
    ↳ dict_values(['Pedro', 'Alves', 27])
    
    # percorrendo os valores
    » for t in valores:
    »     print(t, end=' ')
    ↳ Pedro Alves 27
    
    # percorrendo as chaves
    » for t in chaves:
    »     print(t, end = ' ')
    ↳ nome sobrenome idade
    
    # valores e chaves podem ser lidos como uma lista de tuplas
    » itn = dic2.items()
    » itn
    ↳ dict_items([('nome', 'Pedro'), ('sobrenome', 'Alves'), ('idade', 27)])
    
    # os pares podem ser percorridos
    » for (k,v) in dic2.items():
    »     print(k,v)
    ↳ nome Pedro
    ↳ sobrenome Alves
    ↳ idade 27
    

    O operador in verifica se um valor está presente entre as chaves ou valores.

    # verificando se uma chave está no dicionário
    » if 'idade' in dic2:
    »     print('idade é uma das chaves')
    ↳ idade é uma das chaves
    
    » print('peso é uma das chaves' if 'peso' in dic2 else 'valor não encontrado')
    ↳ valor não encontrado
    
    # verificando se um valor está presente no dicionário
    » print('Alves é um dos valores' if 'Alves' in dic2.values() else 'valor não encontrado')
    ↳ Alves é um dos valores
    

    Diversos métodos são pré-programados com a classe dos dicionários. Vemos abaixo o uso de pop(), popitem() e copy(). Uma lista de métodos pode ser vista no final dessa seção.

    # o método update serve para alterar o valor atribuído a uma chave
    # e/ou inserir novos pares chave:valor
    » dic2.update({'idade':19, 'sexo':'masc'})
    » dic2
    ↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 19, 'sexo': 'masc'}
    
    # um par chave valor pode ser inserido diretamente (como já fizemos acima)
    » dic2['telefone'] = '21-991111110'
    » dic2
    ↳ {'nome': 'Pedro',
    ↳  'sobrenome': 'Alves',
    ↳  'idade': 19,
    ↳  'sexo': 'masc',
    ↳  'telefone': '21-991111110'}
    
    # um par pode ser removido pela chave
    » dic2.pop('sexo')
    » dic2
    ↳ {'nome': 'Pedro',
    ↳  'sobrenome': 'Alves',
    ↳  'idade': 19,
    ↳  'telefone': '21-991111110'}
    
    # o método popitem() remove o último item inserido (após versão 3.7)
    » dic2.popitem()
    ↳ ('telefone', '21-991111110')
    
    # dic2 fica alterado
    » dic2
    ↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 19}
    
    # cópias de dicionários
    # a atribuição abaixo atribui a dic3 o mesmo objeto que dic2
    » dic3 = dic2
    » dic3['idade'] = 67
    
    # dic2 fica alterado
    » dic2
    ↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 67}
    
    # para criar cópia independente (criando um novo objeto) usamos
    » dic4 = dic2.copy()
    # ou, usando o instanciador do objeto
    » dic4 = dict(dic2)
    
    # agora dic4 pode ser alterado sem afetar dic2
    » dic4['idade'] = 3
    » dic2
    ↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 67}
    

    No código acima vemos que a atribuição dic3 = dic2 simplesmente associa o mesmo objeto à variável dict3. Para gerar um novo objeto, que pode ser alterado independentemente, usamos o método dic4 = dic2.copy().

    Dicionários podem ser aninhados ou seja, um dicionário pode conter outros dicionários como itens.

    # o construtor pode receber diretamente os dicionários
    » irmaos = {
    »          1:{'nome':'Maria' , 'nasc':1989},
    »          2:{'nome':'Marcos' , 'nasc':1991},
    »          3:{'nome':'Marla' , 'nasc':2000},
    »          }
    
    # talvez seja mais legível definir separadamente (o que é equivalente)
    » irmao1 ={'nome':'Maria' , 'nasc':1989}
    » irmao2 = {'nome':'Marcos' , 'nasc':1991}
    » irmao3 ={'nome':'Marla' , 'nasc':2000}
    » irmaos = {1:irmao1, 2:irmao2,3:irmao3}
    
    # em qualquer dos casos ficamos com o dicionário
    » irmaos
    ↳ {1: {'nome': 'Maria', 'nasc': 1989},
    ↳  2: {'nome': 'Marcos', 'nasc': 1991},
    ↳  3: {'nome': 'Marla', 'nasc': 2000}}
    
    # para acessar a data de nascimento do segundo irmão fazemos
    » irmaos[2]['nasc']
    ↳ 1991
    # lembrando que, nesse caso, 2 não é um índice mas a chave do dicionário.
    

    Vamos usar um dicionário para contar quantas letras existem em uma palavra ou frase. Para isso criamos um dicionário vazio e iteramos pelas letras da palavra, usando a letra como índice e contagem como valor. A operação palavra.lower().replace(' ', '') transforma todas as letras em minúsculas e elimina espaços. set(palavra) pega as letras da palavra sem repetições. O método string.count(sub) conta quantas vezes a substring sub aparece na string.

    # dicionário (contar quantas letras há em uma palavra)
    » def letras(palavra):
    »     ''' Recebe parametro palavra (string)
    » 
    »     Retorna dicionário {letra: contagem}
    »     onde contagem é o número de letras = letra
    »     Todas as letras são transformadas em minúscula
    »     espaços são ignorados
    »     '''
    »     palavra = palavra.lower().replace(' ', '')
    »     contagem = {}
    »     for t in set(palavra):
    »         contagem[t] = palavra.count(t)
    »     return contagem
    
    # quais e quantas letras
    » dic = letras('Oftalmotorrinolaringologista') 
    » print(dic)
    ↳ {'o': 6, 'f': 1, 't': 3, 'a': 3, 'l': 3, 'm': 1, 'r': 3, 'i': 3, 'n': 2, 'g': 2, 's': 1}
    
    # quantas letras 't'?
    » dic['t']
    ↳ 3
    
    # espaços são ignorados, pois foram removidos
    » print(letras('Rio de Janeiro'))
    ↳ {'r': 2, 'i': 2, 'o': 2, 'd': 1, 'e': 2, 'j': 1, 'a': 1, 'n': 1}
    

    O método dictionary.get(chave, default) retorna o valor relativo à chave ou o valor default, caso a chave não seja encontrada.

    » dic.get('t',0)
    ↳ 3
    # não existe a letra 'b'
    » dic.get('b',0)
    ↳ 0
    

    Compreensão de dicionários: Dicionários podem ser construídos com compreensão:

    » x = {i : i+2 for i in range(5)}
    » x
    ↳ {0: 2, 1: 3, 2: 4, 3: 5, 4: 6}
    
    # outro exemplo
    » lista = ('a', 'céu de brigadeiro', 'de', 'prateado', 'laguna', 'introvertido', 'a' )
    » dicio = {p : len(p) for p in lista}
    » dicio
    ↳ {'a': 1,
       'céu de brigadeiro': 17,
       'de': 2,
       'prateado': 8,
       'laguna': 6,
       'introvertido': 12}
    

    Métodos dos dicionários (dictionary)

    Método Descrição
    clear() remove todos os elementos do dictionário
    copy() retorna uma cópia do dicionário
    fromchaves() retorna dicionário com chaves e valores especificados
    get() retorna o valor relativo a chave dada, ou valor default dado
    items() retorna uma lista contendo uma tupla para cada par chave:valor
    keys() retorna objeto iterável com as chaves do dicionário
    pop() remove o elemento relativo à chave especificada
    popitem() remove o último par chave:valor inserido
    setdefault() retorna o valor relativo à chave dada. Se a chave não existe insere chave:valor
    update() Atualiza o dicionário com pares chave:valor dados
    values() retorna objeto iterável os valores do dicionário

    Continue lendo: Arquivos e Pastas .

    🔺Início do artigo

    Bibliografia

    Consulte a bibliografia no final do primeiro artigo dessa série.

    Python: Testes, laços e funções


    Testes lógicos e Laços

    As linhas de código em um programa são executadas de cima para baixo, na ordem em que aparecem. Muitas tarefas podem ser executadas com esse esquema de coisas. No entanto a funcionalidade do código fica muito aumentada quando se insere os testes lógicos e laços que permitem a ramificação do fluxo de código sob certas condições.

    if, elif, else

    É possível alterar a ordem de execução de um programa usando testes e laços (loops). Para ver isso usaremos o comando input() que abre um caixa de interação com o usuário para ler um input de teclado. Em seguida um teste é realizado com a valor digitado pelo usuário, transformado em um inteiro, e ocorre uma ramificação na execução do código dependendo do valor inserido.

    » i = int(input('Digite um número: '))
    » if i > 10:
    »    print(i, 'é maior que 10.')
    » else:
    »    print(i, 'não é maior que 10!')
    
    ↳ Digite um número: 6
    ↳ 6 não é maior que 10!
    
    ↳ Digite um número: 12
    ↳ 12 é maior que 10.
    

    A sintaxe completa do teste lógico é:

    if bool_1:
        execute_1
    elif bool_2:
        execute_2
    ... outros elifs, se necessário
    else:
        execute_3
    

    Note que bool_1 e bool_2 devem ser valores booleanos (True ou False) ou expressões que são avaliadas como booleanos. As linhas execute_1 (2 ou 3) são quaisquer comandos ou sequência de comandos. Se bool_1 for True o código em execute_1 é executado e o teste é abandonado (nenhuma das demais linhas são executadas). Se bool_2 (após elif) for True o código em execute_2 é executado. Se nenhum dos testes resultar em True o código depois de else é executado. Tanto elif quanto else são opcionais.

    No Python os blocos de código são definidos por indentação (espaços ou tabs), e não por chaves {}, como é mais comum em outras linguagens de programação. É costume se usar 4 espaços, e não tabulações para marcar esse escopo. Outro exemplo:

    » i = int(input('Digite um número: '))
    » if i < 10:
    »    print(i, 'é menor que 10.')
    » elif i < 20:
    »    resposta = str(i) + 'é maior ou igual a 10 mas menor que 20.'
    »    print(reposta)
    » else:
    »    print(i, 'é maior ou igual a 20.')
    
    ↳ Digite um número: 17
    ↳ 17 é maior ou igual a 10 mas menor que 20!
    

    É importante notar que apenas o primeiro teste satisfeito é executado No caso, como o número digitado foi i = 17 o segundo teste foi satisfeito e as demais linhas ignoradas.

    O operador ternário também é útil para gerar código sintético e de fácil leitura. Ele tem a seguinte sintaxe:
    valor_1 if teste else valor_2. Ele retorna valor_1 se teste for verdadeira, valor_1 se falsa.

    # operador ternário
    » i = 3
    » texto = 'MAIOR que 5' if i > 5 else 'MENOR ou IGUAL a 5'
    » print(texto)
    ↳ MENOR ou IGUAL a 5
    

    O mesmo teste admite outra sintaxe que é menos usada mas aparece em alguns códigos. Ela pode ajudar a tornar mais legível o código, dependendo da situação. Ela tem a seguinte forma:
    (valor_0, valor_1)[teste], retornando valor_1 se teste == True ou valor_0 se teste == False.

    » for i in range(5):
    »     txt = str(i) + ' é ' + ('impar', 'par')[i % 2 == 0]
    »     print(txt)
        
    ↳ 0 é par
    ↳ 1 é impar
    ↳ 2 é par
    ↳ 3 é impar
    ↳ 4 é par
    

    No código acima o método str() transforma a variável de inteiro em texto para que possa ser concatenada com outras strings. O teste verifica se o resto da divisão de i por 2 é zero, ou seja, se i é par ou não.

    Observe que o resultado acima é apenas uma aplicação do seguinte fato: True é avaliado como 1, False como 0.

    » lista = ['valor_1','valor_2','outros_valores']
    » print(lista[0], lista[1])
    ↳ valor_1 valor_2
    » print(lista[False], lista[True])
    ↳ valor_1 valor_2    
    

    O teste pode ser aninhado. No entanto, se o código ficar muito muito complexo ou difícil de ler, pode ser mais simples quebrar a operação em partes mais simples.

    » for i in range(5,11):
    »     txt = str(i) + (' é par', (' não é múltiplo de 3',' é múltiplo de 3')[i%3==0] )[i%2==1]
    »     print(txt)
    ↳ 5 não é múltiplo de 3
    ↳ 6 é par
    ↳ 7 não é múltiplo de 3
    ↳ 8 é par
    ↳ 9 é múltiplo de 3
    ↳ 10 é par
    # se não for par é retornado ('não é múltiplo de 3','é múltiplo de 3')[i%3==0]
    

    Testes lógicos compostos podem ser implementados com o uso dos operadores lógicos and, or e not:

    » x = 'oup'
    » if x in 'Guarda' or x in 'roupas':
    »     print('ok!')
    ↳ ok!
    

    Intervalos podem ser testados de uma única vez. Por exemplo a < x ≤ b retorna True se x está no intervalo (a, b] (que exclui a e inclui b).

    » for i in range(1, 8):
    »     if 0 < i ≤ 3:
    »         print(i, 'pertence [1, 3]')
    »     elif 3 < i ≤ 6:
    »         print(i, 'pertence [4, 6]')
    »     else:
    »         print(i, ' acima de 6')
    
    ↳ 1 pertence [1, 3]
    ↳ 2 pertence [1, 3]
    ↳ 3 pertence [1, 3]
    ↳ 4 pertence [4, 6]
    ↳ 5 pertence [4, 6]
    ↳ 6 pertence [4, 6]
    ↳ 7  acima de 6
    

    Dicionários e decisões

    Essa seção contém técnicas um pouco mais avançadas usando dicionários e funções. Se você não está familiarizado com elas pule para a seção Laços for e while. Você também pode consultar a seção sobre dicionários em Sequências e Coleções.

    Quando existem muitos testes no código, principalmente quando valores individuais são verificados (em oposição à intervalos), pode ser uma boa técnica usar um dicionário como seletor de valores. Esse dicionário deverá conter os valores de testes nas chaves e retornos nos valores.

    » # ao invés de fazer 10 testes
    » for i in range(11):
    »     if i==0:
    »         print('zero')
    »     elif i==1:
    »         print('um')
    »     elif i==2:
    »         print('dois')
    » # ... etc ... (truncado)
            
    » # podemos fazer
    » num_texto = {0:'zero', 1:'um', 2:'dois',3:'três', 4:'quatro',
    »              5:'cinco',6:'seis',7:'sete',8:'oito',9:'nove'}
    » for i in range(11):
    »     print(i, num_texto.get(i,'não encontrado!'))
    ↳ 0 zero
    ↳ 1 um
    ↳ 2 dois
    ↳ 3 três
    ↳ 4 quatro
    ↳ 5 cinco
    ↳ 6 seis
    ↳ 7 sete
    ↳ 8 oito
    ↳ 9 nove
    ↳ 10 não encontrado!
    

    Lembrando: O método de dicionário num_texto.get(i, default) procura pela chave i e retorna o valor correspondente. Se a chave não for econtrada o valor default é retornado.

    Além disso um dicionário pode conter, em suas chaves, funções. Isso significa que podemos selecionar entre diversas funções sem usar seletores if.

    # bloco 1
    » dicio = {1: lambda x: x**2, 2: lambda x: x**3+4}
    » print(dicio[1](3))
    » print(dicio[2](3))
    ↳ 9
    ↳ 31
    
    # bloco 2
    » import math as m
    » dicio2 = {1: m.sin, 2: m.exp}
    » print(dicio2[1](3))
    » print(dicio2[2](3))
    ↳ 0.1411200080598672
    ↳ 20.085536923187668
    
    # bloco 3
    » dicio = {'a': 'print("estou em a")', 'b': 'print("estou em b")'}
    » eval(dicio['a'])
    » eval(dicio['b'])
    ↳ estou em a
    ↳ estou em b
    

    No bloco 1 o dicionário retorna funções anônimas (lambda):
    dicio[1](3) é o mesmo que (lambda x: x**2)(3) = 3**2 =9.
    No bloco 2 o dicionário retorna funções do módulo importado math:
    dicio2[1](3) é o mesmo que math.sin(3) = 0.141...
    No bloco 3 o dicionário retorna um texto que pode ser executado com eval:
    dicio['a'] = 'print("estou em a")',
    uma string, que quando executada como comando, imprime estou em a.

    O código lambda x: x**2 corresponde à função matemática \( x \mapsto x^2\). As funções anônimas (lambda) serão vistas mais tarde nessas notas [ funções lambda].

    Laços for e while


    Outra estrutura útil de controle do fluxo de execução do código são os laços for. Eles servem para percorrer valores dentro de um objeto iterável.

    # dentro do loop letra = P, y, t, h, o, n, sucessivamente
    » for letra in 'Python':
    »    if letra == 'y':
    »        print('Chegamos na letra y')
    ↳ Chegamos na letra y
    

    A variável letra assume os valores P, y, t, h, o, n sucessivamente mas apenas quando a letra é y uma mensagem é impressa. Observe os oito espaços abaixo do teste if para representar uma dupla indentação.

    É comum que uma operação tenha que ser repetida um determinado número de vezes. Isso pode ser feito criando um objeto iterável e percorrendo seus valores.

    # primeiro percorremos uma tupla
    » for t in (1,3,5,7,9):
    »    print(t, end=', ')
    ↳ 1, 3, 5, 7, 9,
    
    # criamos uma 'faixa' de números (range) de 0 a 10, exclusive
    » for t in range(10):
    »     print(t, end=', ')
    ↳ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    

    Nos dois casos acima usamos o parâmetro end para a função print para que ela não insira uma quebra de linha após cada impressão, que é o comportamento default. Podemos usar a instrução else para inserir código a ser executado no final do loop.

    » alunos = ['Martha', 'Renato', 'Paula']
    » for aluno in alunos:
    »      print(aluno, end=", ")
    » else:
    »      print("\b\b.\nTodos os nomes foram listados!")
    
    ↳ Martha, Renato, Paula. 
    ↳ Todos os nomes foram listados!
    

    A instrução print("\b\b.\nTodos...") faz o retrocesso de dois caracteres, substitui a vírgula final por um ponto e pula para a proxima linha, antes de imprimir Todos….


    Instruções break, continue e pass são usadas dentro de laços para alterar o fluxo de execução. break interrompe o laço, continue instrui pela continuação do laço e passserve apenas para marcar uma posição nas linhas de código, sem executar nenhuma tarefa. No exemplo abaixo o laço for percorre números de 0 até 99. O laço é abandonado quando num = 50.

    » for num in range(100):
    »     if num < 50:
    »         continue
    »     else:
    »         break
    » print(num)
    ↳ 50
    

    Também são permitidos laços while que executam operações enquanto uma condição for verdadeira. No exemplo abaixo inicializamos a variável i = 1 e percorremos o laço enquanto i < 10. Dentro do laço a variável é impressa e incrementada de 1. O caracter especial “\n” serve para que uma quebra de linha seja inserida.

    » i = 1
    » while i < 10:
    »     print(i, end=' ')
    »     i += 1
    » print('\nFim do loop. i =', i)
    ↳ 1 2 3 4 5 6 7 8 9 
    ↳ Fim do loop. i = 10
    

    Não é raro criarmos um laço infinito a ser abandonado sob condição dada.

    » num = 0
    » while True:
    »     num +=1
    »     if num == 50:
    »         print('cinquenta')
    »     if num >= 100:
    »         break
    » print(num)
    ↳ cinquenta
    ↳ 100
    

    Advertência importante: Sempre que se usa laços deve-se ter certeza de que o laço termina após um número finito de iterações. Caso contrário a execução do código entra em um loop infinito que pode exigir uma interrupção forçada. Por exemplo, o código abaixo entra em um loop infinito pois a variável t, dentro do loop, nunca assume o valor t=5, que é a condição de saída do laço.

    » t = 0
    
    » while t ! = 5:
    »     t +=2
    »     print(t, end='-')
    ↳ 2-4-6-8-10
    # execução interrompida pelo usuário
    

    Para interromper esse loop você pode apertar CTRL-C se estiver em sessão interativa do Python. No Jupyter Notebook aperte ■ na barra de ferramentas ou I-I com o cursor no modo de controle da célula.

    Funções


    Uma outra alternativa para alterar o fluxo de execução do código são as funções. Sempre que um bloco de código (um conjunto de operações) deve ser executado várias vezes esse bloco pode ser colocado dentro de uma função. As funções podem ser muito úteis para reaproveitamente do código dentro do módulo em que se está trabalhando ou mesmo em outros módulos diferentes. Funções são criadas e executadas da seguinte forma:

    # definição
    » def nome_da_funcao(argumentos):
    »     operações # usando os argumentos
    »     return valor
    # para usar essa função
    » nome_da_funcao(argumentos)
    

    Nessa definição argumentos são um ou diversos objetos de quaisquer tipo que contém as informações que serão usadas no corpo da função. As operações podem conter instruções de qualquer tipo, tais como a impressão de texto, de leitura ou gravação de arquivos, ou o cálculo de valor a ser retornado pela função. A instrução return é opcional: se ela não for incluída a função retorna None.

    » def minha_funcao(numero1, numero2):
    »     resultado = numero1 ** numero2
    »     return resultado
    
    # para calcular 23
    » minha_funcao(2,3)
    ↳ 8
    
    # a chamada à função pode ser usada dentro de um cálculo
    » minha_funcao(2,10) - 24
    ↳ 1000
    


    Na figura ilustramos as partes de uma função. Parâmetros são as variáveis definidas e usadas no corpo da função. Os valores passados como parâmetros são os argumentos. No caso do uso da raiz quadrada seria necessária a importação da biblioteca math, na forma de from math import sqrt antes do uso.

    Os argumentos (passados como parâmetros) das funções podem ter um valor default. Os parâmetros com valor default devem sempre ser inseridos por último, na definição da função.

    » def outra_funcao(numero1, numero2 = 3):
    »     return numero1 ** numero2
    
    # se numero2 for omitida ela assume o valor default
    » outra_funcao(6,)      # 63
    ↳ 216
    
    # se numero2 for fornecida o valor default é sobrescrito
    » outra_funcao(6,2)    # 62
    ↳ 36
    

    Parâmetros podem ser usados como palavras chaves (keywords). Nesse caso eles podem aparecer em qualquer ordem, desde que nomeados durante a chamada à função.

    » def funcao(a, b):
    »     return 10 * a + b
    
    # chamando a função sem usar palavras chaves
    » funcao(10, 5)
    ↳ 105
    
    # usando as palavras chaves
    » funcao(b = 2, a = 11)
    ↳ 112
    

    Finalmente, se pode passar um número arbitrário de argumentos passando um objeto iterável para a função. Isso é útil quando se escreve uma função que deve agir sobre um número desconhecido de argumentos. Isso é feito colocando-se um asterisco (*) antes do parâmetro.

    » def somar(*numeros):
    »     soma = 0
    »     for num in numeros:
    »         soma += num
    »     return soma
    
    # exemplo de uso
    » somar(1,34,67,23,876)
    ↳ 1001
    

    Recursividade

    Função fatorial: para consolidar o conceito de função vamos considerar o exemplo de uma função para calcular o fatorial de um número inteiro. Por definição o fatorial de n é
    $$
    n! = \bigg\{ \begin{array}{lr}
    1, & \text{ se n = 0,}\\
    1 \times 2 \times 3 \times \cdots \times n, & \text{ se n > 0.}\\
    \end{array}
    $$

    » def fatorial(n):
    »     fat = 1
    »     while n > 1:
    »         fat *= n
    »         n -= 1
    »     return fat
    
    » fatorial(11)
    ↳ 39916800
    

    Lembrando, n -= 1 é o mesmo que n = n - 1 e fat *= n é fat = fat * n. O loop while só é executado se n = 2 ou maior. Dentro do loop a variável é decrementada de 1 e o produto armazenado em fat.

    Uma definição mais elegante pode ser obtida usando recursividade. A função pode fazer uma chamada a si mesma.

    » def fatorial_recursivo(n):
    »     if n ≤ 1:
    »         return 1
    »     else:
    »         return n * fatorial_recursivo(n-1)
    
    » fatorial_recursivo(3)
    ↳ 6 
    

    A mesma função pode ser definida de forma ainda mais compacta usando-se a estrutura valor1 if teste_booleano else valor2.

    » def fatorial(n):
    »     return 1 if n <=1 else n * fatorial(n-1)
    
    » print(fatorial(10))
    ↳ 3628800
    

    No entanto, é muito comum que uma função de muito uso já esteja incluída em alguma biblioteca do Python, como é o caso da função fatorial que está na biblioteca math. Para usar um recurso que está em uma biblioteca externa ao núcleo básico do Python usamos import.

    » import math
    » math.factorial(11)
    ↳ 39916800
    

    A série de Fibonacci é outro exemplo interessante. Essa é uma série de inteiros com muitas aplicações práticas. Ela é definida da seguinte forma: denotando o n-ésimo termo da série por \(f_n \) temos

    $$f_0=1, f_1=1, f_n = f_{n-1} + f_{n-2} \text{ para } n \geq 2.$$

    A série de Fibonacci tem muitas aplicações práticas interessantes

    Portanto, cada número da série, à partir de n = 2 é a soma dos dois números anteriores. Por ex,: \(f_4 = f_3 + f_2 \).

    Para calcular digamos, \(f_{10}\) podemos calcular todos os elementos da série até obter \(f_8\) e \(f_9\) e calcular sua soma. Alternativamente podemos definir uma soma recursiva.

    » def fibonacci(n):
    »     """ retorna o n-ésimo termo da série de Fibonacci """
    »
    »     if n in (0, 1):
    »         return 1
    »     else:
    »         return fibonacci(n-1) + fibonacci(n-2)
    
    » # para imprimir a série até um inteiro n
    » def printFibonacci(n):
    »     txt = ''
    »     for t in range(n):
    »         txt += '%d, ' % fibonacci(t)
    »     print('Série de Fibonacci: { %s...}' % txt)
    
    » printFibonacci(10)
    ↳ Série de Fibonacci: { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...}
    

    Essa função também calcula todos os elementos anteriores recursivamente, até chegar em n=0 e n=1, o que termina a recursão, mas de um modo elegante e de fácil leitura. Esses procedimentos podem envolver grande quantidade de cálculo e memória. Tanto na série de Fibonacci quanto no calculo do fatorial um valor muito grande para argumento das funções pode travar o processador por falta de memória.

    Observe que, da forma como esta função foi escrita para obter a série de Fibonacci, para calcular o n-ésimo termo é necessário calcular todos os termos anteriores. Uma forma forma mais rápida e eficiente pode ser obtida da seguinte forma:

    » # para calcular os 10 primeiros termos da série de Fibonacci, retornando uma lista
    » quantosTermos=10
    » a, b = 0, 1
    » fib = []
    » for t in range(quantosTermos):
    »     fib.append(b)
    »     a, b = b, a+b
    
    » print(fib)
    ↳ [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
    

    Nesse código usamos a função range(n) que retorna um objeto iterável (que pode ser percorrido um a um), de 0 até n-1. fib = [] inicializa uma lista vazia e fib.append(b) insere b nessa lista.

    Gravando e importando funções

    Podemos gravar em disco as funções e recuperá-las para uso no código. Suponha que gravamos o seguinte conteúdo em um arquivo com nome primo.py.

    # arquivo primo.py    
    def primo(n):
        """
        recebe um inteiro n
        retorna True se n é primo, caso contrário False
        """
    
        if n==0: return False        # uma condição especial para retornar 0, não primo
        primo = True
        for i in range(2, n//2):
            if (n % i) == 0:
                primo = False        # n é divisivel por i
                break
        return primo
    

    No código acima procuramos por divisores de n de 2 até n//2 (metade inteira de n). Depois de gravado ele pode ser lido com import primo (o que importa todo o conteúdo do arquivo ou módulo) ou from primo import primo, que importa a função específica primo(). Depois podemos usá-la no codigo, com por ex.:

    » from primo import primo
    » while True:
    »     n = int(input("Digite um número (0 para terminar)"))
    »     if n==0: break
    »     print(n, 'é primo' if primo(n) else 'não é primo')
    

    Esse código inicia um laço que só termina quando o usuário digita 0. Se não for 0 ele testa se número é primo usando a função importa, e emite uma mensagem.

    Digamos que digitamos 4 e 5:

    Digite um número (0 para terminar) 4
    4 não é primo
    Digite um número (0 para terminar) 5
    5 é primo
    

    Docstrings

    No Python você pode, e deve, inserir strings de documentação (docstrings) em suas funções para informar o que ela faz, que parâmetros recebe e que tipo dado ela retorna. Essa documentação pode ser consultada no momento em que se pretende usar a função. Docstrings também são inseridos em módulos, classes e métodos do Python.

    A documentação de um objeto é definida inserindo-se uma string na primeira linha de sua definição, como um comentário.

    def uma_funcao(parametros):
        ''' Essa função recebe parâmetros tal e tal; e retorna qual
        '''
        < corpo de função >
        return qual
    

    Docstrings podem ser visualizadas com a propriedade __doc__ do objeto.

    » def quadrado(n):
    »     '''Recebe um número n, retorna n ao quadrado.'''
    »     return n**2
    
    » print(quadrado.__doc__)
    ↳ Recebe um número n, retorna n ao quadrado
    

    Por convenção docstrings devem começar com uma letra maiúscula e terminar com um ponto e a primeira linha deve conter uma breve descrição da função. Caso existam mais de uma linha a segunda deve ficar em branco, separando o resumo do resto da descrição. As demais linhas devem descrever as convenções de chamada da função, os parâmetros e seu retorno. Ela também deve explicitar as exceções que podem ser lançadas na sua execução.

    As funções pré definidas no python, assim como as importadas, podem igualmente ter suas docstrings consultadas. Docstrings também podem ser consultadas com a função help().

    Abaixo um exemplo de uma docstring bem estruturada, e o uso de help.

    » def soma_inteiros(i, j):
    »     '''
    »     Retorna a soma de dois inteiros.
    » 
    »             Parâmetros:
    »                     i (int): inteiro
    »                     j (int): inteiro
    »             Retorna:
    »                     string com texto: A soma i + j = soma.
    »     '''
    »     txt = 'A soma %d + %d = %d'% (i,j,i+j)
    »     return txt
    
    » soma_inteiros(45,54)
    
    ↳ 'A soma 45 + 54 = 99'
    
    » help(soma_inteiros)
    
    ↳ Help on function soma_inteiros in module __main__:
    
      soma_inteiros(i, j)
          Retorna a soma de dois inteiros.
        
                  Parâmetros:
                          i (int): inteiro
                          j (int): inteiro
        
                  Retorna:
                          string com texto: A soma i + j = soma.
    

    Além de documentar o código, doscstrings podem conter testes para as suas funções correspondentes, como veremos mais tarde.

    No Jupyter Notebook a doscstring é exibida pressionando-se Shift-Tab após o nome da função ou objeto, o que pode ser feito para funções internas ou aquelas criadas pelo usuário.

    Gerenciando erros (exceções)

    Naturalmente cometemos erros quando escrevemos código. Nesses casos o python gera uma resposta que pode nos ajudar a corrigir o problema. No python erros são denominados exceções.

    Suponha que executamos o seguinte código:

    » x = input('Digite um número: ')
    » y = x + 10
    » print('O valor de y é %d' % y)
    # o usuário insere o dígito 4 e obtém a resposta
    ↳ x = input('Digite um número: ')
     ----> y = x + 10
           print('O valor de y é %d' % y)
     TypeError: can only concatenate str (not "int") to str
    


    Um erro de tipo foi lançado, TypeError pois o resultado do comando input é uma string (um texto) mesmo que o usuário tenha digitado ‘4’. O erro ocorreu na linha que tenta somar uma string a um número. Vemos que a resposta a um erro é útil e informa onde ele ocorreu. Muitas vezes são dadas sugestões de possíveis reformas ou até mesmo indicações para os manuais. No caso acima esse erro poderia ser resolvido com a inserção da conversão x = int(input('Digite um número: ')) que converteria a string ‘4’ em um dígito 4. Mas, o problema não estaria resolvido se o usuário resolver digitar ‘a’, por exemplo.

    # a string 'a' não pode ser convertida em um inteiro
    » int('a')
    ↳ ValueError: invalid literal for int() with base 10: 'a'
    

    Esse tipo de problema pode ser contornado com as cláusulas try, except, else, finally. Coloque o comando onde um erro pode ocorrer dentro de uma cláusula try.

    » saida = ''    
    » x = input('Digite um número: ')
    » try:
    »     x = int(x)
    » except:
    »     saida = 'Ocorreu um erro.Digite um número, não letra.'
    » else:
    »     y = x + 10
    »     saida = 'O valor da soma é %d' % y
    » finally:
    »     print(saida)
    

    Havendo erro o fluxo de execução é transferido para except, caso contrário para else. Em qualquer dos casos o código após finally é executado.

    # o usuário digita 4:
    ↳ Digite um número: 4
    ↳ O valor da soma é 14
    
    # o usuário digita a:
    ↳ Digite um número: a
    ↳ Ocorreu um erro.Digite um número, não letra.
    

    Existem muitos erros de execução pré-definidos no python. Suponha que exista uma função previamente definida, onde erros de alguns tipos podem ocorrer:

    » try:
    »     execute_alguma_coisa()
    » except SyntaxError:
    »     print('Ocorreu um erro de sintaxe')
    » except TypeError:
    »     print('Erro de tipo de variável!')
    » except ValueError:
    »     print('Um erro de valor!')
    » except ZeroDivisionError:
    »     print('Não é permitido dividir por zero!')
    » else:
    »     print('Relaxa, nada errado aconteceu!')
    » finally:
    »     print('Chegamos ao final...')    
    

    Erros podem ser lançados a força pelo programador, para fins de depuração ou outro motivo qualquer.

    » raise MemoryError("Acabou a memória")
    ↳ MemoryError: Acabou a memória
    
    » raise ValueError("Erro de valor")
    ↳ ValueError: Erro de valor
    

    Continue a leitura: Sequências e Coleções.

    🔺Início do artigo

    Bibliografia

    Consulte a bibliografia no final do primeiro artigo dessa série.

    Python: Strings


    Strings

    Strings são variáveis usadas para armazenar texto. No Python elas são sequências, coleções de caracteres ordenada por posição. A manipulação de strings é um dos pontos fortes do python e existem diversas funções para isso.

    Strings são definidas com aspas simples (') ou aspas duplas ("). Aspas triplas (''' ou """) podem ser usadas para definir strings longas. Strings dentro de parentêses são concatenadas, o que é útil para se escrever uma string longa sem perder a legibilidade.

    » string_1 = 'casa'
    » string_2 = ' da mãe Joana'
    » print(string_1 + string_2)
    ↳ casa da mãe joana
    
    » string_longa = '''
                 este é um
                 exemplo de uma
                 string longa!
                 '''
    » print(string_longa)
    ↳              este é um
                   exemplo de uma
                   string longa!
    
    # string concatenada com parênteses (quebras de linhas são ignoradas)
    » outra_longa = (
         'https://phylos.net/'
         '2020-12-16/'
         'pacote-factoranalyser/'
         )
    
    » print(outra_longa)
    ↳  'https://phylos.net/2020-12-16/pacote-factoranalyser/'
    

    Quando se usa aspas triplas, (''' ou """), os espaços e quebras de linha são mantidos na string final. Esse recurso é muito utilizado da documentação de módulos e funções, como veremos.  Já nas strings concatenadas com parênteses espaços e quebras de linha são removidos.

    Vamos ver alguns exemplos de strings e de suas manipulações. Aqui usamos print(string, end=' ') para exibir a valor de string sem a quebra de linha que é default para essa função. Nesse caso a quebra é substituída por end=’ ‘, um espaço em branco.

    # a sequência pode ser percorrida
    » for letra in "banana":
    »     print(letra, end=' ')
    ↳ b a n a n a 
    
    # definimos uma variável de string
    » palavra = 'Pindamonhangaba!'
    
    # len() fornece seu comprimento (quantos caracteres)
    » len(palavra)
    ↳ 16
    # um caracter pode ser obtido por seu índice
    » print(palavra[0], palavra[15])
    ↳ P !
    # índices negativos contam do fim para o início
    » print(palavra[-1])
    ↳ !
    » print(palavra[-2])
    ↳ a
    
    # palavra[-n] equivale a palavra[len(palavra)-n]  (desde que n < len(palavra))
    » palavra[len(palavra)-3]   # palavra[-3]
    ↳ 'b'
    
    # uma 'fatia' (slice)
    » palavra[1:4]
    ↳ 'ind'
    
    # se o segundo índice for omitido a fatia vai até o fim
    » palavra[5:]
    ↳ 'monhangaba!'
    
    # se o primeiro índice for omitido a fatia começa no início
    » palavra[:5]
    ↳ 'Pinda'
    
    » palavra[:]
    ↳ 'Pindamonhangaba!'
    
    # len(palavra[i:j]) = j-i
    » len(palavra[1:4])
    ↳ 3
    

    Vimos nos exemplos acima que uma fatia (ou slice) palavra[i:f] se inicia em i e termina em f, exclusive. Os índices, e todas as contagens em python se iniciam em 0. A posição final não é incluida de forma que len(palavra[i:j]) = j-i.

    Os operadores + e * (adição e multiplicação) tem significado especial quando os operandos são strings. + promove a concatenação de strings e * retorna uma repetição.

    # Operações com palavras
    » p1 = 'casa'
    » p2 = 'da mãe Joana'
    
    # '+' faz uma concatenação
    » p1 + ' ' + p2
    ↳ 'casa da mãe Joana'
    
    # '*' repete a string
    » p1 * 5
    ↳ 'casacasacasacasacasa'
    

    Alguns operadores lógicos também são permitidos.

    » palavra1 = 'teste'
    » palavra2 = 'Teste'
    
    » palavra1 == palavra2   # 2 sinais de =
    ↳ False
    
    » palavra1 != palavra2
    ↳ True
    
    » 't' in palavra1
    ↳ True
    
    » 'y' not in palavra1
    ↳ True
    

    Observe que nenhuma das operações realizadas até agora afetam o conteúdo da variável. Ao contrário, cada uma delas produz uma nova string. Dizemos que strings são imutáveis no python. Como são imutáveis, strings não podem ser alteradas in loco. No exemplo abaixo, para alterar ‘rato’ para ‘pato’ é necessário construir nova string e atribuí-la à variável.

    # Strings são imutáveis
    » str = 'rato'
    » str[0]
    ↳ 'r'
    
    # a tentativa de alterar a string resulta em erro
    » str[0] = 'p'
    ↳ TypeError: 'str' object does not support item assignment
    
    # para trocar 'r' por 'p'
    » str = 'p' + str[1:]
    » str
    ↳ 'pato'
    

    Métodos das strings

    Claro que o leitor não precisa se preocupar em memorizar todas essas funções. Uma primeira leitura serve para se ter conhecimento das possibilidades.

    Como objetos, as strings possuem métodos e propriedades (coisas que veremos com detalhes mais tarde). A lista abaixo contém alguns métodos.

    Método descrição
    capitalize() converte 1º caracter em maiúsculo
    casefold() converte string em minúsculas
    center() retorna string centralizada
    count() retorna número de ocorrências de um valor especificado na string
    endswith() retorna True se string termina com valor especificado
    find() busca por valor especificado na string e retorna a posição se encontrado
    format() Formata de acordo com valores especificados (detalhes abaixo)
    index() busca por valor especificado na string e retorna the posição se encontrado
    isalnum() retorna True se todos os caracteres são alfa-numéricos
    isalpha() retorna True se todos os caracteres são alfabéticos
    isdecimal() retorna True se todos os caracteres são decimais
    isdigit() retorna True se todos os caracteres são dígitos
    islower() retorna True se todos os caracteres são minúsculos
    isnumeric() retorna True se todos os caracteres são numéricos
    isspace() retorna True se todos os caracteres são espaços
    istitle() retorna True se a string segue regra de títulos
    isupper() retorna True se todos os caracteres são maiúsculos
    join() reune elementos de um iterável no final da string
    ljust() retorna a string justificada à esquerda
    lower() converte a string para minúsculas
    lstrip() retorna a string sem espaços à esquerda
    partition() retorna tuple partindo a string em 3 partes
    replace() substitui trecho da string por outro especificado
    rfind() busca trecho especificado value e retorna última posição
    rindex() busca trecho especificado value e retorna última posição
    rjust() retorna string justificada à direita
    rsplit() quebra a string no separador especificado, retornando lista
    rstrip() retorna a string sem espaços à direita
    split() quebra a string no separador especificado, retornando lista
    splitlines() quebra a string nas quebras de linha, retornando lista
    startswith() retorna True se string começa com valor especificado
    strip() retorna a string sem espaços laterais
    swapcase() inverte minúsculas e maiúsculas
    title() converte em maiúscula o 1º caracter de cada palavra
    upper() converte a string em maiúsculas
    zfill() preencha com número de zeros especificado, no início

    Alguns exemplos de uso são apresentados a seguir.

    » str = 'uma palavra qualquer'
    # tornar a primeira letra maiúscula
    » str.capitalize()
    ↳ 'Uma palavra qualquer'
    
    # contar quantas letras 'a' existem na string
    » str.count('a')
    ↳ 5
    
    # a string termina com 'r'?
    » str.endswith('r')
    ↳ True
    
    # em que posição 'qu' está na string
    » str.find('qu')
    ↳ 12
    
    # se o trecho procurado não existe na string
    » str.find('y')
    ↳ -1
    
    » str.isnumeric()
    ↳ False
    
    » '1234567890'.isnumeric()
    ↳ True
    
    » str.replace('palavra','coisa')
    'uma coisa qualquer'
    
    » str.startswith('un')
    ↳ False
    
    # join é usada para concatenar strings
    » uma_tupla = ('Carro', 'Casa', 'Gado')
    » x = '-'.join(uma_tupla)
    » print(x)
    ↳ Carro-Casa-Gado
    
    # exemplo de uso para join, construindo uma tabela html
    » html = '<table><tr><td>'
    » html += '</td><td>'.join(uma_tupla)
    » html += '</td></tr></table>'
    » print(html)
    ↳ <table><tr><td>Carro</td><td>Casa</td><td>Gado</td></tr></table>
    
    # lembrando que x = 'Carro-Casa-Gado'
    » for txt in x.split('-'):
    »     print(txt)
    ↳ Carro
      Casa
      Gado
    
    # quebrando uma frase em palavras
    » frase = 'A segunda palavra da frase'
    » frase.split(' ')[1]
    ↳ 'segunda'
    

    No exemplo abaixo str é quebrada nos espaços, pegando cada palavra em separado. O primeiro caso imprime a palavra que começa com ‘r’ e termina com ‘a’. O segundo imprime a palavra que começa com ‘p’ e não termina com ‘a’.

    » str = 'pato pata rato rata'
    
    » for palavra in str.split(' '):
    »     if palavra.startswith('r') and palavra.endswith('a'):
    »         print(palavra)
    ↳ rata
    
    » for palavra in str.split(' '):
    »     if palavra.startswith('p') and not palavra.endswith('a'):
    »         print(palavra)
    ↳ pato
    

    Existem alguns mecanismos de apoio à formatação de strings, particularmente útil na impressão de resultados. Os sinais especiais %d e %s podem ser usados na montagem de strings, o primeiro como substituto de um dígito, o segundo de texto. %f informa a substituição por número de ponto flutuante, sendo %.nf  um número de ponto flutuante com n casas decimais. %E indica número com notação científica (usando potências de 10).

    » s1 = 'Uma string com um dígito e texto: dígito: %d; texto: %s' % (10, 'dez')
    » print(s1)
    ↳ Uma string com um dígito e texto: dígito: 10; texto: dez
    
    print('Um inteiro convertido em ponto flutuante: %f' % 5)
    ↳ Um inteiro convertido em ponto flutuante: 5.000000
    
    print('Exibir o número %.02f com 2 casas decimais' % 25.123456)
    ↳ Exibir o número 25.12 com 2 casas decimais
    
    print('Um número com notação científica: %E' % 6.789E7)
    ↳ Um número com notação científica: 6.789000E+07
    

    Um método mais moderno de formatação consiste um usar string.format(), que permite código mais legível.

    # marcadores nomeados são supridos por format
    » txt1 = "Eu me chamo {nome} e tenho {idade} anos.".format(nome = 'João', idade = 36)
    » print(txt1)
    ↳ Eu me chamo João e tenho 36 anos.
    
    # os lugares podem ser marcados numericamente
    » txt2 = "Moro em {0}, {1} há {2} anos.".format('Brasília', 'DF', 20)
    » print(txt2)
    ↳ Moro em Brasília, DF há 20 anos.
    
    # ou marcados apenas por sua ordem de aparecimento
    » txt3 = "{} são convertidos em {}. Exemplo: {}.".format('Dígitos', 'strings', 100)
    » print(txt3)
    ↳ Dígitos são convertidos em strings. Exemplo: 100.
    
    # podemos controlar do número de casas decimais exibidos
    » txt4 = 'Esse livro custa R$ {preco:.2f} com desconto!'.format(preco = 49.8)
    » print(txt4) 
    ↳ Esse livro custa R$ 49.80 com desconto!
    

    Também existe o chamado método de interpolação de strings que usa o prefixo f para que a string possa incorporar variáveis.

    » nome = 'Albert'
    » sobrenome = 'Einstein'
    » ano = 1879
    » print(f'O físico {nome} {sobrenome} nasceu em {ano}')
    ↳ O físico Albert Einstein nasceu em 1879
    
    # podemos inserir operações dentro da string de formatação
    » a = 214
    » b = 3
    » print(f'{a} multiplicado por {b} é {a * b}.')
    ↳ 214 multiplicado por 3 é 642.
    

    Observe que strings justapostas são concatenadas. No entanto isso gera um código menos claro. O método de interpolação torna o código de mais fácil entendimento.

    print('Paul ''Adrien ''Maurice ''Dirac' ' nascido em ' '1902')
    Paul Adrien Maurice Dirac nascido em 1902
    
    fisico = 'P. A. M. Dirac'
    ano = 1902
    print(f'O físico {fisico} nascem em {ano}' )
    O físico P. A. M. Dirac nascem em 1902
    

    Outros exemplos com métodos de strings

    Seguem mais alguns exemplos de uso. Os outputs aparecem como comentários.

    # um string de teste
    original = 'Pedro, Paulo,   Jonas'
    
    # split retorna uma lista com as partes
    original.split(',')                      # ['Pedro', ' Paulo', '   Jonas']
    
    # a compreensão de lista com strip para eliminar espaços
    partes = [x.strip() for x in original.split(',')]
    partes                                   # ['Pedro', 'Paulo', 'Jonas']
    
    # join juntas as partes
    ' : '.join(partes)                       # 'Pedro : Paulo : Jonas'
    
    # testes de pertencimento
    'Jonas' in original     # True
    'Jo' in original        # True
    'X' in original         # False
    
    original.index('lo')    # 10
    original.index('X')     # ValueError substring not found
    
    original.find('Pa')     # 7
    original.find('Pu')     # -1
    
    original.count('P')     # 2
    
    original.replace('o','a')                # 'Pedra, Paula,   Janas'
    

    Outras operações mais sofisticadas com strings podem ser realizadas com expressões regulares. Sobre esse assunto você pode ler:

    Caracteres de escape

    Caracteres especiais podem ser inseridos em uma string por meio de “escapes”. Por exemplo, para usar aspas em uma string podemos fazer o seguinte:

    # inserindo aspas simples em string
    » print('O cara se chama O\'Maley')
    ↳ O cara se chama O'Maley
    
    # se aspas duplas são usadas como delimitador a aspa simples pode ser usada
    » print("O cara se chama O'Maley")
    ↳ O cara se chama O'Maley
    
    » print("E seu primeiro nome é \"George\"")
    ↳ E seu primeiro nome é "George"
    
    # quebra de linha
    » print('linha1\nlinha2')
    ↳ linha1
    ↳ linha2
    
    código significado
    \’ aspas simples
    \” aspas duplas
    \\ barra invertida
    \n nova linha
    \r retorno de “carro”
    \t tab, tabulação
    \b backspace, volta 1 espaço
    \f form feed
    \ooo valor octal
    \xhh valor hexadecimal
    🔺Início do artigo

    Continue a leituira: Python, testes, laços e funções

    Bibliografia

    Consulte a bibliografia no final do primeiro artigo dessa série.

    Python: Introdução

    Programação

    Um computador é uma máquina eletrônica com capacidade para armazenar valores numéricos em formato binário e instruções de manipulação desses valores armazenados. Informações não numéricas, como letras, palavras e imagens, precisam ser convertidas em binários para que possam ser tratados. Nos primeiros tempos da computação a interação com as máquinas era muito difícil e um programador tinha que escrever códigos muito complexos, como diretamente em linguagem de máquina ou assembly. Hoje, após muito aperfeiçoamento, as linguagens modernas abstraem as camadas mais complexas de interação com o computador. O código tem a aparência de uma linguagem natural humana e pode ser lido quase como um texto comum.

    Um exemplo pode ilustrar essa descrição. Considere que uma pessoa possui uma caixa cheia de bolas pretas e brancas. Ela quer saber qual é a porcentagem de bolas pretas, no total. Uma possível solução seria o seguinte procedimento, usando um bloco de notas para registrar bolas pretas e brancas:

    1. tire uma bola da caixa,
    2. verifique se é preta,
    3. se for preta faça uma marca para bolas pretas, caso contrário para as brancas,
    4. sem retornar a bola, verifique se ainda restam bolas na caixa,
    5. se ainda restam bolas, volte para a etapa (1),
    6. se acabaram as bolas, conte quantas marcas foram feitas para cada cor,
    7. calcule porcentagem = pretas/(pretas+brancas)*100 e anote esse resultado.

    Esse procedimento é chamado de algoritmo. A lista de tarefas é análoga à um programa de computador. As marcas feitas para contagem das bolas brancas e pretas são análogas às variáveis do programa.

    Jupyter

    O Python pode ser executado de várias formas diferentes. Existem bons editores e IDEs (Integrated Devolopment Environment) tais como o VSCode, Pycharm, Geany ou Spyder. Para esse tutorial usaremos o Jupyter Notebook. Para maiores informações sobre o Jupyter leia nesse site sobre sua instalação e execução.

    Resumindo, sua instalação pode ser feita instalando-se o Anaconda:

    • Baixe a versão mais recente do Anaconda aqui. Existem instaladores gráficos para Linux, Windows e MacOS.
    • Instale seguindo as instruções na página de download ou contidas no arquivo instalador executável.
    • ou … usuários que conhecem Python e que preferem gerenciar seus pacotes manualmente, pode apenas usar:
      pip3 install jupyter.

    Python

    Python é uma linguagem de programação de alto nível, interpretada e de propósito geral, criada por Guido van Rossum em 1985 e em franco desenvolvimento desde então. Ela está disponível sob a licença GPL (GNU General Public License). Ela permite o uso interativo, com o usuário digitando as linhas de código e obtendo o resultado imediatamente, ou através de lotes (batches), com as linhas de código armazenadas em arquivos e executadas em grupo. Apesar de ser chamada de linguagem de script é possível criar aplicativos completos, na web ou para desktop, com interfaces gráficas modernas e eficientes. Além disso existe a possibilidade de gerar arquivos compilados e executáveis usando Cython.

    Python Package Index

    Uma grande quantidade de bibliotecas ou módulos que podem ser importados e executados nas sessões do python o tornam atraente para o gerenciamento de bancos de dados, de imagens, a mineração de dados, análise e plotagem sofisticada, inteligência artificial e aprendizado de máquina, além de várias outras aplicações. Você pode procurar por bibliotecas em The Python Package Index (PyPI).

    Python é uma linguagem orientada a objetos (um conceito a ser estudado mais tarde) e todos de seus elementos são objetos. Seus conceitos básicos são de rápido aprendizado, apesar da existência das várias bibliotecas para uso específico que podem ser um pouco mais complexas, como ocorre com o pandas.

    Variáveis e operações básicas

    Convenção: Usaremos nesse texto a seguinte convenção: linhas iniciadas com » marcam o código inserido para execução. No Jupyter Notebook são as linhas de código dentro das células. marca os outputs, as linhas de resposta do código. Linhas, ou trechos de linha, iniciadas com # são comentários não executados.

    # Essa linha é um comentário    
    » x = 15        # inserindo um inteiro
    » print(x)
    ↳ 15
    

    Você pode usar o python para fazer operações matemáticas como um calculadora, embora isso raramente seja algo muito útil. Mais interessante é a possibilidade de criar variáveis que armazenam dados. No python uma variável é criada no momento em que se atribui a ela um valor, sem necessidade de informar de que tipo será (o que chamamos de tipagem dinâmica).

    # inicializamos duas variáveis numéricas e uma com uma palavra
    » x = 15           # um número inteiro
    » y = 4.2          # um número de ponto flutuante
    » z = 'Pedro'      # uma palavra ou 'string'
    
    # para ver o conteúdo das variáveis podemos usar o método print
    » print(x)
    ↳ 15
    » print(y)
    ↳ 4.2
    » print(z)
    ↳ Pedro
    
    # podemos ver de que tipo são essas variáveis
    » print(type(x))
    ↳ <class 'int'>
    
    » print(type(y))
    ↳ <class 'float'>
    
    » print(type(z))
    ↳ <class 'str'>
    
    # variáveis podem ser apagadas com
    » del x
    » print(x)
    ↳ NameError: name 'x' is not defined 
    

    O comando print() é uma função interna do python, e serve para exibir o conteúdo de seu parâmetro, no caso as variáveis x, y, z. Quando usamos o Jupyter Notebook uma variável ou expressão avaliada na última linha de uma célula é exibida quando a célula é executada, sem a necessidade de print.

    Usamos type() para ver o tipo de conteúdo armazendo. Os tipos básicos do Python são:

    Tipo Exemplos Nome no Python
    Inteiros 1, -1, 67, -9900 int
    Inteiros longos 123L, -999L long integer
    Ponto flutuante 1.0, 3.14, 345.6, -99.9 double float
    Complexos complex(1, 3), complex(“1+2j”), 1 + j complex
    Booleano True, False bool
    String ‘casa’, “teste de string” string, object

    Com esses tipos básicos se constroi objetos mais complexos, como

    Tipo Exemplos Nome no Python
    Lista [‘a’, ‘b’, ‘c’, ‘d’], [1, 2, 3] list
    Tupla (‘a’, ‘b’, ‘c’, ‘d’), (1, 2, 3) tuple
    Dicionário {1: ‘um’, 2: ‘dois’} dictionary

    Qualquer objeto do python pode ser associado a uma variável, que passa a ser uma representante ou “referência de acesso” a esse objeto. Os nomes de variáveis devem obedecer a algumas regras:

    • só podem conter números, letras e sublinhados (underscores): A até z, 0 até 9, _,
    • deve começar com uma letra ou sublinhado,
    • maiúsculas e minúsculas são diferenciadas.

    A diferenciação de maiúsculas de minúsculas é válida em toda parte do Python. As variáveis idade, Idade, IDADE são três variáveis diferentes. A instrução if, para definir um ponto de bifurcação condicional no código, não ser escrita como If ou IF. Além disso existem as palavras reservadas (keywords) que são nomes que fazem parte da sintaxe da linguagem e não podem ser utilizadas para nomes de variáveis:

    Palavras reservadas do python
    and del for lambda raise
    as elif from None return
    assert else global nonlocal True
    break except if not try
    Class exec import or while
    continue False in pass with
    def finally is print yield

    Como assumem o lugar dos objetos as variáveis podem ser usadas em lugar deles. Por ex., a operação abaixo faz um cálculo usando 2 variáveis e atribuindo o valor à uma terceira.

    » largura = 2.20
    » altura = 1.10
    » area_retangulo = largura * altura
    » print("A área de retângulo é: " , area_retangulo)
    ↳ A área de retângulo é: 2.42
    

    Para facilitar a leitura posterior de um código os nomes de variáveis devem ser escolhidos de forma concisa e elucidativa de seu uso.

    Os seguintes operadores matemáticos formam a base para as operações numéricas usuais.

    Operador significado exemplo numérico
    + Adição x + y 24 + 36 = 60
    Subtração x – y 36 – 24 = 12
    * Multiplicação x * y 15 * 3 = 45
    ** Exponentiação x ** y 8 ** 3 = 512
    / Divisão x / y 15 / 8 = 1.875
    // Divisão inteira x // y 15 // 8 = 1
    % Módulo (resto da divisão inteira) x % y 15 % 8 = 7

    Para verificar seu comportamento vamos fazer alguns testes.

    # no python se pode declarar 2 variáveis (ou mais) simultaneamente †
    » x, y = 3, 8
    » x + y
    ↳ 11
    » x - y
    ↳ -5
    » x * y
    ↳ 24
    » y ** x
    ↳ 512
    » x / y
    ↳ 0.375
    # 3 dividido por 8 é 0, com resto 3
    » x % y
    ↳ 0
    » x // y
    ↳ 3
    
    Estritamente dizendo, a atribuição x, y = 3, 8 é feita como em uma atribuição de tupla,
    (x, y) = (3, 8). Leia Sequências e Coleções.

    Terminologia: Um comando ou declaração (statement, em inglês) é uma linha com uma instrução única. Uma atribuição do tipo x = 1 armazena o valor 1 na variável x. No código abaixo temos 4 declarações. As 3 primeiras são atribuições e print() é uma função interna (predefinida) do Python:

    » incremento = 0.2
    » valor = 10
    » valor = valor + incremento
    » print(valor)
    ↳ 10.20
    

    Várias declarações podem ser dispostas em uma única linha, separadas por ;. Por outro lado uma declaração longa pode ser quebrada em várias linhas.

    # várias declarações em uma linha
    » a=10; b=20; c=a*b; print(c)
    ↳ 200
    
    # Uma string longa em várias linhas
    » mensagem = "Olá.\nVocê está aqui para aprender a programar em Python.\n" \
    »            "Leia o material e faça seus testes." \
    »            "Indicar esse site para os seus amigos."
    
    # Uma declaração longa em várias linhas 
    » soma = 1 + 2 + 3 + 4 + \
    »        5 + 6 + 7 + 8 + \
    »        9 + 10
     
    » print(mensagem)
    ↳ Olá.
      Você está aqui para aprender a programar em Python.
      Leia o material e faça seus testes. Indicar esse site para os seus amigos.
    
    » print(soma)
    ↳ 55
    # No texto acima usamos \n que é um código para quebra de linha. 
    

    Diferente das operações matemáticas usuais, x = x + 1 significa some 1 ao valor de x e substitua o resultado em x.
    Um atalho ou shortcut possível e muito usado é o seguinte:

    # ao invés de escrever
    » i = i + 1                  # podemos escrever, como o mesmo efeito
    » i += 1                     # portanto
    » valor = valor + incremento # pode ser escrito como
    » valor += incremento
    

    Outros operadores análogos podem ser usados.

    Operador Exemplo Equivale a
    += Soma a += 2 a = a + 2
    -= Subtração a -= 2 a = a – 2
    *= Multiplicação a *= 2 a = a * 2
    /= Divisão a /= 2 a = a / 2
    %= Módulo a %= 2 a = a % 2
    **= Expoente a **= 2 a = a ** 2
    //= Divisão inteira a //= 2 a = a // 2

    Além dos operadores matemáticos temos os operadores lógicos. Os resultados de operações lógicas são booleanos (True ou False) e eles são a base das decisões feitas em códigos. Alguns desses operadores podem ser usados, como veremos, em outros objetos que não apenas números.

    Operador Significado Exemplo
    > maior que x > y
    >= maior ou igual a x ≥ y
    < menor que x < y
    >= menor ou igual a x ≤ y
    == igual a x == y
    != diferente de x != y

    Exemplos de uso de operações lógicas:

    » num1 = 12
    » num2 = 25
    
    » print(num1 > num2)
    ↳ False
    
    » print(num2 == num1 *2 +1)
    ↳ True
    
    » string1 = 'isso'
    » string2 = 'esse'
    » print(string1 != string2)
    ↳ True
    

    Execução do código em python


    Foi sugerido o uso do Jupyter Notebook para o aprendizado do conteúdo nesse artigo. Isso permite interatividade e facilidade de instalação dos pacotes, bem como uma boa visualização das saídas de código. No entanto muitas vezes é necessário rodar blocos grandes de código sem preocupação com resultados e saídas intermediárias.

    Arquivos contendo comandos python e gravados com a extensão *.py podem ser executados em qualquer máquina que tenha o interpretador instalado. Por exemplo, suponha que temos na pasta de trabalho ativa o arquivo teste.csv e queremos alterar o seu nome. Usando um editor ASCII qualquer, como o Notepad do Windows, Geany ou Gedit no Linux e TextEdit no Mac, gravamos um arquivo com o nome de alterarNome.py, com o seguinte conteúdo:

    #!/usr/bin/env python
    import os
    for arq in os.listdir():
        print(arq)
    nome_antigo = 'teste.csv'
    nome_novo = 'teste_novo.csv'
    os.rename(nome_antigo, nome_novo)
    

    Para executar esse arquivo abrimos uma sessão do terminal e digitamos:

    » python alterarNome.py
    

    A primeira linha contém o shebang (#!) com a instrução de onde está instalado o python em seu computador. Depois é importada a biblioteca os que contém comandos de interação com o sistema operacional. Seu método os.listdir() retorna uma lista com os arquivos na pasta atual, que são impressos dento do laço for. Se existir na pasta um arquivo teste.csv ele será renomeado para teste_novo.csv. Uma mensagem de erro será exibida se o arquivo teste.csv não existir.


    Strings do Python

    🔺Início do artigo

    Bibliografia

    Python.org

    Livros

    • Barry, Paul: Head First Python, O’Reilly, Sebastopol, 2011.
    • Ceder, Vernon: The Quick Python Book, 2 ed., Manning Publications Co., Greenwich, 2010.
    • Downey, Allen: How to Think Like a Computer Scientist, Cambridge University Press, Nova Iorque, 2009.
    • Hall, Tim; Stacey, J-P: Python 3 for Absolute Beginners, Apress, Nova Iorque, 2009.
    • Hetland, Magnus Lie: Beginning Python, From Novice to Professional, Apress, Nova Iorque, 2005.
    • Lambert, Kenneth: Fundamentals of Python: From First Programs Through Data Structures, Cengage, Boston, 2010.
    • Lee, Kent D.: Python Programming Fundamentals, Springer-Verlag, Londres, 2011.
    • Lutz, Mark:Learning Python, 4ª Edição, O’Reilly, Sebastopol, 2005.
    • Payne, James: Beginning Python, Using Python 2.6 and Python 3.1, Wiley, Indianoplis, 2010.
    • Summerfield, Mark: Programming in Python 3, a complete introduction to the Python language, Pearson, Boston, 2010.

    Recursos na internet

    Pandas – Dataframes


    Dataframes do pandas

    Usamos a marcação:
    » # linhas de comentários
    » linhas de código (input)
    ↳ linhas de output

    Um dataframe é uma forma de armazenar dados em forma tabular, como em uma planilha. O dataframe do pandas consiste em uma coleção de Series que são dispostas como suas colunas. A cada linha está associado um índice que serve para ordenar e selecionar dados. Como Series, cada coluna tem um tipo definido. No entanto, não é necessário que todas as colunas tenham o mesmo tipo e portanto dados de tipos diferentes podem ser armazenados.

    O pandas usa muitos dos conceitos de programação do NumPy. A diferença principal entre eles está em que um array do NumPy usa dados homogêneos (todos do mesmo tipo) enquanto os dataframes do pandas podem conter dados de tipos diferentes.

    Assim como o NumPy, muitas operações com dataframes levam em consideração o eixo ou axis. O default é axis = 0 (ou axis = 'index') o que indica operação sobre as linhas. axis = 1 (ou axis = 'column') indica operação realizada sobre as colunas.

    O método mais comum de se criar um dataframe consiste em passar um dicionário e uma lista de índices para o construtor.

    » import pandas as pd
    » import numpy as np
    » dados = {
                'nome': ['Pedro', 'Maria', 'Janaina', 'Wong', 'Roberto', 'Marco', 'Paula'],
                'cidade': ['São Paulo', 'São Paulo', 'Rio de Janeiro', 'Brasília',
                           'Salvador', 'Curitiba', 'Belo Horizonte'],
                'idade': [34, 23, 32, 43, 38, 31, 34],
                'nota': [83.0, 59.0, 86.0, 89.0, 98.0, 61.0, 44.0]
              }
    
    » ids = [10, 11, 12, 13, 14, 15, 16]
    » dfAlunos = pd.DataFrame(data=dados, index=ids)
    ↳ 
    

    nome cidade idade nota
    10 Pedro São Paulo 34 83.0
    11 Maria São Paulo 23 59.0
    12 Janaina Rio de Janeiro 32 86.0
    13 Wong Brasília 43 89.0
    14 Roberto Salvador 38 98.0
    15 Marco Curitiba 31 61.0
    16 Paula Belo Horizonte 34 44.0

    No caso acima usamos um dict onde as chaves são os nomes dos campos ou colunas. À cada chave está associada uma lista cujos valores se tornam os valores das linhas, em cada coluna. A lista de índices foi fornecida separadamente. Se a lista ids não tivesse sido fornecida os índices do dataframe seriam inteiros, começando em 0.

    Dataframes possuem a propriedade shape que contém as dimensões do objeto e os métodos head(n) e tail(n) que permitem, respectivamente, a visualização das n primeiras ou últimas linhas. Ao carregar um dataframe é sempre útil visualizar suas primeiras linhas e nomes de colunas. Também pode ser útil visualizar a matriz sob forma transposta, dada por dfAlunos.T.

    » dfAlunos.shape
    ↳ (7, 4)
    » # o que significa que temos 7 linhas, com 4 campos ou colunas.
    
    » # para visualizar apenas as 2 primeiras linhas
    » dfAlunos.head(2)
    ↳
    
    nome cidade idade nota
    10 Pedro São Paulo 34 83.0
    11 Maria São Paulo 23 59.0
    # para visualizar apenas as 2 últimas linhas
    » dfAlunos.tail(2)
    ↳
    
    nome cidade idade nota
    15 Marco Curitiba 31 61.0
    16 Paula Belo Horizonte 34 44.0
    # A transposta:
    » dfAlunos.T
    ↳
    
    10 11 12 13 14 15 16
    nome Pedro Maria Janaina Wong Roberto Marco Paula
    cidade São Paulo São Paulo Rio de Janeiro Brasília Salvador Curitiba Belo Horizonte
    idade 34 23 32 43 38 31 34
    nota 83 59 86 89 98 61 44

    Os nomes das colunas podem ser obtidos em uma lista, em um nome específico. Devemos nos lembrar que cada coluna do dataframe é uma Series. Portanto valem para elas os métodos e propriedades das Series.

    » dfAlunos.columns
    ↳ Index(['nome', 'cidade', 'idade', 'nota'], dtype='object')
    
    » # O nome da segunda coluna (lembrando que se conta a partir de 0)
    » dfAlunos.columns[1]
    ↳ 'cidade'
    » # Selecionando a coluna 'cidade'
    » dfAlunos['cidade']
    ↳  10         São Paulo
       11         São Paulo
       12    Rio de Janeiro
       13          Brasília
       14          Salvador
       15          Curitiba
       16    Belo Horizonte
       Name: cidade, dtype: object
    
    » # cada coluna é uma Series
    » type(dfAlunos['cidade'])
    ↳ pandas.core.series.Series
    
    » # os métodos das Series se aplicam
    » dfAlunos['cidade'].value_counts()
    ↳  São Paulo         2
       Curitiba          1
       Rio de Janeiro    1
       Belo Horizonte    1
       Salvador          1
       Brasília          1
       Name: cidade, dtype: int64
    
    » # valores únicos podem ser obtidos com unique()
    » dfAlunos['cidade'].unique()
    ↳ array(['São Paulo', 'Rio de Janeiro', 'Brasília', 'Salvador', 'Curitiba',
             'Belo Horizonte'], dtype=object)
    
    » # também podemos transformar esses valores em um set
    » set(dfAlunos['cidade'])
    ↳ {'Belo Horizonte',
       'Brasília',
       'Curitiba',
       'Rio de Janeiro',
       'Salvador',
       'São Paulo'}
    


    Observe que dfAlunos['cidade'] retorna uma Series, que é a coluna especificada do DF. Já o comando dfAlunos[['cidade']] retorna um dataframe com uma única coluna. É sempre importante saber com que tipo de objeto estamos lidando. Para isso podemos usar type() para conhecer esse tipo. Por exemplo, type(dfAlunos[['cidade']]) retorna pandas.core.frame.DataFrame . Observe que strings são listadas apenas como objects (sem discriminação de serem strings).

    Também se pode usar a notação de ponto, dfAlunos.cidade, para obter a mesma coluna.

    Como dissemos, o objeto DataFrame do pandas é formado por colunas que são Series, cada uma delas contendo elementos do mesmo tipo. As linhas podem, portanto, conter elementos de tipos diferentes. Para ver os tipos de cada coluna podemos examinar a propriedade dtype ou o método .info() que fornece uma visão geral sobre os dados, inclusive sobre a existência de valores nulos nos dados.

    » dfAlunos.dtypes
    ↳ nome       object
      cidade     object
      idade       int64
      nota      float64
      dtype: object
    
    » # Uma visão geral sobre os dados pode ser obtido com .info()
    » dfAlunos.info()
    ↳ <class 'pandas.core.frame.DataFrame'>
      Int64Index: 7 entries, 10 to 16
      Data columns (total 4 columns):
       #   Column  Non-Null Count  Dtype
      ---  ------  --------------  -----
       0   nome    7 non-null      object
       1   cidade  7 non-null      object
       2   idade   7 non-null      int64
       3   nota    7 non-null      float64
      dtypes: float64(1), int64(1), object(2)
      memory usage: 600.0+ bytes
    

    A descrição estatística dos campos numéricos é obtida com .describe() que fornece a contagem de itens, o valor médio, o desvio padrão, os quantis e máximos e mínimos. O método .corr() fornece o Coeficiente de Correlação de Pearson para todas as colunas numéricas da tabela. O resultado é um número no intervalo [-1, 1] que descreve a relação linear entre as variáveis.

    # describe: resumo estatístico dos campos numéricos
    » dfAlunos.describe()
    ↳
    
    idade nota
    count 7.000000 7.000000
    mean 33.571429 74.285714
    std 6.187545 19.661420
    min 23.000000 44.000000
    25% 31.500000 60.000000
    50% 34.000000 83.000000
    75% 36.000000 87.500000
    max 43.000000 98.000000
    » dfAlunos.corr()
    ↳ 
    
    idade nota
    idade 1.000000 0.564238
    nota 0.564238 1.000000

    Para acrescentar uma ou mais linhas (registros) ao dataframe podemos criar um novo dataframe com quantas linhas forem necessárias e concatená-lo com o antigo usando o método .concat().

    » # criamos dataframe para a aluna Juliana e seus dados
    » dfInserir = pd.DataFrame([('Juliana','Curitiba',28,80.0)],
                                 columns=['nome','cidade','idade','nota'],
                                 index=[100])
    » pd.concat([dfAlunos, dfInserir])
    ↳
    
    nome cidade idade nota
    10 Pedro São Paulo 34 83.0
    11 Maria São Paulo 23 59.0
    12 Janaina Rio de Janeiro 32 86.0
    13 Wong Brasília 43 89.0
    14 Roberto Salvador 38 98.0
    15 Marco Curitiba 31 61.0
    16 Paula Belo Horizonte 34 44.0
    100 Juliana Curitiba 28 80.0

    Observe que o dataframe original não foi modificado. Caso se pretenda que modificação se torne permanente você deve atribuir o resultado retornado a uma novo (ou o mesmo) dataframe, como em dfAlunos = pd.concat([dfAlunos, dfInserir]).

    Muitas vezes queremos que a novo dataframe criado ignore os índice das duas tabelas concatenadas. Nesse caso podemos ignorar os índices antigos e substituí-los por novos índices fornecidos, ou deixar que sejam preenchidos automaticamente.

    » df = pd.concat([dfAlunos, dfInserir], ignore_index=True)
    » df.index
    ↳ RangeIndex(start=0, stop=8, step=1)
    » # os índices são inteiros de 0 até 8 (exclusive)
    

    Uma nova coluna pode ser inserida, inclusive usando valores obtidos nas linhas. Na operação abaixo inserimos o campo calculado que é igual à multiplicação dos campos nota * idade, que não tem significado e é feito apenas como demonstração.

    » dfAlunos['calculado']=dfAlunos['nota'] * dfAlunos['idade']
    » dfAlunos
    ↳
    
    nome cidade idade nota calculado
    10 Pedro São Paulo 34 83.0 2822.0
    11 Maria São Paulo 23 59.0 1357.0
    12 Janaina Rio de Janeiro 32 86.0 2752.0
    13 Wong Brasília 43 89.0 3827.0
    14 Roberto Salvador 38 98.0 3724.0
    15 Marco Curitiba 31 61.0 1891.0
    16 Paula Belo Horizonte 34 44.0 1496.0

    Como essa nova coluna não tem nenhum significado vamos apagá-la usando .drop().

    # a operação seguinte retorna o dataframe sem a coluna 'calculado', mas não altera a original
    » dfAlunos.drop(['calculado'], axis=1)
    » # para alterar o dataframe usamos o parâmetro inplace=True
    » dfAlunos.drop(['calculado'], axis=1, inplace=True)
    

    Agora o dataframe tem a mesma estrutura de colunas original. Muitas operações do pandas retornam o resultado sobre o objeto sem alterá-lo. Algumas delas admitem o parâmetro inplace que, se True, faz a alteração do objeto in loco.

    Para selecionar mais de uma coluna passamos uma lista com os nomes dos campos entre os colchetes.

    » lista = ['nome','idade']
    » # a linha abaixo é idêntica à dfAlunos[['nome','idade']]
    » dfAlunos[lista]
    ↳
    
    nome idade
    10 Pedro 34
    11 Maria 23
    12 Janaina 32
    13 Wong 43
    14 Roberto 38
    15 Marco 31
    16 Paula 34


    Podemos obter somas dos termos, tanto no sentido das linhas quanto das colunas, o que servirá como exemplo do uso do parâmetro axis. Relembrando:

    axis = 0 (axis = ‘index’) opera sobre todas as linhas de cada coluna
    axis = 1 (axis = ‘column’) opera sobre todas as colunas de cada linha

    Para mostrar isso vamos construir um dataframe contendo apenas os dados numéricos, com os campos ‘idade’ e ‘nota’. Em seguida aplicamos sum(axis=0) para obter a soma das idades e notas, e sum(axis=1) para a soma
    de cada linha.

    » dfNumerico=dfAlunos[['idade', 'nota']]
    » # a soma dos elementos em cada coluna
    » dfNumerico.sum()   # o mesmo que dfNumerico.sum(axis=0)
    ↳ idade         235.0
      nota          520.0
      dtype: float64
    
    » # a soma dos elementos em cada linha
    » dfNumerico.sum(axis=1)
    ↳ 10    117.0
      11     82.0
      12    118.0
      13    132.0
      14    136.0
      15     92.0
      16     78.0
      dtype: float64
    

    Importando um arquivo externo

    É comum que os dados estejam inicialmente em forma de texto com os dados gravados em linhas e com valores separados por vírgula (um arquivo csv, comma separated values) ou outros separadores, tais como tabulação ou ponto e vírgula (;). Também ocorre que a exportação de outros aplicativos, como o Excel, possa ser feita nesse formato ou ser nele convertido.

    Suponha que tenhamos no disco, na pasta de trabalho de sua sessão, um arquivo com o seguinte conteúdo:

        id, nome, cidade, idade, nota
        10, Pedro, São Paulo, 34, 83.0
        11, Maria, São Paulo, 23, 59.0
        12, Janaina, Rio de Janeiro, 32, 86.0
        13, Wong, Brasília, 43, 89.0
        14, Roberto, Salvador, 38, 98.0
        15, Marco, Curitiba, 31, 61.0
        16, Paula, Belo Horizonte, 34, 44.0
    

    Não é importante que as colunas estejam bem alinhadas. Para importar esses dados para dentro de um dataframe usamos o método do pandas .read_csv(arq), onde arq é o nome completo do arquivo a ser lido (inclusive com seu caminho).

    » dfNovoAlunos = pd.read_csv('./alunos.csv')
    » dfNovoAlunos
    ↳
    
    id nome cidade idade nota
    0 10 Pedro São Paulo 34 83.0
    1 11 Maria São Paulo 23 59.0
    2 12 Janaina Rio de Janeiro 32 86.0
    3 13 Wong Brasília 43 89.0
    4 14 Roberto Salvador 38 98.0
    5 15 Marco Curitiba 31 61.0
    6 16 Paula Belo Horizonte 34 44.0

    Vemos que o campo ‘id’ foi lido como um campo comum. Ele pode ser transformado em um pindice id efetivo por meio do método dataFrame.set_index:

    » # torne o campo id o índice
    » dfNovoAlunos.set_index('id', inplace=True)
    » dfNovoAlunos.head(2)
    ↳ 
    
    nome cidade idade nota
    id
    10 Pedro São Paulo 34 83.0
    11 Maria São Paulo 23 59.0

    Observe que o índice é apresentado na segunda linha do cabeçalho para indicar que não é um campo comum.

    Alternativamente podemos ler o arquivo csv usando diretamente a primeira coluna como índice, informado pelo parâmetro index_col. Se o arquivo não contiver vírgulas separando os campos e sim outro sinal qualquer, como ; ou tabulações, passamos essa informação usando o parâmetro sep. Na última importação usamos url, a URL completa do arquivo, que pode estar em qualquer ponto disponivel da rede.

    » # para usar a 1a coluna como índice
    » dfNovoAlunos = pd.read_csv('./alunos.csv', index_col=0)
    » # para ler arquivo em url, usando tab como separador
    » dfOutroDF = pd.read_csv(url, sep='\t')
    

    Vimos que, se nenhum argumento for passado, a primeira linha do arquivo é tomada como contendo os nomes (ou headers) das colunas. Para evitar isso passamos o parâmetro header = None. Nesse caso o nome das colunas é substituído por números.

    Suponha que o arquivo nums.csv, com o conteúdo abaixo, esteja gravado no disco.

        11,12,13,14
        21,22,23,24
        31,32,33,34

    Ele pode ser lido da seguinte forma:

    » # informa que 1a linha não é header
    » dfNone = pd.read_csv('./dados/nums.csv', header=None)
    
    » # inserindo o nome ou labels para as colunas
    » dfNames = pd.read_csv('./dados/nums.csv', names=('A', 'B', 'C', 'D'))
    
    » # exibe os dois dataframes
    » display('sem headers:', dfNone, 'com headers:', dfNames)
    ↳
    

    ‘sem headers:’

    0 1 2 3
    0 11 12 13 14
    1 21 22 23 24
    2 31 32 33 34

    ‘com headers:’

    A B C D
    0 11 12 13 14
    1 21 22 23 24
    2 31 32 33 34

    Finalmente, se o cabeçalho contendo os títulos das colunas não está na primeira linha podemos passar o parâmetro header=n. A n-ésima linha será tomada como cabeçalho e todas as linhas anteriores serão ignoradas.

    » dfPula2 = pd.read_csv('./dados/nums.csv', header=2)
    

    Importação de csv com linhas de comprimentos desiguais

    Pode ocorrer que um arquivo importado precise de alguma forma de tratamento para a montagem adequada de suas colunas, quando importado em um dataframe. Suponha, por exemplo, que temos em disco um arquivo de texto como o seguinte conteúdo:

    palavras
    a;1;2;3;6;7;9;10;17;121;131;138;252;463
    aba;146
    abafa;125;127;129
    abaixado;1;125;127;130
    abastecer;121;146;150;250;354;358
    abatido;1;124;125;127;129;130;140;143;358;360
    aberto;13;22;125;126;131;132;138;11;250;252
    abismei;125;126
    abra;14;22;125;126;131;132;138;11;250;252
    abriga;125;126;131;137
    acabrunha;143;150
    acalmar;1;124;125;126;140;142;143;253
    acaso;125;126;131;135;253
    

    Essa é uma seleção muito pequena, para fim de demonstração da técnica, do extenso dicionário LIWC que associa palavras da língua portuguêsa à um conjunto de códigos associados à características linguísticas e cognitivas. Queremos classificar palavras de acordo com esse dataset.

    Não podemos importar diretamente esses dados usando pd.read_csv(arquivo, sep=';') pois o comprimento irregular dos códigos geraria erro na importação. Então nos resta importar todas as linhas em uma coluna e tratá-las depois, de modo adequado.

    # o arquivo csv está em    
    arquivo='./dados/liwc.csv'
    # importamos com separador que não existe no texto para obter apenas 1 coluna
    dfPalavras = pd.read_csv(arquivo, sep='#')
    
    dfPalavras
        palavras
    0   a;1;2;3;6;7;9;10;17;121;131;138;252;463
    1   aba;146
    2   abafa;125;127;129
    3   abaixado;1;125;127;130
    4   abastecer;121;146;150;250;354;358
    5   abatido;1;124;125;127;129;130;140;143;358;360
    6   aberto;13;22;125;126;131;132;138;11;250;252
    7   abismei;125;126
    8   abra;14;22;125;126;131;132;138;11;250;252
    9   abriga;125;126;131;137
    10  acabrunha;143;150
    11  acalmar;1;124;125;126;140;142;143;253
    12  acaso;125;126;131;135;253
    
    # cada linha pode se quebrada em ; retornando listas
    dfPalavras.palavras.str.split(';').head(3)
    
    0    [a, 1;2;3;6;7;9;10;17;121;131;138;252;463]
    1                                    [aba, 146]
    2                          [abafa, 125;127;129]
    

    O próprio método split pode ser usado com os parâmetros str.split(sep, n, expand), que quebra a string em sep, usando apenas as primeiras n partes, e montando um dataframe se expand=True. Montamos um dataframe vazio df, separamos as linhas em 2 partes e as aplicamos em df, com as partes palavra e codigo separadas.

    df = pd.DataFrame({})
    df[['palavra','codigo']] = dfPalavras.palavras.str.split(';', n=1, expand=True)
    # o seguinte dataframe é gerado
    df
        palavra      codigo
    0   a            1;2;3;6;7;9;10;17;121;131;138;252;463
    1   aba          146
    2   abafa        125;127;129
    3   abaixado     1;125;127;130
    4   abastecer    121;146;150;250;354;358
    5   abatido      1;124;125;127;129;130;140;143;358;360
    6   aberto       13;22;125;126;131;132;138;11;250;252
    7   abismei      125;126
    8   abra         14;22;125;126;131;132;138;11;250;252
    9   abriga       125;126;131;137
    10  acabrunha    143;150
    11  acalmar      1;124;125;126;140;142;143;253
    12  acaso        125;126;131;135;253
    
    # podemos encontrar as palavras associadas ao codigo = 127
    df[df.codigo.str.contains('127')]['palavra']
    2       abafa
    3    abaixado
    5     abatido
    
    # ou os codigos associados à palavra 'abafa'
    df[df.palavra == 'abafa']['codigo'].values[0]
    '125;127;129'
    

    Na última linha df[df.palavra == 'abafa']['codigo'] é uma Series. Para extrair seu valor usamos .values[0]. Nesse caso o número de códigos é muito varíavel e não seria prático construir uma coluna para cada código.

    Se for necessário ter colunas correspondendo à cada código teríamos que preencher os campos vazio com algum valor, como 0 ou NaN.

    Gravando o dataframe em arquivos pickle

    Ao términdo de cada fase de depuração, manipulação e ajustes do dataframe, podemos gravá-lo em disco em um arquivo pickle. O arquivo gravado pode ser lido e o dataframe recuperado em nova sessão de código, sem que tenhamos que repetir as etapas já realizadas.

    » pd.to_pickle(dfNovoAlunos, './dados/Alunos.pkl')
    » del dfNovoAlunos
    » dfLido = pd.read_pickle('./dados/Alunos.pkl')
    

    dfLido será um dataframe idêntico ao dfNovoAlunos gravado em etapa anterior. A pasta de destino deve existir ou uma exceção será lançada.

    to_pickle Grava um objeto do pandas em arquivo pickled
    read_pickle Ler arquivo pickle recuperando objeto
    DataFrame.to_hdf Grava um objeto do pandas em arquivo HDF5
    read_hdf Ler arquivo hdf recuperando objeto
    DataFrame.to_sql Grava dataframe em um banco de dados sql
    read_sql Ler arquivo sql recuperando objeto
    DataFrame.to_parquet Grava dataframe em formato parquet binário.
    read_parquet Ler arquivo parquet recuperando objeto
    🔺Início do artigo

    Bibliografia

    Consulte bibliografia completa em Pandas, Introdução neste site.

    Nesse site:

    Introdução ao Pandas – Series

    🔻Final do artigo

    O que é pandas?

    Pandas é uma biblioteca do Python, de código aberto e com licença BSD, desenvolvida e mantida pelo PуDаtа Dеvеlорmеnt Tеаm. Ela fornece ferramentas de manipulação e análise estatística de dados, capacidade de exibição gráfica E extração de dados análogos (mas não idênticos) aos de consultas sql.

    A biblioteca foi construída com Cython e, por isso, é bastante rápida. Ela se destinava inicialmente ao uso no setor financeiro para análise de séries temporiais, tendo se tornado uma ferramenta de uso comum na manipulação de dados, particularmente em data science e machine learning. Ela tem sido usada para substituir as planilhas do Excel, para processar dados sob forma tabular, importando com facilidade dados de arquivos csv ou json.

    Os experimentos abaixo foram realizados no Jupyter Notebook. Você encontra nesse site um artigo sobre instalação e uso do Jupyter Notebook. As linhas de código e suas respostas, quando existirem, serão representadas da seguinte forma:

    » # Comentários (não são lidos ou executados pelo interpretador)
    » Linha de input (entrada de comandos)
    ↳ Linha de output (resposta do código)
    

    NumPy e matplotlib

    NumPy é a abreviação de Numerical Python, a biblioteca base da computação numérica em Python. Ela fornece as estruturas de dados e algoritmos necessários para a maioria das aplicações científicas e de engenharia utilizando cálculo numérico. Entre outros objetos NumPy NumPy fornece

    • o objeto multidimensional ndarray onde se pode aplicar operações vetorializadas rápidas e eficientes,
    • um conjunto de funções para cálculos elementares com vetores e matrizes,
    • ferramentas de leitura e gravação de dados,
    • operações da álgebra linear, transformada de Fourier e geração de números aleatórios,
    • interação com C e C++.


    Para dados numéricos as matrizes do NumPy são armazenadas e manipuladas de modo mais eficiente do que as demais estruturas do Python. Além disso códigos escritos em linguagens de baixo nível, como C ou Fortran, podem operar diretamente nos dados armazenados com o NumPy. Por isso muitas ferramentas de computação numérica do Python usam as matrizes NumPy como um estrutura de dados primária.

    matplotlib é a biblioteca Python mais popular usada para a produção de gráficos e visualização de dados. Ela pode ser usada na geração de gráficos estáticos ou animados e visualização interativa.

    Pandas

    O pandas se utiliza basicamente de 3 objetos de armazenamento de dados com as seguintes estruturas:
    Estrutura de dados dos objetos do pandas:

    Nome dimensões tabela
    Series 1D coluna (vetor)
    DataFrame 2D tabela (matriz)
    Panel 3D várias tabelas (matriz multidimensional)

    As series e os dataframes são utilizados com maior frequência.

    Como a manipulação de dados usando séries e dataframes frequentemente envolvem operações encontradas no módulo numpy é frequente sua importação junto com pandas.

    » import pandas as pd
    » import numpy as np
    

    Series

    Uma series é um objeto unidimensional, tipo um vetor, que contém uma sequência de objetos do mesmo tipo. A essa sequência está associado um outro vetor de labels chamado de index (índice). O método básico de criação de séries é da seguinte forma:
    serie = pd.Series(data, index=index)
    onde data pode ser um dict (um dicionário do Python), uma lista ou ndarray do numPy, ou um escalar. index é uma lista de índices que, se omitida, é preenchida com inteiros iniciando em 0.

    » serie1 = pd.Series([-1, 9, 0, 2, 5])
    ↳  0   -1
       1    9
       2    0
       3    2
       4    5
    

    À esquerda estão listados os índices que, por default, são uma lista de inteiros começando por 0. Os valores podem ser listados com .values e os índices com .index.

    » serie1.values
    ↳ array([-1,  9,  0,  2,  5])
    
    » serie1.index
    ↳ RangeIndex(start=0, stop=5, step=1)
    

    Os índices podem ser inseridos manualmente e não precisam ser inteiros. O valor correspondente ao índice i pode ser acessado com serie[i], como mostrado abaixo, onde os índices são strings.

    » serie2 = pd.Series([4, 7, -5, 3], index=['a', 'b', 'c', 'd'])
    » serie2
    ↳ a    4
      b    7
      c   -5
      d    3
      dtype: int64
    
    » serie2['c']
    ↳ -5
    

    Uma série pode ser filtrada passando como índice outra série de elementos boolenos, (True, False). Além disso operações vetorializadas podem ser realizadas sobre todos os elementos da série.

    # O teste seguinte gera uma série de booleanos
    » serie2 > 3
    ↳ a     True
      b     True
      c    False
      d    False
      dtype: bool
    
    # Essa serie de True e False filtra a serie original
    » serie2[serie2 > 3]
    ↳ a    4
      b    7
      dtype: int64
    
    # Operações podem, ser realizadas sobre todos os elementos
    » serie2 * 3
    ↳ a    12
      b    21
      c   -15
      d     9
      dtype: int64
    
    # o módulo Numpy possui a função exponencial
    » np.exp(serie2)
    ↳ a      54.598150
      b    1096.633158
      c       0.006738
      d      20.085537
      dtype: float64
    

    Séries se comportam, em muitos sentidos, como um dicionário. Uma série pode ser criada passando-se um dicionário como argumento para pd.Series().

    » populacao = {
               'Sudeste': 89012240, 
               'Nordeste': 57374243,
               'Sul': 30192315,
               'Norte': 18672591,
               'Centro-Oeste':16504303
             }
    
    » serie3 = pd.Series(populacao)
    
    » serie3
    ↳ Sudeste         89012240
      Nordeste        57374243
      Sul             30192315
      Norte           18672591
      Centro-Oeste    16504303
      dtype: int64 
    


    A ordem dos itens na série pode ser alterada através do fornecimento de uma lista com o ordenamento desejado para o argumento index. A elementos não presentes no dicionário serão atribuídos o valor NaN, Not a Number (não número). O método pd.isnull(serie) permite a avalição de quais elementos estão nulos ou NaN.

    # fornecendo uma lista para o argumento index:
    » ordem_alfabetica = ['Brasil', 'Centro-Oeste', 'Nordeste', 'Norte', 'Sudeste', 'Sul']
    » serie4 = pd.Series(populacao, index=ordem_alfabetica)
    » serie4
    ↳ Brasil               NaN
      Centro-Oeste    16504303
      Nordeste        57374243
      Norte           18672591
      Sudeste         89012240
      Sul             30192315
      dtype: int64
    
    # para verificar quais valores são nulos (NaN)
    » pd.isnull(serie4)
    ↳ Brasil           True
      Centro-Oeste    False
      Nordeste        False
      Norte           False
      Sudeste         False
      Sul             False
      dtype: bool
    
    # os seguintes registros são NaN
    » serie4[pd.isnull(serie4)]
    ↳ Brasil   NaN
      dtype: float64
    
    » serie4[pd.notnull(serie4)]
    ↳ Centro-Oeste    16504303.0
      Nordeste        57374243.0
      Norte           18672591.0
      Sudeste         89012240.0
      Sul             30192315.0
      dtype: float64
    
    » 'Brasil' in serie4
    ↳ True
    » 'EUA' in serie4
    ↳ False
    # uma excessão KeyError é lançada se o indice não existe
    » serie4['EUA']
    ↳ KeyError
    

    Como não existe no dicionário um valor para o índice Brasil a série atribuiu o valor NaN (Not a Number) para essa chave, a forma de NumPy e pandas indicar a ausência de um valor. O método retorna True ou False para cada item da série e pd.notnull() o seu inverso booleano. Alternativamente se pode usar o método das séries serie4.isnull().

    Ainda sobre a semelhança entre séries e dicionários, podemos testar a existência de uma chave usando o operador in, como em 'Brasil' in serie4. A tentativa de recuperar um valor com índice não existente gera uma exceção (um erro do Python).

    Observe que uma series tem propriedades de numpy ndarray, mas é um objeto de tipo diferente. Se um ndarray é necessário use series.to_numpy().

    » type(serie4)
    ↳ pandas.core.series.Series
    
    » type(serie4.to_numpy())
    ↳ numpy.ndarray
    

    Series podem ser fatiadas com a notação serie[i:f] onde serão retornadas a i-ésima linha até a f-ésima, exclusive. Se i for omitido a lista se inicia em 0, se f for omitido ela termina no final da series.

    » serie4
    ↳ Brasil                 NaN
      Centro-Oeste    16504303.0
      Nordeste        57374243.0
      Norte           18672591.0
      Sudeste         89012240.0
      Sul             30192315.0
      dtype: float64
    
    » serie4[2:5]
    ↳ Nordeste        57374243.0
      Norte           18672591.0
      Sudeste         89012240.0
      dtype: float64
    
    » serie4[:2]
    ↳ Brasil                 NaN
      Centro-Oeste    16504303.0
      dtype: float64
    
    » serie4[4:]
    ↳ Sudeste    89012240.0
      Sul        30192315.0
      dtype: float64
    

    Series podem ser exibidas com o método display(serie_0, serie_1, ..., serie_n). O resultado de operações envolvendo mais de uma serie, como a soma, alinha os valores por chaves (como uma UNION). Valores não presentes em um dos operandos terá NaN como resultado.

    » serie5 = pd.Series([2, -1, -2, 1], index=['a', 'b', 'c', 'd'])
    » serie6 = pd.Series([3, 4, 7, -1], index=['e', 'c', 'b', 'f'])
    » display(serie5, serie6)
    ↳ a    2
      b   -1
      c   -2
      d    1
      dtype: int64
    
    ↳ e    3
      c    4
      b    7
      f   -1
      dtype: int64
    
    » serie5 + serie6
    ↳ a    NaN
      b    6.0
      c    2.0
      d    NaN
      e    NaN
      f    NaN
      dtype: float64
    

    Series possuem a propriedade name que pode ser atribuída na construção ou posteriormente com o método .rename(). No exemplo usamos o método np.random.randn(n) de numpy para fornecer um numpy.ndarray com n números aleatórios. Damos inicialmente a essa série o nome ‘randomica’, depois a renomeamos para ‘aleatoria’.

    » serie7 = pd.Series(np.random.randn(5), name='randomica')
    » serie7
    ↳ 0   -1.703662
      1    1.406167
      2    0.966557
      3   -0.557846
      4   -0.264914
      Name: randomica, dtype: float64
    
    » serie7.name
    ↳ 'randomica'
    
    » serie7= serie7.rename('aleatoria')
    » serie7.name
    ↳ 'aleatoria'
    

    O nome de uma série se torna seu índice ou nome de coluna caso ela seja usada para formar um DataFrame.

    Atributos e Métodos das Series

    Os atributos e métodos mais comuns e úteis das Series estão listados abaixo. Para uma lista completa consulte pandas.Series: API Reference.

    Atributos

    Atributo Descrição
    at[n] Acesso ao valor na posição n
    attrs Retorna ditionario de atributos globais da series
    axes Retorna lista de labels do eixo das linhas
    dtype Retorna o tipo (dtype) dos objetos armazenados
    flags Lista as propriedades do objeto
    hasnans Informa se existem NaNs
    iat[n] Acesso ao valor na posição n inteiro
    iloc[n] Acesso ao valor na posição n inteiro
    index Retorna lista de índices
    index[n] Retorna índice na n-ésima posição
    is_monotonic Booleano: True se valores crescem de forma monotônica
    is_monotonic_decreasing Booleano: True se valores decrescem de forma monotônica
    is_unique Booleano: True se valores na series são únicos
    loc Acessa linhas e colunas por labels em array booleano
    name O nome da Series
    nbytes Número de bytes nos dados armazenados
    shape Retorna uma tuple com forma (dimensões) dos dados
    size Número de elementos nos dados
    values Retorna series como ndarray

    Alguns casos de acessos à essas propriedades. Os outputs são exibidos como comentários:

    » import pandas as pd
    » import numpy as np 
    » serie = pd.Series([1,-1, 2, 2, 6, 63])
    » serie.size              # 6, o mesmo que len(serie)
    » serie.at[5]             # 63
    » serie.iloc[0]           # 1
    » serie.index             # RangeIndex(start=0, stop=6, step=1)
    » serie.is_monotonic      # False
    » serie.is_unique         # False
    » serie.shape             # (6,) 
    

    Métodos

    Nas tabelas abaixo a expressão “elemento a elemento” é abreviada para “e/e”. Estas operações são repetidas a cada elemento da, ou das, séries envolvidas. Por exemplo, o método serie.add(s2) é feita “elemento a elemento” (e/e):

    » import pd	
    » serie_a = pd.Series([-1,9,0,2, 5, -8])
    » serie_b = pd.Series([1,-9,0, -2,-5, 8])
    » serie_a.add(serie_b)
    » # é o mesmo que
    » serie_a + serie_b
    » # que resulta em serie nula 
    » # pd.Series([0,0,0,0,0,0])
    

    Manipulação e gerenciamento das Series

    Método (sobre série s, outra s2) Descrição
    s.align(s2) Alinha 2 objetos em seus eixos usando método especificado
    s.append(to_append[, ignore_index, …]) Concatena 2 ou mais Series
    s.asof(where[, subset]) Último elemento antes da ocorrência de NaNs após ‘where’
    s.backfill([axis, inplace, limit, downcast]) Aliás para DataFrame.fillna() usando method=’bfill’
    s.bfill([axis, inplace, limit, downcast]) Alias para DataFrame.fillna() usando method=’bfill’
    s.clip([min, max, axis, inplace]) Inclui apenas valores no intervalo
    s.combine(s2, func[, fill_value]) Combina a s com s2 ou escalar, usando func
    s.copy([deep]) Cópia do objeto s, índices e valores
    s.drop_duplicates([keep, inplace]) Remove elementos duplicados de s
    s.dropna() Remove valores faltantes de s
    s.duplicated([keep]) Exibe valores duplicados na s
    s.explode([ignore_index]) Transforma cada elemento de um objeto tipo lista em uma linha
    s.fillna([value, method, axis, inplace, …]) Substitui valores NA/NaN usando método especificado
    s.get(key) Retorna item correspondente à key
    s.groupby([by, axis, level, as_index, sort, …]) Agrupa a s
    s.head([n]) Retorna os n primeiros valores
    s.interpolate([method, axis, limit, inplace, …]) Preenche valores NaN usando metodo de interpolação
    s.item() Primeiro elemento dos dados como escalar do Python
    s.items() Iteração (lazy) sobre a tupla (index, value)
    s.iteritems() Iteração (lazy) sobre a tupla (index, value)
    s.mask(cond[, s2, inplace, axis, level, …]) Substitui valores sob condição dada
    s.max([axis, skipna, level, numeric_only]) Valor máximo
    s.memory_usage([index, deep]) Memória usada pela s
    s.min([axis, skipna, level, numeric_only]) Menor dos valores da s
    s.nlargest([n, keep]) Retorna os n maiores elementos
    s.nsmallest([n, keep]) Retorna os n menores elementos
    s.nunique([dropna]) Retorna quantos elementos únicos existem na s
    s.pad([axis, inplace, limit, downcast]) O mesmo que DataFrame.fillna() usando method=’ffill’
    s.plot() O mesmo que pandas.plotting._core.PlotAccessor
    s.pop(i) Remove s[i] de s e retorna s[i]
    s.repeat(repeats[, axis]) Repete elementos da s
    s.replace([to_replace, value, inplace, limit, …]) Substitui valores em to_replace por value
    s.sort_values([axis, ascending, inplace, …]) Reorganiza s usando seus valores
    s.str Usa funções de string sobre s (se string). Ex. s.str.split(“-“)
    s.tail([n]) Últimos n elementos
    s.unique() Retorna os valores da s, sem repetições
    s.update(s2) Modifica s usando valores de s2, usando índices iguais
    s.view([dtype]) Cria uma nova “view” da s
    s.where(cond[, serie, inplace, axis, level, …]) Substitui valores se a condição cond = True

    Operações matemáticas básicas:

    s.ewm([com, span, halflife, alpha, …])Calcula exponencial com peso

    s.abs() Retorna s com valor absoluto, e/e
    s.add(s2) Soma s com s2, e/e
    s.agg([func, axis]) Agrega usando func sobre o eixo especificado
    s.apply(func[, convert_dtype, args]) Aplica func sobre os valores de s, e/e
    s.div(s2) Divisão (float) de s por s2, e/e
    s.divmod(s2) Divisão inteira e módulo de s por s2, e/e
    s.dot(s2) Produto interno entre a s e s2
    s.floordiv(s2) Divisão inteira da s por s2, e/e
    s.mod(s2[, level, fill_value, axis]) Módulo de s por s2, e/e
    s.rfloordiv(s2[, level, fill_value, axis]) Divisão inteira de s por s2, e/e
    s.rmod(s2[, level, fill_value, axis]) Modulo da divisão da s por s2, e/e
    s.rmul(s2[, level, fill_value, axis]) Multiplicação de s por s2, e/e
    s.round([n]) Arredonda valores da s para n casas decimais.
    s.rpow(s2[, level, fill_value, axis]) Exponential de s por s2, e/e
    s.rsub(s2[, level, fill_value, axis]) Subtração da s por s2, e/e
    s.rtruediv(serie[, level, fill_value, axis]) Divisão (float) de s por s2, e/e
    s.sub(s2) Subtração de s por s2, e/e
    s.subtract(serie) Idem
    s.sum([axis, skipna, level, numeric_only, …]) Soma dos valores da s
    s.transform(func[, axis]) Executa func sobre elementos de s
    s.truediv(s2) Divisão (float) de s por s2, e/e
    s.truncate([before, after, axis, copy]) Trunca a s antes e após índices dados
    s.mul(s2[, level, fill_value, axis]) Multiplicação de s por s2, e/e
    s.multiply(s2[, level, fill_value, axis]) Idem
    s.pow(s2) Exponential de s por s2, e/e
    s.prod([axis, skipna, level, numeric_only, …]) Produto dos elementos da s
    s.product([axis, skipna, level, numeric_only, …]) Idem
    s.rdiv(s2[, level, fill_value, axis]) Divisão (float) de s por s2, e/e
    s.rdivmod(s2) Divisão inteira e módulo de s por s2, e/e

    Operações estatísticas:

    Método (sobre série s, outra s2) Descrição
    s.corr(s2) Correlação de s com s2, excluindo NaNs
    s.count([level]) Número de observações na s, excluindo NaN/nulls
    s.cov(s2[, min_periods, ddof]) Covariância da s, excluindo NaN/nulls
    s.cummax([axis, skipna]) Máximo cumulativo
    s.cummin([axis, skipna]) Mínimo cumulativo
    s.cumprod([axis, skipna]) Produto cumulativo
    s.cumsum([axis, skipna]) Soma cumulativa
    s.describe([percentiles, include, exclude, …]) Gera descrição estatística
    s.kurt([axis, skipna, level, numeric_only]) Kurtosis imparcial
    s.kurtosis([axis, skipna, level, numeric_only]) Idem
    s.hist() Plota histograma da s usando matplotlib.
    s.mad([axis, skipna, level]) Desvio médio absoluto dos valores de s
    s.mean([axis, skipna, level, numeric_only]) Média dos valores
    s.median([axis, skipna, level, numeric_only]) Mediana dos valores
    s.mode([dropna]) Moda da s
    s.quantile([q, interpolation]) Valor no quantil dado
    s.ravel([order]) Retorna dados como um ndarray
    s.sample([n, frac, replace, weights, …]) Amostra randomizada de items da s
    s.sem([axis, skipna, level, ddof, numeric_only]) Erro padrão imparcial da média
    s.skew([axis, skipna, level, numeric_only]) Inclinação imparcial
    s.std([axis, skipna, level, ddof, numeric_only]) Desvio padrão da amostra
    s.value_counts([normalize, sort, ascending, …]) Retorna s com a contagem de valores únicos
    s.var([axis, skipna, level, ddof, numeric_only]) Variância imparcial dos valores da s

    Operações com índices:

    s.add_prefix('prefixo') Adiciona prefixo aos labels com string ‘prefixo’
    s.add_suffix('sufixo') Adiciona sufixo aos labels com string ‘sufixo’
    s.argmax([axis, skipna]) Posição (índice inteiro) do valor mais alto de s
    s.argmin([axis, skipna]) Posição (índice inteiro) do menor valor de s
    s.argsort([axis, kind, order]) Índices inteiros que ordenam valores da s
    s.drop([labels]) Retorna s com labels removidos
    s.first_valid_index() Índice do primeiro valor não NA/null
    s.idxmax([axis, skipna]) Label do item de maior valor
    s.idxmin([axis, skipna]) Label do item de menor valor
    s.keys() Alias de index
    s.last_valid_index() Índice do último valor não NA/null
    s.reindex([index]) Ajusta a s ao novo índice
    s.reindex_like(s2[, method, copy, limit, …]) Série com índices em acordo com s2
    s.rename([index, axis, copy, inplace, level, …]) Altera o nome (labels) dos índices
    s.reorder_levels(order) Reajusta níveis de índices usando order
    s.reset_index([level, drop, name, inplace]) Reinicializa índices
    s.searchsorted(value[, side, sorter]) Índices onde elementos devem ser inseridos para manter ordem
    s.sort_index([axis, level, ascending, …]) Reorganiza s usando os índices

    Testes, com retorno booleanos e comparações:

    Método (sobre série s, outra s2) Descrição
    s.all([axis, bool_only, skipna, level]) Booleano: se todos os elementos são True
    s.any([axis, bool_only, skipna, level]) Booleano: se algum elemento é True
    s.equals(s2) Booleano: True se s contém os mesmos elementos que s2
    s.between(min, max) Booleano: satisfazendo min <= s <= max, e/e
    s.compare(s2[, align_axis, keep_shape, …]) Compara s com s2 exibindo diferenças
    s.eq(s2) Boleano: igualdade entre s e s2, e/e
    s.ge(s2) Booleana: maior ou igual entre s e s2, e/e
    s.gt(s2[, level, fill_value, axis]) Booleana: se s é maior que s2, e/e
    s.isin(valores) Booleano: se elementos da s estão contidos em valores
    s.isna() Booleano: se existem valores ausentes
    s.isnull() Booleano: se existem valores nulos
    s.le(s2) Booleana: se s é menor ou igual a s2, e/e
    s.lt(s2[, level, fill_value, axis]) Booleana: se s é menor que s2, e/e
    s.ne(s2[, level, fill_value, axis]) Booleana: se s é diferente de s2, e/e
    s.notna() Booleana: se existem valores não faltantes ou nulos
    s.notnull() Idem

    Transformações para outros formatos e tipos:

    s.astype(dtype[, copy, errors]) Transforma (cast) para dtype
    s.to_clipboard([excel, sep]) Copia o object para o clipboard do sistema
    s.to_csv([path_or_buf, sep, na_rep, …]) Grava a s como arquivo csv
    s.to_dict() Converte s para dict {label ⟶ value}
    s.to_excel(excel_writer[, sheet_name, na_rep, …]) Grava s como uma planilha Excel
    s.to_frame([name]) Converte s em DataFrame
    s.to_hdf(path_or_buf, key[, mode, complevel, …]) Grava s em arquivo HDF5 usando HDFStore
    s.to_json([path_or_buf, orient, date_format, …]) Converte s em string JSON
    s.to_latex([buf, columns, col_space, header, …]) Renderiza objeto para LaTeX
    s.to_markdown([buf, mode, index, storage_options]) Escreve a s em formato Markdown (leia)
    s.to_numpy([dtype, copy, na_value]) Converte s em NumPy ndarray
    s.to_pickle(path[, compression, protocol, …]) Grava objeto serializado em arquivo Pickle
    s.to_sql(name, con[, schema, if_exists, …]) Grava elementos em forma de um database SQL
    s.to_string([buf, na_rep, float_format, …]) Constroi uma representação string da s
    s.tolist() Retorna uma lista dos valores
    s.to_list() idem

    Operações com séries temporais:

    s.asfreq(freq) Converte TimeSeries para frequência especificada.
    s.at_time(time[, asof, axis]) Seleciona valores em determinada hora (ex., 9:30AM)
    s.between_time(inicio, fim) Seleciona valores com tempo entre inicio e fim
    s.first(offset) Seleciona período inicial de uma série temporal usando offset.
    s.last(offset) Seleciona período final de uma série temporal usando offset

    Alguns exemplos de uso dos métodos de pandas.Series:

    » import pandas as pd
    » import numpy as np
    » serie = pd.Series([1,-1, 2, 2, 6, 63])
    » serie.abs()
    » # retorna  pd.Series([1, 1, 2, 2, 6, 63])
    
    » # Muitos métodos não alteram a series inplace.
    » s2 = serie.rename('NovoNome')  # não altera nome de serie
    » s2.name
    ↳ 'NovoNome'
    » # para alterar o nome usamos
    » serie.rename('NovoNome', inplace=True)  # altera nome de serie inplace
    » serie.name
    ↳ 'NovoNome'
    
    » # um resumo estatístico pode ser visto com describe
    » serie.describe()
    ↳ count    15.000000
      mean      4.866667
      std       3.044120
      min       0.000000
      25%       3.500000
      50%       5.000000
      75%       7.000000
      max       9.000000
      dtype: float64
    
    » # gerando outra series
    » data = np.random.randint(0, 10,size=15)
    » serie2 = pd.Series(data)
    » data
    ↳ array([2, 2, 6, 3, 1, 4, 3, 4, 3, 0, 8, 3, 8, 2, 7])
    
    » serie.div(serie2)
    ↳ 0     2.500000
      1     0.000000
      ...
      9          inf
      13    0.000000
      14    1.000000
      dtype: float64
    


    Observe que a divisão por 0 não gera erro mas é representada por inf.

    Alguma habilidade gráfica pode ser encontrada entre os métodos das series. Um exemplo é o serie.hist() que traça o histograma da series usando matplotlib. Veremos com mais detalhes as funcionalidades dessa biblioteca.

    » data = np.random.randint(0, 10,size=15)
    » data
    array([5, 0, 7, 7, 0, 9, 6, 5, 6, 9, 5, 5, 2, 0, 7])
    » serie = pd.Series(data)
    » serie.hist()
    
    

    Objetos do pandas possuem métodos poderosos e eficientes. O método serie.str() permite operações de strings sobre os elementos da serie, se esses forem strings.

    » str = ['-mercado','-tensão','-plasia']
    » serie = pd.Series(str)
    » serie
    ↳ 0    -mercado
      1     -tensão
      2     -plasia
    
    » serie = serie.str.replace('-','hiper-')
    » serie
    ↳ 0    hiper-mercado
      1     hiper-tensão
      2     hiper-plasia
    
    » serie.str.split('-')
    ↳ 0    [hiper, mercado]
      1     [hiper, tensão]
      2     [hiper, plasia]
    
    » # elementos não strings resultam em NaN
    » serie = pd.Series([123,'-tensão','-plasia'])
    » serie.str.replace('-','hiper-')
    ↳ 0             NaN
      1    hiper-tensão
      2    hiper-plasia
    

    Series podem ser usadas na construção de dataframes, que serão vistos a seguir. Em particular o método to_frame() transforma uma series em um dataframe com uma coluna, onde cada valor ocupa uma linha.

    » # uma series pode ser transformada em um dataframe
    » df = serie.to_frame()
    » # um dataframe é gerado e armazenado em df
    

    Como a maioria dos métodos de series são análogos àqueles de dataframes faremos uma exploração mais extensa desses na sessão referente aos dataframes.

    🔺Início do artigo

    Bibliografia

    • Blair,Steve: Python Data Science, The Ultimate Handbook for Beginners on How to Explore NumPy for Numerical Data, Pandas for Data Analysis, IPython, Scikit-Learn and Tensorflow for Machine Learning and Business, edição do autor disponível na Amazon, 2019.
    • Harrison, Matt: Learning Pandas, Python Tools for Data Munging, Data Analysis, and Visualization,
      edição do autor, 2016.
    • McKinney, Wes: pandas: powerful Python data analysistoolkit, Release 1.2.1
      publicação do Pandas Development Team, revisado em 2021.
    • McKinney, Wes: Python for Data Analysis, Data Wrangling with Pandas, NumPy,and IPython
      O’Reilly Media, 2018.
    • Pandas: página oficial, acessada em janeiro de 2021.
    • Pandas User Guide, acessada em fevereiro de 2021.
    • Miller, Curtis: On Data Analysis with NumPy and pandas, Packt Publishing, Birmingham, 2018.