Python: Funções, Decoradores e Exceções


Funções e decoradores

No Python tudo é um objeto, inclusive as funções. Isso significa que elas podem ser atribuidas a uma variável ou serem retornadas por outra função. Na programação em geral uma função é considerada um objeto de primeira classe se:

  • é uma instância do tipo Object,
  • pode pode ser armazenada em uma variável,
  • pode ser passada como um parâmetro para outra função,
  • pode ser obtida no retorno de outra função,
  • pode ser armazenada em estruturas de dados, como listas e dicionários.

No Python funções podem ser atribuídas a variáveis.

# uma variável pode armazenar uma função interna
» p = print
» p(1234)
↳ 1234

# ou uma do usuário
» def funcao():
»     print('Tô aqui!')

» a = funcao
# a é uma função
» print(a)
↳ <function __main__.funcao()>

# a função é executada com colchetes
» a()
↳ Tô aqui!

# outra função recebe uma string como parâmetro
» def funcao(texto):
»     print(texto)

» a = funcao

» a('Meu nome é Enéas!')
↳ 'Meu nome é Enéas!

Funções podem ter outras funções definidas dentro de seu corpo. No caso abaixo temos o cálculo da função composta \(f(x) = \sqrt(x^2+1)\).

» import math

» def funcaoComposta(x):
»     def funcaoInterna(i):
»         return i**2 + 1
»     return math.sqrt(funcaoInterna(x))

» funcaoComposta(7)
↳ 7.0710678118654755

Funções podem ser passadas como argumentos para outras funções. A função digaOla(arg) recebe outras duas funções como argumento.

# funções como argumento de outras funções
» def falaAlto(texto):
»     return texto.upper()

» def falaBaixo(texto):
»     return texto.lower()

» def digaOla(func):
»     # variável oi armazena o retorno (string) das funções no argumento func
»     oi = func('Olá, texto usado como argumento da função parâmetro!')
»     print (oi)

» digaOla(falaBaixo)
↳ olá, texto passado como argumento da função parâmetro!

» digaOla(falaAlto)
↳ OLÁ, TEXTO PASSADO COMO ARGUMENTO DA FUNÇÃO PARÂMETRO!

A função funcOla é chamada de decoradora. A função funcNome, que é passada como argumento para o decorador, é chamada de função decorada.

» # exemplo 1
» def funcOla(varFuncao):
»     def funcInterna():
»         print('Olá ', end='')
»         varFuncao()
»     return funcInterna

» def funcNome():
»     print('Assurbanipal, rei da Assíria')

» obj = funcOla(funcNome)
» obj()
↳ Olá Assurbanipal, rei da Assíria

# exemplo 2
» def func1(txt):
»     print(txt)

» def func2(funcao, txt):
»     funcao(txt)

» func2(func1, 'Libbali-sharrat, esposa de Assurbanipal')
↳ Libbali-sharrat, esposa de Assurbanipal

# exemplo 3
» def decoradora(func):
»     def interna():
»         print("Ocorre antes da função parâmetro ser executada.")
»         func()
»         print("Ocorre depois da função parâmetro ser executada.")
»     return interna

» def digaUau():
»     print("Uau!!!!")

» f = decoradora(digaUau)    #   <---- f é uma função composta

» f()                        #   <---- executar a função f
↳ Ocorre antes da função parâmetro ser executada.
↳ Uau!!!!
↳ Ocorre depois da função parâmetro ser executada.

Funções compostas são chamadas de objetos de segunda classe ou funções de ordem superior. Decoradores envolvem uma função, modificando seu comportamento. Quando executamos f = decoradora(digaUau) estamos executando interna() tendo em seu corpo func=digaUau().

O Python fornece uma forma simplificada de usar decoradores, usando o sinal @.

» def funcaoDecoradora(funcaoArg):
»     def interna():
»         # corpo de interna usando funcaoArg()
»     return interna

» @funcaoDecoradora
» def funcaoDecorada:
»     # corpo de decorada #

» # essa sintaxe é equivalente à
» funcaoComposta = funcaoDecoradora(funcaoDecorada)
» # para executá-la
» funcaoComposta()

No caso do último exemplo 3 podemos apenas fazer

» def decoradora(func):
»     def interna():
»         print("Ocorre antes da função decorada ser executada.")
»         func()
»         print("Ocorre depois da função decorada ser executada.")
»     return interna

» @decoradora
» def digaUau():
»     print("Uau!!!!")

» digaUau()
↳ Ocorre antes da função decorada ser executada.
↳ Uau!!!!
↳ Ocorre depois da função decorada ser executada.

Se a função a ser decorada possuir parâmetros, a função interna (que envolve a decorada) deve possuir os mesmos parâmetros, que devem ser fornecidos quando se invoca a função decorada.

» def produtoDecorado(func):
»     def interna(a,b):
»         print('%d x %d = ' % (a,b), end='')
»         return func(a,b)
» 
»     return interna

» @produtoDecorado
» def produto(a,b):
»     print(a * b)

» produto(55,33)
↳ 55 x 33 = 1815

Vale lembrar que se desejamos passar um número qualquer de parâmetros podemos usar *args e *kwargs, que representam respectivamente um número arbitrário de argumentos e de argumentos com palavras chaves.

» def produtoDecorado(func):
»     def interna(*args):
»         print('O produto %s = ' % str(args).replace(', ',' x '), end='')
»         return func(*args)
» 
»     return interna

» @produtoDecorado
» def produtorio(*args):
»     prod = 1
»     for t in args:
»         prod *= t
»     print(prod)

» produtorio(1,2,3,4,5,6,7,8,9)
↳ O produto de (1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9) = 362880
: time.time() retorna a hora em segundos, como um número de ponto flutuante, lida no relógio interno do computador. Ela é o número de segundos decorridos desde a época, 1 de Janeiro de 1970, 00:00:00 (UTC), também chamada de (Unix time).

Um uso comum para um decorador é o de medir o tempo de execução de um bloco de código qualquer. Isso pode ser útil na otimização de um programa. Para isso usamos o módulo time, e suas funções time.time(), que lê a hora em segundos , e time.sleep(n), que suspende a execução do código por n segundos.

Para isso envolvemos o bloco de código a ser medido, no caso a função que é decorada, com o contador. O instante inicial é armazenado e comparado com o final, após a execução, a a diferença é exibida.

» # decorador para medir o tempo de execução de um bloco de código
» import time

» def cronometro(func):
»     def interna(*arg):
»         inicio = time.time()
»         func(*arg)
»         print('O código levou %s segundos para rodar.' % str(time.time()-inicio))
»     return interna

» @cronometro
» def funcaoTeste(n):
»     time.sleep(n)

» funcaoTeste(1.5)
↳ O código levou 1.5015053749084473 segundos para rodar.

» # outro teste, com um laço for
» @cronometro
» def laco():
»     soma = 0
»     for t in range(10000):
»         soma += t
»     print('soma =',soma)

» laco()
↳ soma = 49995000
↳ O código levou 0.0010344982147216797 segundos para rodar.

Erros, Exceções e tratamento de erros

No Python existem dois tipos de erros que são denominados erros de sintaxe e exceções.

Erros de sintaxe são comandos escritos incorretamente, a ausência ou excesso de parênteses, chaves ou colchetes ((, {, [,), delimitadores incorretos de strings, vírgulas ausentes ou postas em local incorreto, etc. Quando encontra esses erros o interpretador interrompe a execução e retorna uma instrução de onde o erro ocorreu e, em alguns casos, uma sugestão de como consertá-lo. Nessas mensagens um sinal de ^ indica o local onde o erro foi notado. Se o código foi lido em um arquivo.py o nome do arquivo é indicado e a linha do erro é indicada. Essas mensagens são fornecidas pela função Traceback.

» print 'sem parênteses'
↳   File "<ipython-input-6-cfe4fc7e6b4d>", line 1
↳     print 'sem parênteses'
↳           ^
↳ SyntaxError: Missing parentheses in call to 'print'. Did you mean print('sem parênteses')?

» print('parênteses excessivos'))
↳  File "<ipython-input-7-1c97f0f5b744>", line 1
↳     print('parênteses excessivos'))
                                  ^
↳ SyntaxError: unmatched ')'

» dicionario = {1:'um', 2:'dois' 3:'três'}
↳   File "<ipython-input-12-60359adab8df>", line 1
↳     dicionario = {1:'um', 2:'dois' 3:'três'}
↳                                    ^
↳ SyntaxError: invalid syntax

Esses são, quase sempre, os erros mais fáceis de serem encontrados e corrigidos. Observe que, no Python 2, o comando print 'sem parênteses' estava correto. No Python 3 print() se tornou uma função e os parênteses passaram a ser obrigatórios.

Vimos que o Python usa indentações (que podem ser espaços ou tabs) para delimitar eus blocos de código. Erros desses tipos são capturados como IndentationError e TabError.

Excessões: Uma exceção é um evento que ocorre durante a execução de um programa que interrompe o fluxo das instruções, além dos erros de sintaxe. Quando o interpretador encontra uma situação com esse tipo de erro ele levanta uma exceção, instanciando uma das classes derivadas da superclasse exception. Exceções levantadas devem ser tratadas para que a execução do código não termine de forma indesejada. Uma lista completa de exceções pode ser encontrada no artigo Python, Resumo.

Um exemplo de exceção é a tentativa de dividir por zero.

» for i in range(4):
»     v = 10/(2-i)
»     print(v)
↳ 5.0
↳ 10.0
↳ ---------------------------------------------------------------------------
↳ ZeroDivisionError                         Traceback (most recent call last)
↳ <ipython-input-14-b8aab2286d16> in <module>
↳             1 for i in range(4):
↳ ---->       2     v = 10/(2-i)
↳             3     print(v)
↳ ZeroDivisionError: division by zero

No exemplo é claro que quando i = 2 o denominador será nulo e a divisão por 0 não é definida. Por isso ZeroDivisionError foi lançada. Podemos corrigir esse erro simplesmente testando o denomidor e pulando o valor problemático. Mas denominadores nulos podem surgir de forma inesperada de muitas formas, tais como em dados lidos automaticamente ou inseridos pelo usuário. Por isso precisamos de um tratamento de erros. Para esse fim temos os blocos try, except e finally ou else.

  • try: verifica se há um erro no bloco seguinte de código,
  • except 1: recebe fluxo de execução em caso de exceção 1,
  • … : (podem existir várias capturas de exceções),
  • except n: recebe fluxo de execução em caso de exceção n,
  • else: código executado se nenhum erro for encontrado,
  • finally: código executado em ambos os casos.

Portanto, se suspeitamos que há possibilidade de um erro ser lançado envolvemos partes do código nesses blocos.

» for i in range(4):
»     try:
»         v = 10/(2-i)
»         print('i = %d, v = %d' % (i,v))
»     except:
»         print('Erro em i = %d' % i)

» # no caso de i=2 o primeiro comando print não é executado
↳ i = 0, v = 5
↳ i = 1, v = 10
↳ Erro em i = 2
↳ i = 3, v = -10

No caso acima except captura qualquer erro que tenha acontecido. Blocos grandes de código podem estar dentro de um try com captura genérica. Isso não é muito bom em muitos casos pois não saberíamos que tipo de de erro foi lançado. Ao invés disso podemos capturar um erro específico.

» # supondo que a variável w não está definida
» try:
»     print(w)
» except NameError:
»     print("A variável w não está definida")
» except:
»     print("Outro erro ocorreu")
» A variável w não está definida

O opção else ocorre se nenhuma exceção foi capturada. finally ocorre em ambos os casos e pode ser útil para a execução de alguma finalização ou limpeza.

Suponha que existe o arquivo arquivoTeste.txt na pasta de trabalho atual mas ele está marcado como read only (somente de leitura).

» try:
»     f = open('arquivoTeste.txt')
»     f.write('Lorum Ipsum')
» except:
»     print('Aconteceu alguma coisa errada com esse arquivo!')
» else:
»     print('Operação bem sucedida!')
» finally:
»     f.close()
»     print('* conexão fechada')
↳ Aconteceu alguma coisa errada com esse arquivo!
↳ * conexão fechada

Se o arquivo arquivoTeste2.txt não existe na pasta de trabalho outro erro será lançado:

» try:
»     f = open('arquivoTeste2.txt')
» except FileNotFoundError:
»     print('Esse arquivo não existe!')
» except:
»     print('Aconteceu alguma coisa errada com esse arquivo!')
» finally:
»     f.close()
↳ Esse arquivo não existe!

Suponha que na atual pasta de trabalho existe uma subpasta dados. Se tentarmos abrir essa pasta como se fosse um arquivo teremos uma exceção.

» try:
»     arq = 'dados'
»     f = open(arq)
» except FileNotFoundError:
»     print('Esse arquivo não existe!')
» except IsADirectoryError:
»     print('"%s" é uma pasta e não um arquivo!' % arq)
» else:
»     f.close()
↳ "dados" é uma pasta e não um arquivo!

As exceções FileNotFoundError e IsADirectoryError são ambas subclasses de OSError. As duas exceções são capturadas por essa superclasse.

» try:
»     arq = 'dados'
»     f = open(arq)
» except OSError:
»     print('"%s" é uma pasta e não um arquivo!' % arq)
↳ "dados" é uma pasta e não um arquivo!

» try:
»     arq = 'arquivoNaoExistente'
»     f = open(arq)
» except OSError:
»     print('"%s" não existe!' % arq)
↳ "arquivoNaoExistente" não existe! 

Diversos erros podem ser capturados em um bloco.

» try:
»     lunch()
» except SyntaxError:
»     print('Fix your syntax')
» except TypeError:
»     print('Oh no! A TypeError has occured')
» except ValueError:
»     print('A ValueError occured!')
» except ZeroDivisionError:
»     print('Did by zero?')
» else:
»     print('No exception')
» finally:
»     print('Ok then')

Segue uma lista parcial de erros e sua descrição. Uma lista completa de exceções pode ser encontrada no artigo Python, Resumo.

Exceção Ocorre quando
AsserationError na falha de uma instrução assert
AttributeError em erro de atribuição de atributo
FloatingPointError erro em operação de ponto flutuante
MemoryError ocorre falta de memória para realizar a operação
IndexError há uma chamada à índice fora do intervalo existente
NotImplementedError erro em métodos abstratos
NameError não existe uma variável com o nome no escopo local ou global
KeyError chave não encontrada no dicionário
ImportError tentativa de importar módulo não existente
ZeroDivisorError tentativa de divisão por 0 (zero)
GeneratorExit um gerador é abandonado antes de seu final
OverFlowError uma operação aritmética resulta em número muito grande
IndentationError indentação incorreta
EOFError uma função como input() ou raw_input() retorna end-of-file (EOF, fim de arquivo)
SyntaxError um erro de sintaxe é levantado
TabError espaço ou tabulações inconsistentes
ValueError uma função recebe um argumento com valor incorreto
TypeError tentativa de operação entre tipos incompatíveis
SystemError o interpretador detecta erro interno

É possível capturar o erro lançado com a expressão except Exception as varExcecao: de forma a exibir a mensagem embutida no objeto.

» x, y = 2, '3'
» try:
»     y + x
» except TypeError as t:
»     print(t)
↳ can only concatenate str (not "int") to str

Vários tipos de exceções podem ser capturadas simultaneamente.

try:
    <código que pode conter as exceções>
    ......................
except(Exception1[, Exception2[,...ExceptionN]]]):
    <tratamento das exceções, caso ocorram>
    ......................
else:
    <código executado caso nenhuma das exceções ocorra>
    ......................    

Além das diversas exceções built-in lançadas automaticamente o usuário pode lançar suas próprias exceções. Isso é feito com raise.

» x = 'um'
» if not isinstance(x, int):
»     raise ValueError("Tipo incorreto")
» else:
»     print(34/x)
↳ ValueError: Tipo incorreto

No exemplo acima isinstance(x, int) testa se x é uma instância de int, ou seja, se x é um inteiro.

O usuário pode definir suas próprias exceções, lembrando que devem ser todas derivadas da classe Exception. No exemplo as classes ValorMuitoBaixoError e ValorMuitoAltoError herdam todos os atributos da superclasse, sem acrescentar nenhuma cacterística própria.

» class ValorMuitoBaixoError(Exception):
»     """Erro lançado quando a tentativa é um valor muito baixo"""
»     pass

» class ValorMuitoAltoError(Exception):
»     """Erro lançado quando a tentativa é um valor muito alto"""
»     pass

» # Você deve adivinhar esse número
» numero = 10

» # Loop enquanto o número não for correto
» while True:
»     try:
»         num = int(input("Digite um número: "))
»         if num < numero:
»             raise ValorMuitoBaixoError
»         elif num > numero:
»             raise ValorMuitoAltoError
»         else:
»             print('Acertou!')
»             break
»     except ValorMuitoBaixoError:
»         print("Valor muito pequeno. Tente de novo!\n")
»     except ValorMuitoAltoError:
»         print("Valor muito alto. Tente de novo!\n")

» # ao ser executado o código abre um diálogo para input do usuário
» # suponha que as tentativas feitas são: 2, 55, 10
↳ Digite um número: 2
↳ Valor muito pequeno. Tente de novo!

↳ Digite um número: 55
↳ Valor muito alto. Tente de novo!

↳ Digite um número: 10
↳ Acertou!        

Além de simplesmente herdar da superclasse as classes de erros customizadas podem fazer o overload de seus métodos para realizar tarefas específicas. No caso abaixo usamos apenas uma classe indicativa de erro e alteramos a propriedade message da classe e da superclasse para informar se o erro foi para mais ou menos. Por default o método __str__ retorna essa mensagem.

No trecho abaixo fazemos o overload também de __str__ para incluir uma mensagem mais completa, mantendo igual todo o restante do código.

» # Você deve adivinhar esse número, entre 0 e 100
» numero = 50

» class ValorIncorretoError(Exception):
»     """Exceção lançada para erro de valor """
» 
»     def __init__(self, valor):
»         message='Valor %d é muito %s' % (valor,'baixo' if valor < numero else 'alto')
»         self.message = message
»         super().__init__(self.message)

» # Loop enquanto o número não for correto
» while True:
»     try:
»         num = int(input('Digite um número entre 0 e 100: '))
»         if num != numero:
»             raise ValorIncorretoError(num)
»         else:
»             print('Acertou!')
»             break
»     except ValorIncorretoError as vi:
»         print('%s. Tente de novo!\n' % str(vi))

↳ Digite um número entre 0 e 100: 34
↳ 34. Tente de novo!

↳ Digite um número entre 0 e 100: 89
↳ 89. Tente de novo!

↳ Digite um número entre 0 e 100: 50
↳ Acertou!

Assert e AssertionError

A instrução assert fornece um teste de uma condição. Se a condição é verdadeira o código continua normalmente sua execução. Se for falsa a exceção AssertionError é lançada, com uma mensagem de erro opcional. Ela deve ser usada como um auxiliar na depuração do código, informando o desenvolvedor sobre erros irrecuperáveis ​​em um programa. Asserções são autoverificações internas do programa e funcionam através da declaração de condições que não deveriam ocorrer de forma alguma. O lançamento de uma exceção AssertionError deve indicar que há um bug no código e sua ocorrência deve informar qual condição inaceitável foi violada.

» # forçando o levantamento de AssertionError
» a, b = 2, 3
» assert a==b
↳ AssertionError

Suponha que uma loja monta um sistema para gerenciar suas vendas. Em algum momento o vendedor pode oferecer um desconto na compra mas o gerente determinou que o desconto não pode ser superior a 50%. Definimos uma função de cálculo do valor final da venda que impede que o preço final seja menor que metade do preço original, o maior que ele.

» def precoComDesconto(preco, desconto):
»     try:
»         precoFinal = preco * (1-desconto/100)
»         assert .5 <= precoFinal/preco <= 1
»     except AssertionError:
»         return 'Desconto inválido!'
»     else:    
»         return precoFinal

» print(precoComDesconto(120,50))
↳ 60.0

» print(precoComDesconto(120,55))
↳ Desconto inválido!

O último exemplo mostra que um AssertionError pode ser capturado como qualquer outra exceção lançada.

Exceções do tipo AssertionError não devem ser usadas em produtos finais, no código depois de todos os testes de erros foram executados. Parcialmente porque é possível executar o código desabilitando todas as instruções assert. Suponha que um desenvolvedor quer evitar que um usuário, que não o administrador do sistema, apague registros em um banco de dados.

» # não faça isso!
» def apagarRegistros(usr):
»     assert usr.isAdmin()
»     < código de apagamento >

Se o sistema for executado com desabilitação de assert qualquer usuário tem acesso ao apagamento de dados!

Erros lógicos

Outro tipo de erro são os erros lógicos que, provavelmente, ocupam a maior parte do tempo de debugging dos desenvolvedores. Eles ocorrem quando o código não tem erros de sintaxe nem exceções de tempo de execução mas foram escritos de forma que o resultado da execução é incorreto. Um exemplo simples seria de uma lista que começa a ler os elementos com índice i=1, o que faz com que o primeiro elemento seja ignorado. Esses erros podem ser complexos e difíceis de serem encontrados e corrigidos pois não causam a interrupção do programa nem lançam mensagens de advertência.

Os três exemplos abaixo mostram casos de erros lógicos.

» # 1) queremos o produto dos 9 primeiros números
» produto = 1
» for i in range(10):
»     produto *= i
» print(produto)
↳ 0

» # 2) queremos soma dos 9 primeiros números
» num = 0
» for num in range(10):
»     num += num
» print(num)
↳ 18

» # 3) queremos a soma dos quadrados dos 9 primeiros números
» soma_quadrados = 0
» for i in range(10):
»     iquad = i**2
» soma_quadrados += iquad
» print(soma_quadrados)
↳ 81

É muito difícil ou impossível escrever um codigo mais complexo sem cometer erros de lógica. Algumas sugestões podem ajudar a minorar esse problema:

  • Planeje antes de começar a escrever código:
    • Faça diagramas deixando claro quais são os dados de entrada do código e o que se espera obter. Tenha clareza sobre o objetivo de seu projeto.
    • Fluxogramas e pseudocódigo ajudam nesse aspecto.
  • Comente o código e use docstrings corretos para suas funções e classes:
    • Um código bem documentado é mais fácil de ser compreendido não só por outros programadores que talvez trabalhem em seu projeto como para você mesmo, quando tiver que rever um bloco algum tempo após tê-lo idealizado.
    • Docstrings podem ser acessados facilmente pelo desenvolvedor e usado por várias IDEs para facilitar seu acesso.
  • Escreva primeiro blocos de código de funcionamento geral, depois os detalhes, testando sempre cada etapa.
  • Teste o produto final com dados válidos e dados inválidos:
    • Faça testes usando valores esperados. É particularmente importante testar o código com valores limítrofes, tais como o mais baixo e o mais alto aceitável. Teste também usando valores incorretos que podem ser, inadvertidamente, pelo usuário final. Um exemplo comum, o usuário pode digitar a letra l ou invés do dígito 1. Valores fora da faixa esperada e de tipos diferentes devem ser experimentados.
    • Para aplicativos usados por muitos usuários finais, particularmente os executados na internet, use testadores que não aqueles que desenvolveram o código.

Instrumentos de depuração (debugging)

Uma das formas simples de encontrar erros no código consiste em escrever instruções print() nas partes onde desejamos observar um valor intermediário de alguma variável. Existem IDEs que permitem o acompanhamento em tempo real de cada valor, na medida em que ocorrem na execução do código. E, finalmente, existem programas auxiliares para a localização de erros. Algumas dessas ferramentas verificam a sintaxe do código escrito marcando os erros e sugerindo melhor estilo de programação. Outras nos permitem analisar o programa enquanto ele está em execução.

Pyflakes, pylint, PyChecker e pep8

Descritos na documentação do Python esses quatro utilitários recebem os arquivos *.py como input e analisam o código em busca de erros de sintaxe e alguns de erros de tempo de execução. Ao final eles imprimem avisos sugerindo melhor estilo de codificação e códigos ineficientes e potencialmente incorretos como, por exemplo, variáveis ​​e módulos importados que nunca são usados.

Pyflakes analisa as linhas de código sem importá-lo. Ele detecta menos erros que os demais aplicativos mas é mais seguro pois não há risco de executar código defeituoso. Ele também é mais rápido que as demais ferramentas aqui descritas.

Pylint e PyChecker importam o código e produzem listas mais extensas de erros e advertências. Eles são especialmente importantes quando se considera a funcionalidade de pyflakes muito básica.

Pep8 faz uma análise do código procurando por trechos com estilo de codificação ruim, tendo como padrão o estilo proposto na Pep 8 que é o documento de especificação para um bom estilo de codificação no Python.

Todos eles são usados com os comandos de comando, no prompt do sistema:

> pyflakes meuCodigo.py
> pylint meuCodigo.py
> pychecker meuCodigo.py
> pep8 meuCodigo.py

pdb


pdb é um módulo built-in, usado para depurar código enquanto ele está em execução. É possível usá-lo invocando-o como um script enquanto o código é executado ou importar o módulo e usar suas funções junto com o código do desenvolvedor. pdb permite que o código seja executado uma linha de cada vez, ou em blocos, inspeccionando a cada passo o estado do programa. Ele também emite um relatório de problemas que causam o término da execução por erros.

> import pdb
» def funcaoComErro(x):
»     ideia_ruim = x + '4'

» pdb.run('funcaoComErro(3)')
↳ > <string>(1)>module>()

Como script pdb pode ser executado da seguinte forma:
python3 -m pdb myscript.py.

Uma descrição mais completa de pdb pode ser encontrada em Python Docs: Pdb.

🔺Início do artigo

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: 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.

    Controle de Fluxo e Funções do Usuário

    Laços: Controle de Fluxo

    O interpretador de R processa as linhas de comandos de modo sequencial, uma linha após a outra. Muitas vezes é necessário bifurcar o código ou repetir um conjunto de linhas, dependendo de certas condições. Para isso temos os laços (loops) e testes lógicos.

    Teste lógico if() e else

    if(condicao){
        Instruções1
    } else {
        Instruções2
    }
    

    A condição para o teste deve ser uma comparação lógica resultando em TRUE ou FALSE. Instruções1 serão executadas se a condição for verdadeira, Instruções2 caso contrário.

    Teste vetorizado ifelse()

    ifelse(condicão, valor1, valor1)
    Retorna valor1 se condicao = TRUE, valor2 caso contrário.
    

    Instrução switch()

    switch(expr, valor1, ..., valorn)
        Se expr é um inteiro i, retorna valori (o i-ésimo valor)
        Se expr é um string os demais argumentos devem nomeados e switch
        retorna valor correspondente ao name = expr.
    

    Por exemplo:

    > # Teste if/else
    > n <- 17
    > if (n %% 2 == 0) {
         print(paste(m, " é par"))
     } else {
         print(paste(m, " é ímpar"))
     }
    [1] "17 é ímpar"
    
    > # ifelse
    > m <- 4
    > ifelse(m==3, "é", "não é")
    [1] "não é"
    > m <- 3
    > ifelse(m==3, "é", "não é")
    [1] "é"
    
    > # A função ifelse realiza internamente um
    > # loop nos componentes de um vetor (ou outro objeto)
    > teste <- c(1,2.3,4,5.5, 2.3, 7.3, 0.9)
    > resultado <- ifelse(teste > 5, "aprovado", "reprovado")
    > resultado
    [1] "reprovado" "reprovado" "reprovado" "aprovado" "reprovado" "aprovado" "reprovado"
    
    > # A operação acima tem idêntico resultado à:
    > teste <- c(1,2.3,4,5.5, 2.3, 7.3, 0.9)
    > resultado <- NULL
    > for (i in 1:length(teste)) {
           if (teste[i]>5) resultado[i] <-"aprovado"
           else resultado[i]<- "reprovado"
           }
    > resultado
    [1] "reprovado" "reprovado" "reprovado" "aprovado" "reprovado" "aprovado" "reprovado"
    > # A primeira forma, além de mais compacta, é mais eficiente e rápida.
    
    > # Uso de switch() com argumento inteiro:
    > print(switch(3,"um", "dois", "três", "quatro"))
    [1] "três"
    > # Outros exemplos de uso de switch() abaixo, com a instrução for
    

    Laço for()

    for(condicao) {
        Instruções1 ...
    }
    

    A condição para os laços for devem ser sempre do tipo var in seq, onde a variável var percorre uma sequência.

    Laço while()

    while(condicao) {
        Instruções ...
    }
    

    Executa as instruções enquanto a condição for verdadeira. Deve-se ter o cuidado de providenciar um mecanismo de saída para este laço.

    Laço repeat()

    repeat() {
        Instruções ...
    }
    

    Executa as instruções indefinidamente. Uma saída para este laço pode ser forçada com a instrução break.

    As seguintes instruções são usados juntamente com os laços for, while e repeat

    Instrução Efeito
    break força a saída de um laço
    next pula uma iteração do laço (retornando para seu início)
    return retorna o valor de uma função
    > # laço for
    > for(i in c(1,3,5,7)) {print(paste(i,"^2 = ",i^2, sep =""))}
    [1] "1^2 = 1"
    [1] "3^2 = 9"
    [1] "5^2 = 25"
    [1] "7^2 = 49"
    
    > # Laço while
    > n <- 1
    > while(n < 5) {
        print(paste(n, "< 5"))
        n<-n+1
    }
    [1] "1 < 5"
    [1] "2 < 5"
    [1] "3 < 5"
    [1] "4 < 5"
    
    > # Laço repeat, o mesmo que while(TRUE)
    n <- 1
    > repeat {
        print(paste(n, "< 4"))
        n <- n+1
        if(n == 4) break
    }
    [1] "1 < 4"
    [1] "2 < 4"
    [1] "3 < 4"
    > # Saltando dentro de um laço
    > for (i in 1:10) {
    	if(i<4 | i>6) next
    	print(i)
    }
    [1] 4
    [1] 5
    [1] 6
    > # Observe que a variável continua existindo após o loop
    > print(i)
    [1] 10
    > # Uso de switch() com argumento inteiro:
    > for (i in 1:4) print(switch(i,"um", "dois", "três", "quatro" ))
    [1] "um"
    [1] "dois"
    [1] "três"
    [1] "quatro"
    
    > # Uso de switch() com argumento de string:
    > sinto <- c("medo", "alegria")
    > for (i in sinto) {
    	print(switch(i, triste = "alegre-se", medo = "calma", alegria = "aproveita") )
    	}
    [1] "calma"
    [1] "aproveita"
    

    Funções do Usuário

    O usuário pode criar funções em R de acordo com suas necessidades. Elas geralmente servem para armazenar uma série de instruções que será utilizada repetidamente ou apenas para organizar um bloco de código mais complexo. Funções possuem a seguinte estrutura básica:

    funcao <- function(arg1, ..., argn) {
         lista de Instruções
         return(objeto)
    }
    

    A instrução return é opcional. Se omitida a função retornará o resultado da última operação realizada. Os colchetes podem também ser omitidos se a função consiste em apenas uma linha de código.
     
    A função é chamada fornecendo-se seus argumentos
    funcao(varg1, …, argn)
    Quando ela retorna um valor que será usado em seguida atribuímos seu valor a uma variável:
    var <- funcao(arg1, …, argn)

    Qualquer objeto, ou nenhum, pode ser retornado pela função. Quanto aos argumentos eles podem ou não ser nomeados. Argumentos não nomeados devem ser identificados pela sua posição na chamada da função. Se forem nomeados eles podem receber valores default na definição da função que serão usados caso sejam omitidos quando a função é invocada.

    > funcao1 <- function(x, y) {
    			z <- x+y
    			return(x + y^z) }
    > funcao1(2, 3)
    [1] 245
    > # O mesmo resultado seria obtido se omitíssemos a instrução return:
    > funcao1 <- function(x, y) x + y^(x+y)
    
    > # Com argumentos nomeados e com valores default:
    > funcao2 <- function(inicio=1,fim=10) {
    			 v <- inicio:fim
    			 return(v) }
    > funcao2()
     [1]  1  2  3  4  5  6  7  8  9 10
    > funcao2(5)  # apenas o primeiro arg é fornecido
    [1]  5  6  7  8  9 10
    > funcao2(,5)  # segundo arg é reconhecido pela posição
    [1] 1 2 3 4 5
    > funcao2(fim=13)  # segundo arg é reconhecido pelo nome
    [1]  1  2  3  4  5  6  7  8  9 10 11 12 13
    > # A instrução return não é obrigatória,
    > # nem os colchetes para uma função de única linha
    modulo <- function(z) sqrt(Re(z)^2 + Im(z)^2)
    > modulo(4+5i)
    [1]  6.40312
    > # A função tratará, sempre que possível, qualquer tipo de argumento
    > funcao3 <- function(x, y) { return(x + y) }
    > funcao3(c(1,2,3), c(4,5,6))
    [1] 5 7 9
    > # Você pode visualizar a constituição de uma função
    > funcao1
    function(inicio=1,fim=10) {return(inicio:fim)}
    > # Para exibir seus argumento use:
    > args(funcao1)
    function (inicio = 1, fim = 10)
    NULL
    
    A função args() pode ser usada em sessões interativas para mostrar os argumentos de uma função. Para descobrir quais são esses argumentos e seus valores default programaticamente use a função formals().

    Pode-se também especificar que um argumento é nulo se não for declarado explicitamente na chamada da função como, por exemplo, em:

    f <- function(a, b = 1, c = NULL) {...}.

    Neste caso deve-se testar no corpo da função se o argumento foi fornecido, antes de usá-lo. É importante notar que uma variável inicializada dentro do corpo de definição da função tem seu escopo limitado à esta função (e não pode ser usada fora dela).
    A instrução de return, embora não obrigatória, pode ser útil para interromper o fluxo de comandos, forçando o término da função. No exemplo abaixo calculamos, apenas como exercício, o fatorial de um escalar. Claro que R já tem uma função fatorial embutida que calcula fatorial em vetores e matrizes.

    > fat <- function(n) {
    	 m <- as.integer(n)
    	 if (length(n)!=1) return("O argumento deve ser um escalar")
    	 if (m!=n) return("O argumento deve ser inteiro")
    	 if (m<0) return("O argumento deve ser positivo")
    	 return(ifelse(m==0, 1, prod(1:m))
     }
    > fat(4.3)
    [1] "O argumento deve ser inteiro"
    > fat(-3)
    [1] "O argumento deve ser positivo"
    > fat(1:2)
    [1] "O argumento deve ser um escalar"
    > fat(0)
    [1] 1
    > fat(9)
    [1] 362880
    > # Usando factorial
    > u <- 1:9
    > dim(u)<-c(3,3)
    > u
    	 [,1] [,2] [,3]
    [1,]    1    4    7
    [2,]    2    5    8
    [3,]    3    6    9
    > factorial(u)
    	 [,1] [,2]   [,3]
    [1,]    1   24   5040
    [2,]    2  120  40320
    [3,]    6  720 362880
    

    O argumento ... (3 pontos) tem um significado especial em R. Ele indica que um número indeterminado de argumento podem ser passados para a função e é particularmente útil quando existe outra função aninhada (com muitos argumentos) no corpo da primeira.

    > montarLinha <- function(x, ...){
    				 print(paste("Existem ", x, "cores:", ...))
    				 }
    > montarLinha(3, "vermelho", "verde", "azul")
    [1] "Existem  3 cores: vermelho verde azul"
    

    Função podem ser aninhadas, ou seja, é possível chamar uma função de dentro de outra funcão:

    > funcao1 <- function(txt) { return(paste("modificação 1: ",txt))}
    > funcao2 <- function(txt, t2=NULL) {
    			 retorna <- ""
    			 if (is.character(txt)) {
    				 retorna <- paste("modificação2: ", funcao1(txt))
    			 } else {
    				 retorna <- "Argumento deve ser um string..."
    			 }
    			 if (!is.null(t2)) retorna <- paste(retorna,"!")
    			 return(retorna)
      }
    > print(funcao2(2))
    [1] "Argumento deve ser um string..."
    > print(funcao2("testando"))
    [1] "modificação2:  modificação 1:  testando"
    > print(funcao2("Inserindo o 2o argumento", 1))
    [1] "modificação2:  modificação 1:  Inserindo o 2o argumento !"
    

    Funções podem retornar qualquer um dos objetos de R, inclusive outras funções:

    > potencia <- function(ordem) {
    			  f <- function(x) {x ^ ordem}
    			  return(f)
    			  }
    > quarta <- potencia(4)    # define a função f(x) = x^4
    > quarta(2)
    [1] 16
    > quadrado <- potencia(2)  # define a função f(x) = x^2
    > quadrado(15)
    [1] 225
    

    Observação: Fizemos uso das funções is.character(var) e is.null(var) que testam, respectivamente, se a variável var é do tipo character ou null. Muitas outras funções de teste existem e são muito úteis, principalmente em scripts. Associadas a elas estão as funções de conversão que forçam a transformação de um tipo em outro, quando possível.
    Algumas destas funções estão listadas abaixo:

    Teste Conversão
    is.numeric() as.numeric()
    is.character() as.character()
    is.vector() as.vector()
    is.matrix() as.matrix()
    is.data.frame() as.data.frame()
    is.factor() as.factor()
    is.logical() as.logical()
    is(var, type) as(var, type)
    > is.character("1")            #  TRUE
    > is("1", "character")         #  TRUE
    > is("1", "numeric")           #  FALSE
    > is(1, "numeric")             #  TRUE
    > is(1i, "complex")            #  TRUE
    > n <- as("125", "numeric")    #  n = 125
    > is.logical(1==2)             #  TRUE
    > is.vector(1:2)               #  TRUE
    > is.vector("1")               #  TRUE (um vetor com um componente)
    > as.logical(1)                #  TRUE (as.logical(0) = FALSE
    > dt<- as.Date("2018-11-25")   #  dt = "2018-11-25"
    > class(dt)                    #  "Date"
    


    Programação com R


    Aquisição de Dados

    Operadores e Funções Internas

    Operadores

    Os seguintes operadores matemáticos estão definidos em R:

    Operador Descrição Exemplo
    + adição
    subtração
    * multiplicação
    / divisão 3/2 = 1.5;
    ^ ou ** exponenciação 3^2 = 6, 2**3 = 8
    %% módulo 9 %% 2 = 1
    %/% divisão inteira 9 %/% 2 = 4

    Os seguintes operadores lógicos estão definidos:

    Operador Descrição Exemplo
    < menor que 5 < 7 = TRUE
    <= menor ou igual 3 <= 9 = TRUE
    > maior que 5 > 7 = FALSE
    >= maior ou igual 7 >= 7 = TRUE
    == igual 3 == 5 = FALSE
    != diferente 3 != 5 = TRUE
    !x não x !(7 < 3) = TRUE
    x | y x ou y c(T, T, F) & c(T, F, F) = (T, T, F)
    x || y ou (apenas 1º elemento examinado) c(T, F, F) || c(F, F, F) = TRUE
    x & y x e y c(T, T, F) & c(T, F, F) = (T, F, F)
    x & y e (apenas 1º elemento examinado) c(T, F, F) && c(F, F, F) = FALSE
    isTRUE(x) verifica se x é TRUE isTRUE(7 < 9) = TRUE

    Outros operadores:

    Operador Descrição Exemplo
    : (dois pontos) cria uma sequência de números 2:6 = (2, 3, 4, 5, 6)
    %in% pertence 3 %in% 1:4 = TRUE,5 %in% 1:4 = FALSE
    %*% multiplicação de matrizes por sua transposta A %*% t(A)
    any(condição sobre x) TRUE se algum elemento de x satisfaz a condição any(x==9)
    all(condição sobre x) TRUE se todos os elementos de x satisfazem a condição all(x!=9)

    Nos elementos de um vetor as operações ocorrem entre componentes de mesma posição em cada vetor.

    > # Usando dois vetores de mesmo comprimento
    > u <- c(1,2,3,4,5) > v <- c(5,4,3,2,1) > u+v
    [1] 6 6 6 6 6
    > u-v
    [1] -4 -2  0  2  4
    > u*v
    [1] 5 8 9 8 5
    > u**v
    [1]  1 16 27 16  5
    > u %% v
    [1] 1 2 0 0 0
    > u %/% v
    [1] 0 0 1 2 5
    > media <- (u + v) / 2 > media
    [1] 3 3 3 3 3
    
    > # Se os operandos têm comprimentos diferentes então um deve
    > # ter comprimento múltiplo do outro. n cópias do vetor menor
    > # serão usadas na operação e o resultado terá o tamanho do maior.
    > u + c(1,2,3)
    [1] 2 4 6 5 7
    Warning message:
    In u + c(1, 2, 3) :
      longer object length is not a multiple of shorter object length
    > u + 10
    [1] 11 12 13 14 15
    > w <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) > u+w
     [1]  2  4  6  8 10  7  9 11 13 15
     w/u
    [1]  1.000000 1.000000 1.000000 1.000000 1.000000
    [6]  6.000000 3.500000 2.666667 2.250000 2.000000
    
    ># Lembrando que u = (1,2,3,4,5)
    > any(u > 4)
    [1] TRUE
    > any(u > 5)
    [1] FALSE
    > all(u < 4)
    [1] FALSE
    

    Comparações lógicas entre vetores também são efetuadas entre elementos, um a um.

    > u > v
    [1] FALSE FALSE FALSE  TRUE  TRUE
    > x <- 1:10 > x
     [1]  1  2  3  4  5  6  7  8  9 10
    > x[x < 3 | x > 7]
    [1]  1  2  8  9 10
    > x[x > 2 & x < 8] [1] 3 4 5 6 7 >
    > # O operador %in% busca valores entre todos de um vetor
    > # Lembrando que u = (1, 2, 3, 4, 5)
    > 1 %in% u
    [1] TRUE
    > 6 %in% u
    [1] FALSE
    
    

    Lembrando que x = 1:10, vamos verificar com maior detalhe a operação:

        x[x < 3 | x > 7] = (1, 2, 8, 9, 10)
    Por partes:
        x < 3 = (T, T, F, F, F, F, F, F, F, F)
        x > 7 = (F, F, F, F, F, F, F, T, T, T)
        x < 3 | x > 7 = (T, T, F, F, F, F, F, T, T, T)
    Finalmente
        x[(T, T, F, F, F, F, F, T, T, T)] = (1, 2, 8, 9, 10)
    Portanto selecionamos os componentes do vetor x que são menores que 3 ou maiores que 7.
    

    Operações pode ser realizadas entre membros de outros objetos compostos. Por exemplo, usando um data frame:

    > mdata<-data.frame(x = c(2, 2, 6, 4), y = c(3, 4, 2, 8)) > mdata
       x  y
    1  2  3
    2  2  4
    3  6  2
    4  4  8
    > attach(mdata)
    > mdata$soma <- x + y > mdata$media <- (x + y)/2 > detach(mdata)
    > # As operações acima acrescentaram dois novos campos à mdata:
    > mdata
       x  y  soma  media
    1  2  3    5   2.5
    2  2  4    6   3.0
    3  6  2    8   4.0
    4  4  8   12   6.0
    > # O mesmo tipo de operação pode ser feita de forma
    > # alternativa usando-se a função transform
    > valores <- data.frame(x=c(1,4,6,8), y=c(1,3,5,7))
    > valores <- transform(valores, soma = x+y, media = (x+y)/2, teste = x>y)
    > valores
      x y soma media teste
    1 1 1    2   1.0 FALSE
    2 4 3    7   3.5  TRUE
    3 6 5   11   5.5  TRUE
    4 8 7   15   7.5  TRUE
    

    Funções internas

    Quase toda a funcionalidade do R é obtida através de funções. Um grande número delas faz parte do bloco básico, carregado por default, e muitas outras podem ser utilizadas através da instalação de pacotes (packages).

    Funções numéricas

    Função Descrição
    abs(x) \(\left|x\right|\), valor absoluto
    sqrt(x) \(\sqrt x\), raiz quadrada
    ceiling(x) menor inteiro acima: ceiling(3.475) = 4
    floor(x) maior inteiro abaixo: floor(3.475) = 3
    trunc(x) truncamento: trunc(5.99) is 5
    round(x, digits=n) arredondamento: round(3.475, digits=2) = 3.48
    signif(x, digits=n) n dígitos significantes: signif(3.475, digits=2) = 3.5
    cos(x), sin(x), tan(x) funções trigonométricas, seno, cosseno, tangente
    acos(x), cosh(x), acosh(x) outras funções trigonométricas
    log(x) \(\ln(x)\) logaritmo natural (base e)
    log10(x) \(\log(x)\), logaritmo de base 10
    exp(x) \(e^x\), exponencial

    Funções de texto

    Função Descrição
    substr(x, a, b) retorna ou substitui parte de uma string x, da posição a até b.
    sub(texto1, texto2, x) substitui, na string x, o texto1 pelo texto2.
    grep(padrao, x , ignore.case=FALSE, fixed=FALSE) Procura padrao em x. Se fixed = FALSE o padrão é uma expressão regular, caso contrário um texto simples. Retorna índice de localização.
    strsplit(x, sep) Quebra o vetor x em sep.
    paste(…, sep=”-“) Concatena strings usando a string sep como separador.
    paste(…, collapse=”-“) Monta uma única string com partes do argumento, usando collapse como separador.
    toupper(x) retorna texto em letras maiúsculas
    tolower(x) retorna texto em letras minúsculas
    nchar(x) retorna o comprimento da string x
    > # Retornar uma substring
    > substr("abcdef",3,5)
    [1] "cde"
    > cores <- c("azul escuro", "verde escuro", "preto")
    > substr(cores, 1, 5)
    [1] "azul " "verde" "preto"
    > # Substituir caracteres
    > sub("strings", "texto", "Trocar strings")
    [1] "Trocar texto"
    > # A substituição pode ser feita em todos os componentes do vetor
    > sub("escuro", "claro", cores)
    [1] "azul claro"  "verde claro" "preto"
    
    > # Partir texto
    > strsplit("as casas de maria", " ")
    [[1]]
    [1] "as"  "casas"  "de"  "maria"
    > strsplit("ABCDEF", "BC")
    [[1]]
    [1] "A"   "DEF"
    > strsplit("ABCDEF", "")
    [[1]]
    [1] "A" "B" "C" "D" "E" "F"
    > # As funções agem em todos os comonentes do objeto
    > x <- c("estudar", "R", "no site") > substr(x,1,2)
    [1] "es" "R"  "no"
    
    > # Uso se expressão regular
    > s <- "www.phylos.net" > sub("p.+s","filosofia", s)
    [1] "www.filosofia.net"
    
    > toupper("falando alto")
    [1] "FALANDO ALTO"
    > tolower("Não GRITE")
    [1] "não grite"
    
    > # Para juntar strings:
    > paste("primeiro", "segundo", "terceiro")
    [1] "primeiro segundo terceiro"
    > paste("primeiro", "segundo", "terceiro", sep = ", ")
    [1] "primeiro, segundo, terceiro"
    > # Valores numéricos são convertidos em strings
    > paste(1,2,3, sep="-")
    [1] "1-2-3"
    > paste(1,2,3, sep="")
    [1] "123"
    > paste("tentativa", 1)
    [1] "tentativa 1"
    > tent <- paste("tentativa", 1:5) > tent[5]
    [1] "tentativa 5"
    

    Funções Auxiliares Úteis

    Função Descrição
    seq(from, to, by) gera sequência numérica (início, fim, passo)
    rep(x, times=n) repete x n vezes
    cut(x, n) divide variável contínua (numérica) em fator com n níveis
    pretty(x,n) divide variável contínua em n intervalos
    cat( , file = nomeArquivo, append = FALSE) concatena objectos em e os envia para o console ou arquivo nomeArquivo (se existir)
    > seq(12, 30, 2)
    [1] 12 14 16 18 20 22 24 26 28 30
    > # Forçando o resultado a ter 5 elementos
    > seq(from=0, to=20, length.out=5)
    [1]  0  5 10 15 20
    
    > rep("ha", times=4)
    [1] "ha" "ha" "ha" "ha"
    > # Repetindo cada elemento n vezes
    > rep(c(1,2,3), each=2)
    [1] 1 1 2 2 3 3
    > $ Repete o primeiro elemento 4 x, o segundo 2 x
    > rep(c("a", "b"), times = c(4,2))
    [1] "a" "a" "a" "a" "b" "b"
    
    > # cut permite a criação de fator. Abaixo, com 4 níveis:
    > idades <- c(12, 14, 16, 17, 34, 32, 12, 12, 11) > cut(idades, breaks=4)
    [1] (11,16.8]   (11,16.8]   (11,16.8]   (16.8,22.5] (28.2,34]
    [5] (28.2,34]   (11,16.8]   (11,16.8]   (11,16.8]
    Levels: (11,16.8] (16.8,22.5] (22.5,28.2] (28.2,34]
    > # Uso de pretty
    > pretty(1:20, n=2)
    [1]  0 10 20
    > pretty(1:20, n=10)
    [1]  0  2  4  6  8 10 12 14 16 18 20
    > # Uso de cat
    > nome <- "Ana" > cat("Olá",nome,"\b.\n", "\t \"Bom dia!\"")
    Olá Ana.
     	 "Bom dia!"
    

    Usamos na linha de demonstração de cat() usamos as sequências de escape \n (newline), \b (backspace), \t (tab), \" (aspas duplas)

    Funções úteis para a manipulação de Objetos

    Listamos em seguida algumas das funções importantes para a leitura e edição de objetos.

    Função Descrição
    length(obj) retorna o número de elementos ou componentes do objeto
    dim(obj) retorna as dimensões do objeto.
    str(obj) exibe a estrutura do objeto.
    head() lista os seis primeiros elementos do objeto
    tail() lista os seis últimas elementos do objeto
    class(obj) retorna a classe do objeto.
    mode(obj) exibe como o objeto foi armazenado
    names(obj) exibe os nomes de componentes do objeto
    c(obj1, …, objn) concatena objetos em um vector
    cbind(obj1, …, objn) combina objetos em colunas
    rbind(obj1, …, objn) combina objetos em linhas
    obj, print(obj) exibe / imprime objeto.
    head(obj) lista a primeira parte do objeto
    tail(obj) lista a parte final do objeto
    ls() exibe lista dos objetos carregados, equivalenta à função objects()
    rm(obj1, …, objn) remove um ou mais objetos
    rm(list = ls()) )remove todos os objetos
    novoObj <- edit(obj) edita objeto e o armazena como novoObj
    fix(obj) edita objeto salvando nele as alterações

    Data e Hora

    Datas são armazenadas internamente no R como o número de dias decorridos desde 01/01/1970. Datas anteriores são representadas como números negativos.

    Função Descrição
    as.Date(string) converte a string em uma data
    Sys.Date( ) retorna a data de hoje
    date() retorna data e hora corrente
    data2 – data1 retorna a diferença entre data1 e data2 em dias
    as.character(data) retorna a data como um string
    format(data, format=strDeFormato) formata a data segundo o string de formatação
    weekdays(data) dia da semana correspondente a data (ou datas)
    months(data) mês não abreviado correspondente a data (ou datas)
    quarters(data) Quarter (Q1, Q2, Q3, Q4)
    seq(from=data1, to=data2, by=n) sequência de datas de data1 até data2, em passos n (dias)
    > # Data do sistema (um string, formato ano/mês/dia)
    > Sys.Date()
    [1] "2018-11-23"
    > # Transforma esta string em uma data
    > data1 <- as.Date(Sys.Date()) > # Soma 250 dias à data1
    > data2 <- data1 + 250 > data2 - data1
    [1] Time difference of 250 days
    > # Sequência de datas começando em data1 até data2 com passos de 50 dias
    > seq(from=data1, to=data2, by=50)
    [1] "2018-11-23" "2019-01-12" "2019-03-03"
    [4] "2019-04-22" "2019-06-11" "2019-07-31"
    > # Produz sequência de datas de data1 até data2 com 5 elementos
    > seq(from=data1, to=data3, length.out=5)
    [1] "2018-11-23" "2019-01-24" "2019-03-28"
    [4] "2019-05-29" "2019-07-31"
    > weekdays(data1)
    [1] "sexta"
    > months(data1)
    [1] "novembro"
    > quarters(data1)
    [1] "Q4"
    > format(data1, "%d/%m/%y")
    [1] "23/11/18"
    > format(data1, "%d/%m/%Y")
    [1] "23/11/2018"
    > format(data1, "%A, %d de %B de %Y")
    [1] "sexta, 23 de novembro de 2018"
    > # As funções se aplicam a vetores e outros objetos
    > strData <- c("01/05/1965", "08/16/1975")
    > datas <- as.Date(strDatas, "%m/%d/%Y") > datas
    [1] "1965-01-05" "1975-08-16"
    > Para lidar com o formato brasileiro de dia/mes/ano podemos fazer
    > dBr <- as.Date("23/11/2018", "%d/%m/%Y") > dBr
    [1] "2018-11-23"
    

    Os seguintes símbolos (ou máscaras) podem ser usados com datas. Os resultados dependem das configurações de data/hora locais, que podem ser visualizadas com o comando Sys.localeconv().

    Símbolo Descrição Exemplo
    %d dia, numérico 01 a 31
    %a dia da semana, abreviado Mon, (Seg)
    %A dia da semana por extenso Monday (segunda)
    %m mês, numérico 01 a 12
    %b nome do mês, abreviado Feb, (Fev)
    %B nome do mês por extenso February, (Fevereiro)
    %y ano em 2 dígitos 18
    %Y ano em 4 dígitos 2018

    Funções Estatísticas

    Função Descrição
    mean(x, trim=0,na.rm=FALSE) média do objeto x
    sd(x) desvio padrão do objeto x
    var(x) variância
    mad(x) desvio absoluto médio
    median(x) mediana
    quantile(x, probs) quantil de x, probs= vetor numérico com probabilidades em [0,1]
    range(x) intervalo
    sum(x) soma
    diff(x, lag=1) diferenças defasadas, lag = defasagem a usar
    min(x) mínimo de x
    max(x) máximo de x
    scale(x, center=TRUE, scale=TRUE) centro da coluna ou padronizar uma matriz
    > x <- c(123, 234, 345, 242, 34, 100, NA)
    > mean(x)
    [1] NA
    > # mesma operação ignorando valor NA
    > mean(x, na.rm=TRUE)
    [1] 179.6667
    > # redefinindo x
    > x <- c(123, 234, 345, 242, 34, 100)
    > sum(x)
    [1] 1078
    > range(x)
    [1]  34 345
    > min(x)
    [1] 34
    > max(x)
    [1] 345
    > mean(x)
    [1] 179.6667
    > # Eliminando valores 20% nas bordas da amostra
    > mean(x, trim=.2)
    [1] 174.75
    > sd(x)
    [1] 113.9731
    > var(x)
    [1] 12989.87
    > mad(x)
    [1] 105.2646
    > median(x)
    [1] 178.5
    

    A tabela seguinte lista funções relacionadas com distribuições probabilísticas. Para gerar sequências pseudo-randômicas (que podem ser replicadas mais tarde) use set.seed(1234) (ou outro inteiro).

    Função Descrição
    dbinom(x, size, prob)
    pbinom(q, size, prob)
    qbinom(p, size, prob)
    rbinom(n, size, prob)
    distribuição binomial,
    onde size = tamanho da amostra e prob é probabilidade
    de sucesso em cada experimento.
    dpois(x, lambda)
    ppois(q, lambda)
    qpois(p, lambda)
    rpois(n, lambda)
    Distribuição de Poisson com m=std=lambda
    dunif(x, min=0, max=1)
    punif(q, min=0, max=1)
    qunif(p, min=0, max=1)
    runif(n, min=0, max=1)
    Distribuição uniforme, segue mesmo padrão
    que a distribuição normal acima.

    Voltaremos ao estudos destas funções mais tarde.

    A Distribuição Normal

    Por completeza listamos aqui algumas funções de distribuição de probabilidades. As funções de densidade, de distribuição, quantil e a geração aleatória para a distribuição normal podem ser obtidas com média e desvio padrão especificados.

    A função rnorm gera dados aleatórios com distribuição normal:

        dnorm(x, mean = 0, sd = 1, log = FALSE)
        pnorm(q, mean = 0, sd = 1, lower.tail = TRUE, log.p = FALSE)
        qnorm(p, mean = 0, sd = 1, lower.tail = TRUE, log.p = FALSE)
        rnorm(n, mean = 0, sd = 1)
    

    São argumentos:

    x, q vetor de quantils.
    p vetor de probabilidades.
    n número de observações. Se length(n) > 1, the length is taken to be the number required.
    mean vetor de médias.
    sd vetor de desvios padrão.
    log, log.p logical; se TRUE, probabilidades são dadas como log(p).
    lower.tail logical; se TRUE (default), probabilidades são P[X ≤ x]; caso contrário, P[X > x].

    Se a média e o desvio padrão não são especificados eles assumem o valor default 0 e 1, respectivamente.

    A distribuição normal tem densidade:

    $$
    f(x) = \frac{1}{\sigma \sqrt{2\pi}} \text{e}^{-\frac{(x-\mu)^2}{2\sigma ^2}}
    $$

    onde \(\mu\) é a média da distribuição e \(\sigma\) é o desvio padrão.

    dnorm fornece a densidade, pnorm a função de distribuição, qnorm a função quantil, rnorm gera os desvios aleatórios. O comprimento do resultado é determinado por n para rnorm. Para as demais funções ele é igual ao maior comprimento dos argumentos numéricos.

    Os argumentos numéricos (exceto de n) são reciclados para o tamanho do resultado. Apenas os primeiros elementos dos argumentos lógicos são usados.

    > x <- pretty(c(-3,3), 100)
    > y <- dnorm(x)
    > plot(x, y, type = "l", xlab = "Desvio Normal", ylab = "Densidade")
    
    Distribuição normal

    Este código gera o gráfico exibido à direita. Veremos em breve maiores detalhes sobre o uso da função plot() usada para gerar o gráfico.

    A distribuição normal é uma das mais utilizadas na modelagem dos fenômenos naturais e das ciências naturais e sociais. Ela é também chamada de distribuição de Gauss ou de Laplace–Gauss, em referência aos matemáticos Pierre-Simon Laplace (1749-1827) e Carl Friedrich Gauss (1777-1855).


    Controle de Fluxo e Funções do Usuário