Introdução a Python - Parte 1

De MediaWiki do Campus São José
Revisão de 08h24min de 19 de outubro de 2019 por Msobral (discussão | contribs) (→‎Estruturas de repetição)
(dif) ← Edição anterior | Revisão atual (dif) | Versão posterior → (dif)
Ir para navegação Ir para pesquisar

Referências

Um primeiro exemplo

Qualquer tutorial de programação que se preze inicia com um exemplo do tipo Hello world !. Pois bem, em Python isso pode ser feito assim:

print('Hello world !')

Para executá-lo, há pelo menos duas maneiras:

  1. Usando o prompt do interpretador Python: isso é parecido com o uso do shell em um terminal.
    1. Em um terminal execute o interpretador Python:
      $ python3
      Python 3.4.3 (default, Sep 14 2016, 12:36:27) 
      [GCC 4.8.4] on linux
      Type "help", "copyright", "credits" or "license" for more information.
      >>>
      
    2. No prompt do interpretador digite a instrução para mostrar o Hello world ! e em seguida tecle ENTER:
      >>> print('Hello world !')
      
    3. Você deve ver na tela o resultado:
      >>> print('Hello world !')
      Hello world !
      >>>
      
  2. Criando um arquivo de script: um script é um programa executado por meio de um interpretador. Se a instrução para o Hello world ! for gravada em um arquivo da maneira mostrada a seguir, esse arquivo pode ser considerado um programa Python. Sendo assim:
    1. Crie o arquivo hello.py com seu editor favorito, e nele grave este conteúdo:
      #!/usr/bin/python3
      
      print('Hello world !')
      
    2. Ative a permissão de execução do seu arquivo hello.py:
      chmod +x hello.py
      
    3. Por fim, execute o programa hello.py:
      $ ./hello.py
      Hello world !
      $
      
    4. Uma forma alternativa de executar o script é esta:
      $ python3 hello.py
      Hello world !
      $
      

Um pouco de história


Zen de Python

  • Belo é melhor que feio
  • Explícito é melhor que implícito
  • Simples é melhor que complexo
  • Complexo é melhor que complicado
  • Plano é melhor que aninhado
  • Esparso é melhor que denso
  • Legibilidade é importante
  • Casos especiais não são especiais o suficiente para quebrar as regras
  • Embora praticidade vença pureza
  • Erros nunca deveriam passar silenciosos
  • A não ser que explicitamente silenciados
  • Em face a ambiguidade, recuse a tentação de adivinhar
  • Deveria existir uma, e preferencialmente somente uma, maneira óbvia de fazer
  • Embora essa forma possa não ser óbvia de primeira, a não ser que você seja um holandês
  • Agora é melhor que nunca
  • Embora nunca é frequentemente melhor do que agora mesmo
  • Se a implementação é difícil de explicar, é uma má ideia
  • Se a implementação é fácil de explicar, pode ser uma boa ideia
  • Namespaces são uma grande e sensata ideia -- vamos fazer mais desses !

Uma simples (?) aplicação


Existem muitas possíveis aplicações que podem ser criadas com Python. Portanto, o critério para escolher uma aplicação como tema para guiar esta introdução à linguagem é um tanto arbitrária. Nem por isso o programa a ser apresentado deixa de ser interessante. Afinal, o principal ponto é demonstrar como se pode criar com facilidade uma aplicação que faz uma tarefa não tão simples.

Imagine que seja necessário um programa que extraia automaticamente imagens existentes em páginas web. Esse programa basicamente acessa uma URL, e no documento web lá existente ele procura por imagens e as transfere para o computador. Esse programa precisaria do seguinte:

  1. Uma forma de obter o conteúdo de uma página web, dada sua URL.
  2. Identificar todas as referências a imagens contidas nessa página.
  3. Transferir cada uma das imagens para o disco local.


A primeira versão dessa aplicação pode ser vista a seguir. Ela somente mostra os nomes dos links das imagens existentes em uma determinada página. A URL dessa página deve ser informada como argumento de linha de comando:

#!/usr/bin/python3

# Importa alguns modulos necessarios

# Modulo de sistema (um modulo basico)
import sys

# Este modulo fornece um interpretador de conteúdo HTML
from bs4 import BeautifulSoup

# Este modulo fornece um acessador de conteudo web (cliente HTTP)
from urllib import request

# define a variavel "url", cujo conteudo eh o primeiro argumento de linha de comando, 
# o qual deve conter uma URL
url = sys.argv[1]

# cria um acessador para acessar a URL
con = request.urlopen(url)

# Le todos os bytes do conteudo web identificado pela URL
dados = con.read()

# encerra a conexao do acessador
con.close()

# cria um analisador de conteudo HTML
parser = BeautifulSoup(dados, 'lxml')

# extrai uma lista de elementos HTML do tipo "img"
imagens = parser.find_all('img')

# itera a lista de elementos img
for img in imagens:
  # mostra o nome da imagem
  print(img['src'])

sys.exit(0)


Para executá-la, deve-se aplicar a técnica 2 (ver Primeiro exemplo):

  1. Copie o conteúdo do script Python para um arquivo chamado app1.py.
  2. Ative a permissão de execução desse arquivo
  3. Execute-o informando uma URL como argumento. Os caminhos das imagens serão mostrados em seguida (um por linha). Ex:
    $ ./app1.py http://mcc.sj.ifsc.edu.br 
    http://mcc.sj.ifsc.edu.br/files/2016/10/MCC_2016_TOPO-SITE_v3.jpg
    http://mcc.sj.ifsc.edu.br/files/2016/07/imagem_capa_site_mcc2016-1.png
    http://mcc.sj.ifsc.edu.br/wp-content/themes/responsive/core/icons/twitter-icon.png
    http://mcc.sj.ifsc.edu.br/wp-content/themes/responsive/core/icons/facebook-icon.png
    

OBS: esta aplicação depende de uma biblioteca Python que precisa antes ser instalada. No Ubuntu deve-se fazer isto:

sudo apt install python3-pip
pip3 install beautifulsoup4


Essa aplicação será explorada ao longo deste curso, quando diversas melhorias serão realizadas.

Elementos da linguagem

Python é uma linguagem com algumas características muito particulares, as quais a simplificam e a tornam muito rica e expressiva. Nesta parte do curso, alguns elementos básicos da linguagem serão apresentados.

Apresentação de dados na tela

Uma necessidade elementar de um programa é apresentar informações na tela na forma de texto simples. Python oferece a função predefinida print, que mostra texto na tela de forma simples e com grande flexibilidade. Ela já foi vista no exemplo Hello world !, e aqui alguns detalhes sobre como usá-la serão mostrados.

A função print pode ser usada para mostrar um texto. Note que uma constante string pode ser delimitada tanto por aspas simples quanto duplas:

print('Um texto')
print("Outro texto")


Múltiplos argumentos podem ser passados à função, bastando separá-los por vírgulas. Ao serem apresentados, um espaço em branco é inserido entre eles:

print('Um texto',"Outro texto")

Valores de diferentes tipos podem ser mostrados com print, tais como números inteiros e ponto flutuante:

print('Numero inteiro:', 5)
print('Numero ponto-flutuante:', 3.1416)

Naturalmente, os valores a serem apresentados podem estar contidos em variáveis:

x = 5
pi = 3.1416
print('Numero inteiro:', x)
print('Numero ponto-flutuante:', pi)

Quando múltiplos valores são apresentados, pode-se modificar o separador inserido entre eles. Como já informado, incialmente o separador é um espaço em branco, mas ele pode ser qualquer coisa:

x = 5
pi = 3.1416
print('Numero inteiro:', x, sep='-->')
print('Numero ponto-flutuante:', pi, sep='...')


Por fim, print por default adiciona uma quebra de linha ao final dos dados apresentados, mas isso pode ser alterado. Por exemplo, se for desejável mostrar uma vírgula ao invés da quebra de linha:

x = 5
pi = 3.1416
print('Numero inteiro:', x, end=', ')
print('Numero ponto-flutuante:', pi)

Leitura de dados do teclado

A leitura do teclado pode ser feita com a função input. Essa função possibilita ler uma linha digitada pelo usuário, e também apresentar um prompt (caso necessário).


# Lê algo do teclado sem apresentar antes um prompt
linha = input()

print('Você digitou:', linha)

Sem apresentar prompt


# Lê algo do teclado, mas antes apresenta um prompt
linha = input('Digite qualquer coisa e tecle ENTER: ')

print('Você digitou:', linha)

Apresentando um prompt antes da leitura


OBS: o valor lido é sempre uma string.

Exercício

  1. Modifique a aplicação do curso para que a URL seja lida do teclado.

Tipos de dados básicos

Python possui alguns tipos de dados básicos. A rigor, todos tipos de dados em Python são na verdade classes, e variáveis desses tipos são objetos. Isso abre um grande leque de operações que podem ser feitos com essas variáveis.

Tipos numéricos

Python possui três tipos numéricos:

  • Inteiros (int)
  • Ponto-flutuante (float)
  • Número complexo (complex)

Variáveis desses tipos são criadas principalmente ao se atribuirem constantes numéricas a elas:

# x é uma variável do tipo int
x = 1

# pi é uma variável do tipo float
pi = 3.1416

Tipos numéricos suportam operações aritméticas típicas:

x = 1
pi = 3.1416

# Soma de dois inteiros: resultado é inteiro
y = x + 2

# Soma de inteiro e float: resultado é float
z = x + pi

# Multiplicação de inteiros: resultado é inteiro
w = x * 2

# Multiplicação entre int e float: resultado é float
v = pi*x

# Divisão entre inteiros: resultado é float
y = y / 2

# Potenciação de int: resultado é int
x = x**2

# Potenciação de float: resultado é float
y = pi**3

Exemplo sobre operações aritméticas


Conversões entre os tipos de dados podem ser feitas usando diretamente as classes int e float:

  • Conversão para int:
    # pi é float
    pi = 3.1416
    
    # numero é uma string !
    numero = '12345'
    
    # converte os valores para int
    x = int(pi)
    y = int(numero)
    
    print('x:', x)
    print('y:', y)
    
  • Conversão para float:
    # x é int
    x = 23
    
    # numero é uma string !
    numero = '3.1416'
    
    # converte os valores para float
    y = float(x)
    pi = float(numero)
    
    print('y:', y)
    print('pi:', pi)
    

Exercícios

  1. Execute o exemplo sobre operações aritméticas. Que resultados ele apresenta ?
  2. Modifique o programa para que cada nova variável tenha seu valor mostrado na tela segundo este formato: nome_da_variável=valor_da_variável

Tipo booleano

Python possui um tipo de dados booleano chamado bool. Basicamente ele pode armazenar apenas dois valores: True ou False. Esse tipo de dados deve ser útil ao se usarem estruturas de decisão.

ok = True
nok = False

# tipo booleano suporta operações lógicas
print('ok and nok:', ok and nok)
print('ok or nok:', ok or nok)
print('not ok:', not ok)
print('ok xor nok:', ok ^ nok)

# uma comparação gera um valor bool
a = 1
b = 2
print('a == b ?', a == b)
print('a != b ?', a != b)
print('a < b ?', a < b)
print('a > b ?', a > b)

Tipo string

Outro tipo de dados básico existente em Python é str. Seu uso mais elementar é armazenar texto (ou sequências de caracteres). Diversas operações básicas existem para string:

  • Criação de variáveis string: a forma mais comum é pela atribuição de constantes string:
    # uma constante string pode ser delimitada por aspas simples
    nome='Mandroval "O Cara"'
    
    # ... ou por aspas duplas
    sobrenome="Muvurunga 'da Silva'"
    
    # ou ainda por aspas triplas !
    endereco = '''Rua Cafundó, 0
    Vila Fim de Mundo
    Buraco Perdido, ZZ
    CEP: 00000-000'''
    
    print("Nome:", nome, sobrenome)
    print('Endereco:', endereco)
    
  • Concatenação: strings podem ser concatenadas usando-se o operador +:
    nome = 'Mandroval'
    sobrenome = 'Muvurunga'
    
    nome_completo = nome + ' ' + sobrenome
    
    print('Nome completo:', nome_completo)
    
  • Repetição: uma string pode ser repetida quantas vezes se desejar usando-se o operador *:
    aviso = 'Nao dormirei mais em aula !'
    saida = aviso * 100
    print(saida)
    
  • Comparação: string podem ser comparadas com poeradores ==, !=, < e >:
    nome = 'Mandroval'
    sobrenome = 'Muvurunga'
    
    print ('nome == sobrenome  ?', nome == sobrenome)
    print ('nome != sobrenome  ?', nome != sobrenome)
    print ('nome antecede alfabeticamente sobrenome  ?', nome < sobrenome)
    print ('nome sucede alfabeticamente sobrenome  ?', nome > sobrenome)
    
  • Formatação: string podem ser criadas segundo um formato. Tal formato possibilita substituir partes da string por valores de diferentes tipos, de forma semelhante ao que se faz com o velho printf da linguagem C:
    x = 2
    
    # cria a string resultado segundo o formato indicado. Nele se substituem dois valores inteiros nas posições indicadas
    # por %d
    resultado = '%d elevado a 2 = %d' % (x, x**2)
    print(resultado)
    
    # cria a string resultado segundo o formato indicado. Nele se substitui um valores inteiro nas posição indicada
    # por %d, e um valor float onde aparece %f
    resultado = '%d / 3 = %f' % (x, x/3)
    print(resultado)
    
    nome = 'Mandroval'
    sobrenome = 'Maravilha'
    
    # apresenta nome e sobrenome no formato "sobrenome, nome". cada %s é substituído por uma string
    print('%s, %s' % (sobrenome, nome))
    
  • Comprimento: o comprimento de uma string é obtido com a função predefinida len:
    nome = 'Mandroval'
    
    print ('Comprimento de %s = %d' % (nome, len(nome))
    
  • Conversões para string: em tese valores de outros tipos de dados podem ser convertidos para string de duas formas:
    • Função repr: isso gera uma versão string de um dado imprimível na tela (uma representação do dado como string)
    • Função str: gera uma versão string de um dado. Usualmente o resultado é o mesmo do que obtido com repr, mas isso não é garantido. Veja a diferença quando se usa str e repr para uma string:
      >>> str(x)
      'banana'
      >>> repr(x)
      "'banana'"
      
  • Acesso a um caractere específico: um caractere em uma posição específica de uma string pode ser acessado desta forma:
    nome = 'Mandroval'
    
    # posicoes positiva contam a partir do inicio da string
    inicial = nome[0]
    segundo = nome[2]
    
    # ... mas posicoes negativas contam a partir do final !
    ultimo = nome[-1]
    penultimo = nome[-2]
    
    print('Inicial:', inicial)
    print('Ultimo:', ultimo)
    
  • Acesso a um trecho da string: um trecho da string pode ser obtido usando esta sintaxe:
    nome = 'Mandroval'
    
    # obtem os três primeiro caracteres
    inicio = nome[:3]
    
    # ... e os três últimos:
    ultimos = nome[-3:]
    
    # ... e agora os caracteres entre a primeiro e último
    meio = nome[1:-1]
    
    print('Inicio:', inicio)
    print('Ultimos:', ultimos)
    print('Meio:', meio)
    
  • Existência de um caractere ou substring: pode-se facilmente verificar se um caractere ou substring existem dentro de uma string:
    nome = 'Mandroval'
    
    # operador "in" testa se o valor da esquerda existe na string da direita
    # o resultado é bool
    ok = 'a' in nome
    print('a existe em %s ?' % nome, ok)
    
    # o valor procurado pode ser uma substring
    ok = 'and' in nome
    print('and existe em %s ?' % nome, ok)
    
    # pode-se também verificar se o valor não existe
    ok = 'and' not in nome
    print('and NÃO existe em %s ?' % nome, ok)
    


Exercícios

  1. Escreva um programa que leia uma string do teclado e a divida em suas outras strings de mesmo comprimento. Essas novas strings devem ser mostradas na tela.
  2. Escreva um programa que leia o nome, sobrenome e prefixo de tratamento (Sr, Sra, Srta), e crie variáveis string com cada um desses formatos (cada uma delas deve ser mostrada na tela):
    • prefixo: nome sobrenome
    • prefixo. sobrenome, nome
    • sobrenome (prefixo. nome)
  3. Modifique a aplicação do curso para que se decomponha a URL fornecida. Suas três partes devem ser apresentadas em separado: protocolo, servidor e caminho

Estruturas de decisão

Como qualquer linguagem de programação (conferir !), Python possibilita que condições sejam testadas e ações correspondentes sejam executadas. A única estrutura de decisão disponível na linguagem é do tipo se condição então ... senão .... Porém ela possui algumas particularidades.


Ver este primeiro exemplo para a estrutura de decisão:

x = 5

if x < 10:
  print('%d é menor que 10 !' % x)
else:
  print('%d é maior ou igual a 10 !' % x)

O resultado a ser apresentado, que é um tanto óbvio, deve ser:

5 é menor que 10 !


Este primeiro exemplo mostra também uma característica importante da linguagem Python: blocos de sentença são delimitados de acordo com a identação. Duas ou mais sentenças pertencem ao mesmo bloco se estiverem alinhadas na mesma coluna de texto. Com isso, Python difere de outras linguagens, como C, C++ e Java, que usam chaves ({ }) para delimitar blocos de sentença. Um efeito dessa forma de delimitar blocos é obrigar o programador a identar seu código-fonte ... algo sempre apreciado ! No exemplo acima, o bloco de sentenças caso a condição seja verdadeira está deslocado duas colunas à direita ... mesmo havendo uma única sentença. O mesmo se pode verificar no bloco a ser executado caso a condição seja falsa. Ao menos uma coluna de texto deve ser deslocada à direita para se iniciar um novo bloco.


O interpretador Python é rigoroso quanto à identação. Se linhas de código sucessivas estiverem em colunas diferentes sem razão para tal, um erro é apresentado e o programa sequer é executado. Por exemplo, se o programa de demonstração do if for escrito assim:

x = 5

if x < 10:
  print('%d é menor que 10 !' % x)
 else:
  print('%d é maior ou igual a 10 !' % x)

... ao se tentar executá-lo isto será mostrado (supor que ele foi gravado em um arquivo err.py):

  File "./err.py", line 7
    else:
        ^
IndentationError: unindent does not match any outer indentation level


Python não possui a estrutura de decisão switch .. case. Porém, sua implementação de if .. else possui uma extensão elif que torna switch .. case desnecessária. Ver este novo exemplo:

opcao = 3

if opcao == 1:
  print('escolhida a opcao 1')
elif opcao == 2:
  print('escolhida a opcao 2')
elif opcao == 3:
  print('escolhida a opcao 3')
elif opcao == 4:
  print('escolhida a opcao 4')
else:
  print('Opcao %d desconhecida !' % opcao)

Exercícios

1. Um radar de trânsito faz a medição de velocidade de veículos e, dependendo do valor, calcula a multa a ser aplicada. Em uma determinada via esse radar foi configurado da seguinte forma:

  • Se a velocidade for maior que 80 km/h, a multa é de R$ 360.
  • Se a velocidade for maior que 60 km/h, a multa é de R$ 180.
  • Se a velocidade for menor ou igual a 60 km/h, não há multa.

    Escreva um programa que calcule a multa de acordo com a velocidade de um veículo. A velocidade deve ser lida do teclado.


2. Na Universidade da California, no centro Médico de San Diego, quando um paciente com ataque cardíaco é recebido, dezenove variáveis são medidas ao longo das primeiras 24 horas. Essas incluem pressão sanguínea, idade, e 17 outras variáveis ordenadas e binárias (booleanas) que sumarizam os sintomas médicos considerados importantes indicadores da condição do paciente.

O objetivo de um estudo médico feito entre os anos 70 e 80 foi o desenvolvimento de um método para identificar pacientes de alto risco (que não sobreviverão ao menos 30 dias) com base nos dados obtidos nas primeiras 24 horas. O diagrama abaixo mostra uma regra de classificação que foi produzida nesse estudo. A letra F significa que não há um risco alto, e a letra G quer dizer paciente de alto risco. Essa regra classifica os pacientes como F ou G dependendo de respostas do tipo sim/não a no máximo três perguntas.

Crt-decisao.png


Implemente um programa que leia os valores necessários do teclado, e informe se o paciente o risco de vida do paciente.

(Exemplo obtido do livro Classification and Regression Trees, de Leo Breiman, Jerome Friedman, Richard Olshen e Charles Stone, editora Chapman & Hall, 1984).


3. Faça um jogo de par ou ímpar, em que o jogador aposta contra o computador. O jogador deve digitar um número entre 0 e 5 e optar entre par ou ímpar. O computador deve sortear um número também entre 0 e 5. Se a paridade da soma dos números do jogador e do computador for a mesma que o jogador optou, então ele ganha a partida, senão o computador vence.


4. Modifique a aplicação extratora de imagens para que extraia apenas imagens cujo nome (ou caminho):

  • Seja um caminho relativo ou absoluto dentro do próprio servidor de onde se obteve o documento HTML: nesse caso, o nome da imagem é da forma /um/caminho/para/imagem.jpg ou subdir/onde/existem/imagens/imagem.png. OBS: qualquer extensão de imagem é aceita ...

Estruturas de repetição

Python oferece duas estruturas de repetição que atendem todas as possíveis necessidades. A primeira é do tipo enquanto condição faça ..., sendo genérica e aplicável a qualquer caso. A segunda é do tipo para variável em sequência faça .., e tem uma aplicação um pouco mais específica (e muito útil !).

A estrutura de repetição while tem sintaxe bem direta, e é parecida com o que existe em outras linguagens:

x = 1

while x < 5:
  print('x = %d' % x)
  x = x + 1


Deve-se observar a identação do bloco de sentenças interno ao while. Aqui se aplicam as mesmas considerações explicadas no caso de estruturas de decisão: todo novo bloco de sentenças deve estar identado pelo menos uma coluna à direita.


A estrutura de repetição for é específica para uso com sequências. Por enquanto a única sequência já apresentada é o tipo de dados string, que representa sequências de caracteres. Outros tipos básicos de Python que são sequências serão mostrados em uma próxima seção. Por enquanto, demonstra-se o uso de for apenas com string:

nome = 'Mandroval'

# a variável letra assumirá cada valor da sequência contida em "nome"
for letra in nome:
  print(letra, end=',')

# para forçar uma quebra de linha
print('')


Pode parecer limitado, mas for ajudaria a resolver o exercício 4 da seção anterior ... E mais pra frente a praticidade de for deve ficar evidente.


Exercício

  1. Escreva um algoritmo que teste se um número informado pelo teclado é primo.
  2. Estenda o exercício 4 da seção anterior para acrescentar este caso de aceitação de imagem:
    • Seja uma URL que aponte o mesmo servidor de onde se obteve o documento HTML: o nome do servidor na URL da imagem deve ser idêntico ao contido na URl do documento HTML. Ex: se o documento HTML está na URL http://www.sj.ifsc.edu.br/cursos, a URL de imagem logo.png é aceita, mas ifsc.jpg não.

Tratamento de exceções


Todo programa está sujeito a erros ou situações anômalas ou inesperadas, sejam de programação ou de utilização. Por exemplo, um programa pode ter bugs que causam erros esporádicos, como, por exemplo, uma divisão por zero. Outra situação é a utilização incorreta pelo usuário, como, por exemplo, se fornece um nome de arquivo a ser aberto mas o arquivo não existe. Linguagens de programação modernas incluem um mecanismo chamado de tratamento de exceção, que serve tanto para representar e avisar da ocorrência de erros, quanto para tratá-los, se apropriado.

Em Python, exceções são disparadas por diferentes razões, tais como valores inválidos para conversão, erros de sintaxe no código-fonte, erros de entrada e saída, divisão por zero, e muitas outras. Além disso, programadores podem definir novos tipos de exceções mais informativos e consistentes com seus programas. Independente do tipo de exceção, todas exceções são especializações da classe Exception e, por isso, qualquer exceção pelo menos pode ser reconhecida como Exception. O exemplo a seguir mostra um programa que causa uma exceção:

x = 10
y = 0

# esta divisão é impossível ... isso gera uma exceção ZeroDivisionError
print(x/y)


Ao tentar executá-lo, obtém-se o seguinte:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero


O tratamento de exceções envolve usar a estrutura try. Essa estrutura tenta executar um trecho de código e, se uma exceção ocorrer, outro bloco de sentenças é executado para tratá-la. O exemplo acima poderia assim ser modificado:

x = 10
y = 0

try:
  # esta divisão é impossível ... isso gera uma exceção ZeroDivisionError
  print(x/y)
except ZeroDivisionError:
  print('Erro: divisão por zero !')


Se o tipo da exceção não for especificado, qualquer exceção pode ser capturada. Por outro lado, se o tipo for especificado mas uma exceção diferente for disparada, ela não será capturada.


x = 10
y = 0

# Isto funciona, pois qualquer exceção será capturada. Porém o tratamento da exceção fica limitado,
# pois no fim não se sabe o que causou o erro ...
try:
  print(x/y)
except:
  print('Algum erro ...')


x = 10
y = 0

# Opa ... isso não funciona como esperado, pois apenas exceções do tipo ValueError
# são capturadas e tratadas, mas uma exceção ZeroDivisionError será disparada
try:
  print(x/y)
except ValueError:
  print('Algum erro ...')


Muitas diferentes exceções podem ser disparadas em Python, devido a variadas situações de erro ou anormalidade.


Exercícios

  1. Investigue as exceções que podem ser disparadas na aplicação do curso. Veja na lista de exceções o significado dos erros.
    • experimente executá-lo sem fornecer uma URL
    • execute-o forencendo uma URL inválida
  2. Trate as exceções na aplicação do curso ...

Funções

Funções em Python, como em outras linguagens, são úteis para encapsular algoritmos. Uma função possui um nome e, opcionalmente, uma lista de argumentos. Os argumentos são usados para passar parâmetros para a função. A declaração de uma função está exemplificada a seguir:

def hello():
  print('Hello world !')

A declaração de uma função inicia com a palavra-chave def, seguida do nome da função. A lista de argumentos deve ser especificada dentros dos parênteses imediatamente após o nome da função. Se não houver argumentos, então os parênteses devem aparecer vazios (como no exemplo acima). Como toda construção sintática em Python que contém um bloco de de sentenças, a declaração da função deve terminar com :. Uma vez declarada, uma função pode ser usada, sendo chamada pelo nome seguido dos parênteses (e os argumentos, caso necessário):

print('Mostra uma mensagem muito especial:', end='')

hello()


Argumentos de uma função são especificados como uma lista de identificadores, sem qualquer menção a seus tipos de dados. Nisso há grande diferença entre Python e linguagens fortemente tipadas, tais como C, C++ e Java. A função mostrada a seguir calcula o módulo de um vetor bidimensional:

def modulo(x, y):
  # a raiz quadrada da soma dos quadrados
  r = (x**2 + y**2)**0.5
  return r

print('Modulo de (5,5):', modulo(5,5))

No exemplo acima, a função modulo recebe dois parâmetros numéricos que representam os componentes de um vetor, e com eles calcula o módulo desse vetor. Ao final, a função retorna o resultado usando o comando return. Esse exemplo evidencia duas coisas:

  1. Como tipos de argumentos não são especificados, nenhuma verificação automática é feita quanto à validade dos parâmetros passados. Caso isso seja necessário, a própria função deve conferi-los.
  2. Funções retornam algum resultado, que pode ser especificado usando o comando return. Caso uma função não retorne explicitamente um valor, seu resultado será por default definido como sendo None (um valor especial que significa NADA).

O valor de algum argumento pode ser predefinido. Com isso, fica opcional a passagem de parâmetro através desse argumento. No entanto, todos os argumentos posteriores a ele também devem ter valores predefinidos:

def raiz(x, indice=2):
  r = x**(1/indice)
  return r

valor = 16
print('Raiz quadrada de %d:' % valor, raiz(valor))
print('Raiz cúbica de %d:' % valor, raiz(valor, 3))
print('Raiz quarta de %d:' % valor, raiz(valor, 4))


Duas outras formas especiais de argumentos existem em funções Python:

  • Sequências de parâmetros não-especificados: uma função pode receber zero ou mais parâmetros através do argumento *identificador (sendo identificador um nome de argumento qualquer). Isso possibilita que funções possam receber uma quantidade de parâmetros variável. Dentro da função, identificador aparece como uma tupla contendo os valores passados como parâmetros. Um exemplo é a função print, que pode receber zero ou mais parâmetros.
  • Argumentos com palavra-chave: através do argumento **identificador (sendo identificador um nome de argumento qualquer), uma função pode receber argumentos cujos nomes são explicitamente declarados na própria chamada. Dentro da função, o argumento identificador é um dicionário em que as chaves são as palavras-chaves. Novamente a função [ print serve como exemplo, quando se usam os argumentos end ou sep.


O exemplo a seguir mostra duas funções que exploram todas as formas de especificação de lista de argumentos:

# argumento "numeros" aparece como uma tupla dentro da função
def media(*numeros):
  r = 0
  for x in numeros:
    r += x
  return r/len(numeros)

# argumento "complemento" aparece como um dicionário dentro da função
def formata_nome(nome, **complemento):
  try:
    nome = complemento['prefixo'] + nome
  except KeyError:
    pass

  try:
    nome = nome + complemento['sufixo']
  except KeyError:
    pass

  return nome

print('Media de 1 2 3 4 5:', media(1,2,3,4,5))

nome = 'Maneca'
print('Nome:', formata_nome(nome, prefixo='Sr.'))


Exercícios

  1. Implemente uma função que testa se um número é primo.
    • Use-a para testar se um número informado pelo teclado é primo.
    • Use-a para identificar todos os números primos menores que 1000.
  2. Modifique a aplicação do curso para que alguns de seus algoritmos sejam encapsulados em funções. Por exemplo, crie uma função que verifique a validade de uma imagem conforme pedido no exercício 4 sobre estruturas de decisão e no exercício 2 sobre estruturas de repetição.

Tipos de dados não tão básicos

Python implementa alguns tipos de dados (na verdade, classes) elementares e muito úteis, porém que não costumam ser encontrados nativamente em outras linguagens. São eles:

Listas

Listas são de grande utilidade e intensivamente usadas em Python. A implementação feita na linguagem é eficiente, proporcionando operações rápidas.

A criação de uma lista se faz com uma sintaxe limpa e direta, como se pode notar neste exemplo. Basicamente, os valores de uma lista devem estar separados por vírgulas e delimitados por colchetes:

# criação de uma lista
lista = ['abacaxi','banana','morango','laranja']

# criação de uma lista vazia
vazia = []

# pode-se mostrar uma lista
print('Lista:', lista)
print('Vazia:', vazia)


Listas podem ser concatenadas com o operador '+', e multiplicadas com o operador '*':

lista1 = ['banana','abacate']
lista2 = ['laranja','morango']

# cria uma nova lista, que é resultado da concatenação de lista1 e lista2
lista = lista1 + lista2

print(lista)
print(lista*3)


Para acessar um dado de uma lista, pode-se referenciá-lo por sua posição. As regras de acesso aos dados por posição são exatamente as mesmas vistas no caso de string (porque string, assim como lista, é um tipo de sequência):

lista = ['abacaxi','banana','morango','laranja']

primeiro = lista[0]
ultimo = lista[-1]

print('Primeiro=%s, ultimo=%s' % (primeimro, ultimo))


Com isso, podem-se acessar todos os dados de uma lista usando uma estrutura de repetição while. Note que a função len funciona também para listas, retornando seu comprimento:

lista = ['abacaxi','banana','morango','laranja']

pos = 0
while pos < len(lista):
  print(lista[pos])
  pos = pos + 1


Uma forma alternativa de acessar todos os dados da lista explora a estrutura de repetição for, e enfim mostra como for pode ser útil:

lista = ['abacaxi','banana','morango','laranja']

for fruta in lista:
  print(fruta)


Da mesma forma que string, podem-se extrair fatias de uma lista (ou sublistas):

lista = ['abacaxi','banana','morango','laranja']

meio = lista[1:3]
print(meio)

# isso gera uma lista idêntica a "meio"
outra = lista[1:-1]
print(outra)

# ... idem:
maisuma = lista[-3:-1]
print(maisuma)


Pode-se modificar uma lista, anexando novos valores, inserindo valores em posições específicas, ou removendo valores:

lista = []

# anexa dois valores
lista.append('laranja')
lista.append('abacaxi')

# insere outros dois no início da lista
lista.insert(0, 'morango')
lista.insert(0, 'banana')

for fruta in lista:
  print(fruta)

# remove um valor
lista.remove('banana')

print(lista)

# remove um valor por sua posição ... isso usa a função del
del lista[1]

print(lista)


Pode-se testar se um valor existe numa lista:

lista = ['abacaxi','banana','morango','laranja']

if 'banana' in lista:
  print('banana está na lista')

if 'abacate' not in lista:
  print('abacate não está na lista')


Uma lista pode ser ordenada, e também ter sua ordem invertida:

lista = ['abacaxi','banana','morango','laranja']

print('Antes de ordenar:', lista)

lista.sort()
print('Após ordenar:', lista)

lista.reverse()
print('Após invertê-la:', lista)


... e listas podem até armazenar valores de tipos diferentes (mas nesse caso não se pode ordená-la) !

lista = ['abacaxi',23, 3.1416, True]

print(lista)

Exercícios

  1. Escreva um programa que testa se uma lista está ordenada.
  2. Na aplicação extratora de imagens, o analisador retorna uma lista contendo todos os elementos de imagem encontrados quando se chama find_all. Isso pode ser visto nesta linha:
    # extrai uma lista de elementos HTML do tipo "img"
    imagens = parser.find_all('img')
    
    Modifique sua aplicação para que a filtragem das imagens aceitas seja feita diretamente nessa lista: uma nova lista deve ser gerada contendo os nomes das imagens aceitas.

Processando listas

Python oferece alguns mecanismos para processar listas, sendo que dois deles são muito comuns de serem usados:

  • Função map: executa uma função para cada elemento de uma lista, retornando uma nova lista contendo os resultados dessa função.
  • Função filter: aplica uma função para cada elemento de uma lista, retornando uma nova lista contendo apenas os elementos para os quais a função avaliou True.

Apesar de não serem explorados neste curso, vale apena aprendê-los. Fica a sugestão ...

Faixas (range)

Faixas representam sequências de números inteiros ordenados. Uma faixa pode ser criada com esta sintaxe:

# gera uma sequência de 10 números inteiros, iniciando em 0 e terminando em 9
faixa1 = range(10)

# gera uma sequência de 6 números inteiros, iniciando em 2 e terminando em 7
faixa2 = range(2,8)

print('Faixa 1:')
for x in faixa1:
  print(x)

print('\nFaixa 2:')
for x in faixa2:
  print(x)


O incremento entre os números pode ser informado na criação da faixa:

# gera uma sequência iniciando em 0 e terminando em 8, com incrementos de 2
faixa1 = range(0, 10, 2)

# gera uma sequência de 6 números inteiros, iniciando em 8 e terminando em 3
faixa2 = range(8, 2,-1)

print('Faixa 1:')
for x in faixa1:
  print(x)

print('\nFaixa 2:')
for x in faixa2:
  print(x)


As operações de acesso a dados por posição, e obtenção de um trecho da faixa (ex: do segundo ao quinto dado), podem ser realizadas com a mesma sintaxe dessas operações em listas e string.


A vantagem de faixas sobre listas está no consumo de memória, que é pequeno e não depende do comprimento da faixa.

Tuplas

Tuplas são sequências que não podem ser modificadas uma vez criadas. Portanto, não há operações para anexar, inserir ou remover dados de tuplas. No entanto, tuplas podem ser concatenadas de forma a gerar uma nova tupla.


A criação de uma tupla é parecida com lista, porém ao invés de se usarem colchetes, usam-se parênteses:

dados = ('banana','morango','laranja','abacaxi')

print('Há %d dados:' % len(dados), dados)


Tuplas podem ser concatenadas, gerando uma nova tupla:

dados = ('banana','morango','laranja','abacaxi')
outros = ('abacate','goiaba')

tupla = dados + outros

print('Há %d dados:' % len(tupla), tupla)


O acesso a dados em tuplas é idêntico a listas:

dados = ('banana','morango','laranja','abacaxi')

primeiro = dados[0]
ultimo = dados[-1]
print(primeiro, ultimo)


... e também se podem extrair fatias de uma tupla:

dados = ('banana','morango','laranja','abacaxi')

meio = dados[1:-1]
print('Há %d dados:' % len(meio), meio)


Os dados de uma tupla podem ser extraídos diretamente para variáveis:

dados = ('banana','morango','laranja')

# cada valor da tupla é copiado para uma variável
# a quantidade de variáveis deve ser igual ao comprimento da tupla !
a,b,c = dados
print(a)
print(b)
print(c)

... e isso tem relação com este uso de tuplas:

dados = ('banana','morango','laranja')

print('As frutas são: %s, %s e %s' % dados)

Dicionários

Dicionário é um tipo de dados (classe) Python de grande utilidade. Ele oferece uma tabela hash com sintaxe muito simplificada. Um dicionário armazena pares de dados, formados por uma chave e um valor. A chave se torna o localizador do valor armazenado, o que implementa uma forma de mapeamento entre os dados.

# cria um dicionário com três pares de dados. Cada par é formado por chave:valor
d = {'bilica': 'banana', 'maneca':'tainha', 'mariazinha': 'balaio'}

print('Senha da bilica:', d['bilica'])
print('Senha do maneca:', d['maneca'])
print('Tudo:', d)


Novos pares podem ser adicionados a um dicionário:

# cria um dicionário com um par de dados.
d = {'bilica': 'banana'}

# acrescenta outros pares
d['maneca'] = 'tainha'
d['mariazinha'] = 'balaio'

print('Senha da bilica:', d['bilica'])
print('Senha do maneca:', d['maneca'])


Para se verificar se uma chave existe em um dicionário, pode-se fazer assim:

d = {'bilica': 'banana', 'maneca':'tainha', 'mariazinha': 'balaio'}

if 'maneca' in d:
  print('maneca está no dicionário e tem valor', d['maneca'])


Pode-se obter a lista de chaves de um dicionário, e também uma lista de seus valores:

# cria um dicionário com três pares de dados. Cada par é formado por chave:valor
d = {'bilica': 'banana', 'maneca':'tainha', 'mariazinha': 'balaio'}

chaves = d.keys()
valores = d.values()

print('Chaves:', chaves)
print('Valores:', valores)


... e também pode-se iterar os pares contidos em um dicionário, usando o método items(). Esse método retorna uma lista de tuplas, e cada tupla contém uma chave e o valor a ela associado:

# cria um dicionário com três pares de dados. Cada par é formado por chave:valor
d = {'bilica': 'banana', 'maneca':'tainha', 'mariazinha': 'balaio'}

for chave,valor in d.items():
  print('%s --> %s' % (chave, valor))


Um par pode ser removido usando-se o operador del:

# cria um dicionário com três pares de dados. Cada par é formado por chave:valor
d = {'bilica': 'banana', 'maneca':'tainha', 'mariazinha': 'balaio'}

for chave,valor in d.items():
  print('%s --> %s' % (chave, valor))

del d['bilica']

print('Após remover a bilica:')
for chave,valor in d.items():
  print('%s --> %s' % (chave, valor))

Exercícios

  1. Escreva um programa que conte quantas vezes cada palavra existe em um texto lido do teclado.
  2. Modifique a aplicação do curso para garantir que cada imagem será transferida uma única vez. Quando uma imagem repetida aparecer, a aplicação deve ignorá-la, mas informar isso na tela.

Conjuntos

Conjuntos armazenam apenas valores únicos. Eles são úteis para ignorar valores repetidos, dentre outras aplicações.

Conjuntos são criados com uma sintaxe simplificada, semelhante a dicionários:

# cria um conjunto inicialmente com três valores
dados = {'banana','morango','sapoti'}

print(dados)


Pode-se testar se um dado pertence a um conjunto:

# cria um conjunto inicialmente com três valores
dados = {'banana','morango','sapoti'}

if 'sapoti' in dados:
  print('sapoti pertence ao conjunto', dados)


Conjuntos suportam operações típicas de união e interseção:

# cria um conjunto inicialmente com três valores
dados = {'banana','morango','sapoti'}
outros = {'laranja','caqui'}
alguns = {'banana','laranja'}

novo = dados.union(outros)
print('União de %s e %s:' % (dados, outros), novo)
print('Interseção de %s e %s:' % (dados, alguns), dados.intersection(alguns))
print('%s está contido em %s ?' % (alguns, novo), alguns < novo)
print('União de %s e %s:' % (alguns, alguns), alguns.union(alguns))
print('Interseção de %s e %s:' % (dados, outros), dados.intersection(outros))


Novos dados podem ser adicionados e removidos de conjuntos:

# cria um conjunto inicialmente com três valores
dados = {'banana','morango','sapoti'}

dados.add('laranja')

print(dados)

dados.remove('sapoti')

print(dados)


Por fim, conjuntos podem ser iterados:

# cria um conjunto inicialmente com três valores
dados = {'banana','morango','sapoti'}

for fruta in dados:
  print(fruta)


Exercícios

  1. Use conjuntos para garantir que não há imagens repetidas na relação de imagens obtida pela aplicação do curso.

Mais sobre string

Algumas funcionalidades de string só podem ser entendidas após entender como funcionam listas.

Uma string pode ser separada em substrings, de acordo com um caractere ou substring delimitadora. O resultado é uma lista contendo as substrings:

nome = 'Mandroval da Maravilha'

# cria uma lista contendo as substrings de "nome". A separação foi feita usando o espaço branco como delimitador
dados = nome.split()

for item in dados:
  print(item)


Se o caractere ou string delimitadora for informado, então a separação ocorre usando-o como critério:

linha = 'nome,telefone,rua,numero,cep,bairro,cidade,estado'

# cria uma lista de substrings. A separação usa a virgula como delimitador
dados = linha.split(',')

for item in dados:
  print(item)


O contrário também é possível, gerando uma string a partir de uma lista de substrings. Essa concatenação é feita pelo método join da string:

lista = ['Mandroval','da','Maravilha']

# concatena as strings contidas em lista. Quem concatena é a string ' ' (espaço), portanto
# a string resultante terá um espaço entre as substrings concatenadas.
nome = ' '.join(lista)

print(nome)


Exercícios

  1. Por vezes é desejável garantir que substrings dentro de uma string estejam separadas por um único espaço. Por exemplo, nomes de pessoas ou endereços podem ser normalizados dessa forma. Escreva uma função chamada normalize que realize essa tarefa para uma determinada string passada como parâmetro.
  2. Os algoritmos de verificação de aceitação de imagens da aplicação do curso poderiam ser simplificados se fossem usados os métodos split ou join de string ? Caso conclua que sim, reimplemente-os usando esses métodos.

Arquivos

Em Python arquivos podem ser lidos e escritos com operações típicas, semelhantes a outras linguagens. Porém em Python toda escrita ou leitura em arquivo envolve somente string ou bytes. Assim, não há operações especiais para ler ou gravar cada possível tipo de dados, como em C++ ou C. Cabe ao programador representar seus dados como string ou bytes para transferi-los para arquivos. Diferente do que talvez se imagine, isso simplifica as operações com arquivos.

Leitura de arquivos

O primeiro exemplo mostra a leitura completa de um arquivo, e a apresentação na tela do conteúdo lido:

# Abre o arquivo "/etc/hosts"
arq = open('/etc/hosts')

# Lê todo seu conteúdo
# O resultado da leitura é uma string
texto = arq.read()

# fecha o arquivo
arq.close()

print(texto)


O exemplo mostra que primeiro é necessário abrir o arquivo com a função open. O resultado dessa função é um objeto do tipo arquivo, e seus métodos possibilitam que se façam operações de arquivo (tais como leitura com o método read). No exemplo se faz a leitura de todo o conteúdo do arquivo, porém é possível ler quantidades específicas de caracteres, como mostrado a seguir:

# Abre o arquivo "/etc/hosts"
arq = open('/etc/hosts')

while True:
  # Lê 16 caracteres de cada vez ...
  texto = arq.read(16)

  # Se texto for uma string vazia, então
  # nada se conseguiu ler .. fim de arquivo !
  if not texto:
    break

  print(texto, end='')

# fecha o arquivo
arq.close()


Outra possibilidade é ler linha a linha:

# Abre o arquivo "/etc/hosts"
arq = open('/etc/hosts')

while True:
  # Lê uma linha por vez
  linha = arq.readline()

  # Se linha for uma string vazia, então
  # nada se conseguiu ler .. fim de arquivo !
  if not linha:
    break

  # A linha lida contém a quebra de linha ao final ... por isso
  # print não deve acrescentar mais uma quebra de linha
  print(linha, end='')

# fecha o arquivo
arq.close()


... ou mesmo todas as linhas de uma vez (isso não é muito legal se o arquivo for muito grande ...):

# Abre o arquivo "/etc/hosts"
arq = open('/etc/hosts')

# Método readlines retorna uma lista com todas as linhas
linhas = arq.readlines()

for linha in linhas:
  print(linha, end='')

# fecha o arquivo
arq.close()

Exercícios

  1. Escreva um programa que leia as linhas de um arquivo e mostre somente a primeira ocorrência de cada linha. As linhas devem ser apresentadas na ordem em que foram lidas.
  2. Escreva um programa que procure por uma palavra dentro de um arquivo. Esse programa deve mostrar os números das linhas em que a palavra aparece.

Escrita em arquivos

Assim como na leitura, primeiro deve-se abrir o arquivo com a função open. Porém um parâmetro deve ser informado na chamada desas função para informar que o arquivo deve ser aberto para escrita.

lista = ['banana','morango','laranja','abacaxi','sapoti']

# abre o arquivo em modo escrita (parâmetro 'w')
# se arquivo já existir, será primeiramente apagado
arq = open('frutas.txt','w')

for fruta in lista:
  # escreve cada fruta, sucedida por uma quebra de linha
  arq.write('%s\n' % fruta)

arq.close()


Existe a possibilidade de gravar todas as linhas de uma vez, as quais devem estar em uma lista. Cada linha deve incluir seu terminador, que usualmente é a quebra de linha:

lista = ['banana\n','morango\n','laranja\n','abacaxi\n','sapoti\n']

# abre o arquivo em modo escrita (parâmetro 'w')
# se arquivo já existir, será primeiramente apagado
arq = open('frutas.txt','w')

arq.writelines(lista)

arq.close()


Se o arquivo for aberto com o parâmetro "a", as gravações serão realizadas ao final do arquivo. Quer dizer, caso o arquivo exista, seu conteúdo será preservado e novos dados serão escritos ao final:

lista = ['banana\n','morango\n','laranja\n','abacaxi\n','sapoti\n']

# abre o arquivo em modo escrita (parâmetro 'w')
# se arquivo já existir, será primeiramente apagado
arq = open('frutas.txt','w')

arq.writelines(lista)

arq.close()

# abre agora o arquivo para anexar dados
arq = open('frutas.txt','a')

# grava tudo de novo ...
arq.writelines(lista)

arq.close()


Por fim, um arquivo pode ser aberto em modo leitura e escrita:

  • Se for passado o parâmetro 'r+', o arquivo será aberto para leitura e escrita a partir de seu início.
  • Se for passado o parâmetro 'a+', o arquivo será aberto para leitura e escrita a partir de seu final.

Exercícios

  1. Escreva um programa que conte quantas linhas, palavras e caracteres possui um determinado arquivo. O nome do arquivo deve ser passado como argumento de linha de comando.
  2. Estenda a aplicação do curso para que as imagens sejam gravadas dentro do subdiretório imagens. Esse diretório deve ser criado previamente.

Módulos

Python possui uma ampla biblioteca de programação contendo uma grande quantidade de módulos com diversas funcionalidades. Cada módulo contém funções, classes, constantes e possivelmente outros recursos úteis na escrita de programas. Isso favorece intensamente o reaproveitamento de código, pois se reutilizam soluções eficientes e bem documentadas feitas por terceiros, e possibilita que se escrevam programas complexos com menor esforço. Como diz o ditado, não se deve reinventar a roda. Para serem utilizados, módulos devem primeiramente ser importados em programas. O exemplo a seguir mostra um programa que importa dois módulos:

  • sys: módulo com parâmetros e funções específicos do sistema operacional
  • math: módulo com funções matemáticas elementares
#!/usr/bin/python3

import sys
import math

num = input('Digite um número > 0: ')

try:
  num = float(num)
except ValueError:
  print('"%s" não é um número !' % num)
  sys.exit(0)

print('Numero', num, ':')
print('Log2(%f)' % num, math.log(num), sep='=')
print('Log10(%f)' % num, math.log10(num), sep='=')
print('Exp(%f)' % num, math.exp(num), sep='=')
print('Sin(%f)' % num, math.sin(num), sep='=')
print('Cos(%f)' % num, math.cos(num), sep='=')
print('... e PI', math.pi, sep='=')

Nesse programa, os módulos sys e math são importados com o comando import. Desse ponto em diante, todos os elementos existentes nesses módulos estão diponíveis, podendo ser invocados por modulo.elemento. Por exemplo, a terminação do programa pode ser feita chamando-se sys.exit(0) (exit é uma função contida no módulo sys'), e o logaritmo de um número na base 2 pode ser calculado chamando-se math.log (função log contida no módulo math). Essa nomenclatura tem a virtude de esclarecer que módulo implementa cada função utilizada, porém pode ser desejável omitir o nome do módulo por questões de legibilidade. Se o programa for reescrito da seguinte maneira:


#!/usr/bin/python3

import sys
from math import *

num = input('Digite um número > 0: ')

try:
  num = float(num)
except ValueError:
  print('"%s" não é um número !' % num)
  sys.exit(0)

print('Numero', num, ':')
print('Log2(%f)' % num, log(num), sep='=')
print('Log10(%f)' % num, log10(num), sep='=')
print('Exp(%f)' % num, exp(num), sep='=')
print('Sin(%f)' % num, sin(num), sep='=')
print('Cos(%f)' % num, cos(num), sep='=')
print('... e PI', pi, sep='=')

... não é mais necessário usar o prefixo math. em frente aos nomes de elementos do módulo math. Na verdade, se esse prefixo for usado um erro de sintaxe será disparado ! Note que essa mudança se deve à forma com que o módulo math foi importado:

from math import *

Isso pode ser lido assim: importe tudo que existir dentro do módulo math. Sendo assim, todos os elementos no escopo do módulo math aparecem no escopo global. Uma outra forma de se obter o mesmo efeito, mas sem importar tudo que existe em math é:

from math import log,log10,exp,sin,cos,pi

... fazendo com que se importem apenas os elementos desejados.

Exercícios

  1. Investigue alguns módulos Python:
  2. Explore os módulos pathlib e os para a gravação das imagens pela aplicação do curso. Cada imagem deve ser armazenada em um subdiretório correspondente ao caminho onde ela se encontra no servidor.

Classes e objetos

Python é uma linguagem orientada a objetos. Em Python tudo é objeto: variáveis, constantes, arquivos, exceções, funções e métodos, e até mesmo classes. Mesmo em programa mais simples, escritos sequencialmente ou com uma abordagem de programação estruturada, objetos estão em ação. Isso pode ser visualizado por meio da função type, que mostra o tipo (na verdade, a classe) de um valor ou variável:

# mostra o tipo da constante numérica 1
print(type(1))

# mostra o tipo da constante numérica 3.1416
print(type(3.1416))

# mostra o tipo da constante string 'bilica'
print(type('bilica'))

arq = open('/etc/hosts')

# mostra o tipo da variável "arq"
print(type(arq))

tipo = type(arq)

# mostra o tipo da variável "tipo" ... 
print(tipo, 'tem tipo', type(tipo))

Ao se executar esse programa, obtém-se este resultado:

<class 'int'>
<class 'float'>
<class 'str'>
<class '_io.TextIOWrapper'>
<class '_io.TextIOWrapper'> tem tipo <class 'type'>


Se tudo são objetos, como se podem definir novas classes ? Uma classe é definida em Python com a palavra-chave class:


# define uma classe chamada Vetor
class Vetor:

  # este é o construtor da classe ... 
  def __init__(self, x, y):
    self.x = x
    self.y = y

  # este é um método especial, que deve retornar uma representação string 
  # de um objeto. Ele é executado quando se usa a função repr (print faz isso
  # implicitamente)
  def __repr__(self):
    return '(%d,%d)' % (self.x, self.y)

  # calcula o módulo do vetor
  def modulo(self):
    return (self.x**2 + self.y**2)**0.5

# aqui se testa a criação de um objeto Vetor
v = Vetor(2,2)
print(v)
print('Modulo:', v.modulo())

Na definição acima, a classe é declarada na linha 2. Entre as linhas 3 e 17 está o corpo da classe, onde se definem seus métodos e atributos. Todos os métodos da classe recebem pelo menos um argumento chamado self. Esse argumento representa o objeto da classe no escopo do qual o método foi invocado. Por exemplo, o método modulo calcula o módulo do vetor com base nos valores dos atributos x e y do objeto em que se invocou esse método - na linha 22, esse objeto é v. Fazendo uma analogia, o argumento self corresponde ao objeto this existente em C++ e Java. Os atributos de um objeto podem ser criados por qualquer método, mas o mais comum é que sejam criados no construtor da classe.

O construtor de uma classe é representado pelo método __init__. Esse método é executado sempre que se cria um objeto da classe, e ele recebe os parâmetros usados na criação do objeto. Por exemplo, na linha 20 cria-se o objeto v da classe Vetor, passando como parâmetros para seu construtor os valores 2,2. O método __init__ é então executado, recebendo como parâmetros o objeto v através do argumento self, e os valores 2 e 2 através dos argumentos x e y. O construtor então cria os atributos x e y no objeto self, e a eles atribui os valores dos argumentos x e y.

Ao contrário de outras linguagens de programação, Python não tem mecanismos para controle de acesso a atributos e métodos de um objeto ou classe. Não existe o equivalente a public, private e protected existentes em C++ ou Java. Em Python, todos os elementos existentes dentro de uma classe são públicos. Se um programador quiser criar um objeto de uma classe, e então dentro dele criar um atributo, isso será realizado. O código a seguir então funcionaria:

v = Vetor(2,2)
v.z = 10

print('Atributos de v:' v.x, v.y, v.z)
print('Modulo de v:',v.modulo())

O resultado da execução desse programa é:

Atributos de v: 2 2 10
Modulo de v: 2.8284271247461903

Apesar de Python não coibir esse tipo de acesso direto a atributos de objetos, tal prática não é recomendável. Portanto, recomenda-se disciplina aos programadores para evitarem criarem uma bagunça em seus programas ao usarem as construções da linguagem de forma irrefletida.


Existem alguns métodos com finalidades especiais. Dois deles foram mostrados na classe Vetor:

  • __init__: construtor da classe
  • __repr__: usado para obter uma representação de um objeto como string


Outros podem ser definidos, caso desejável. Python não impõe a definição de nenhum desses métodos ... nem mesmo do construtor ! Cabe ao programador defini-los se os achar necessários. Alguns desses métodos são:


Por fim, classes podem ser especializadas via herança. O exemplo a seguir mostra a definição da classe VetorNovo como uma especialização da classe Vetor. Tudo que existe na classe Vetor é herdado pela classe VetorNovo, incluindo o construtor:

import math

class VetorNovo(Vetor):

  def angulo(self):
    return math.atan(self.x/self.y)

  
v = VetorNovo(2,2)
print('Modulo:', v.modulo())
print('Angulo:', v.angulo())

De volta à simples aplicação

Ao longo da introdução à linguagem Python vários exercícios foram propostos com respeito à aplicação extratora de imagens. Neste momento pode-se repensar essa aplicação, definido claramente seus requisitos. Ao longo dos exercícios propôs-se que se deve:

  • Transferir uma única vez cada imagem referenciada em um documento HTML
  • Transferir somente imagens que estão no mesmo servidor onde está o documento HTML
  • Transferir as imagens para arquivos locais, cujos caminhos são baseados nos caminhos das imagens no servidor


Algumas extensões podem ser imaginadas, tornando a aplicação mais poderosa. São elas:

  • Repetir a transferência de imagens para outros documentos HTML referenciados dentro do documento HTML original
  • Limitar a quantidade de documentos HTML que podem ser seguidos sucessivamente a partir do documento original (quer dizer, limitar a profundidade da busca)
  • Acessar uma única vez cada documento HTML
  • Possibilitar que documentos de outros servidores sejam acessados. As imagens deles obtidas devem ser gravadas em arquivos cujos caminhos informem de que servidor foram obtidas.


Por fim, essa aplicação pode ser melhor escrita e estruturada. Experimente modelá-la com classes e objetos !

OBS: não se iniba em explorar a biblioteca padrão Python.

PARTE 2

Introdução a Python - Parte 2