MAC0216 - Técnicas de Programação I

Aula 24 - Introdução à Python (Parte 3)

Tuplas (tipo tuple)

Uma tupla (tuple) em Python é uma sequência imutável de valores de qualquer tipo. Para criar uma tupla, lista-se uma sequência de valores separados por vírgulas e, opcionalmente, entre parênteses.

Tuplas são úteis para representar registros (mas sem atribuir nomes aos campos). Um registro é uma coleção de valores relacionados. Num registro representado como uma tupla, é a posição do valor dentro da tupla que determina o seu significado.

In [1]:
aluno1 = (18632827, "John Lennon", 19, "Bacharelado em Ciência da Computação")   # primeira tupla
aluno2 = 19817271, "Ringo Star", 21, "Licenciatura em Matemática"   # segunda tupla (sem parênteses!)

print("Primeira tupla: ", aluno1, "Tipo: ", type(aluno1))
print("Segunda tupla: ", aluno2, "Tipo: ", type(aluno2))

print("Tamanho da primeira tupla: ", len(aluno1))

print("Valor de uma posição da primeira tupla: ", aluno1[1])

print("Uma fatia da segunda tupla: ", aluno2[1:3])

print("Todos os elementos da segunda tupla: ")
for elemento in aluno2:
    print(elemento)
Primeira tupla:  (18632827, 'John Lennon', 19, 'Bacharelado em Ciência da Computação') Tipo:  <class 'tuple'>
Segunda tupla:  (19817271, 'Ringo Star', 21, 'Licenciatura em Matemática') Tipo:  <class 'tuple'>
Tamanho da primeira tupla:  4
Valor de uma posição da primeira tupla:  John Lennon
Uma fatia da segunda tupla:  ('Ringo Star', 21)
Todos os elementos da segunda tupla: 
19817271
Ringo Star
21
Licenciatura em Matemática

Como em qualquer tipo de sequência em Python, numa tupla t podemos aplicar as operações de indexação(t[i]) , fatiamento (t[i:j:k]), obtenção do comprimento (len(t)), verificação de pertinência (x in t ou x not in t), percorrimento com laço (for x in t), concatenação (t + s), replicação (t * n) obtenção do valor mínimo (min(t)) e máximo (max(t)), entre outras.

Mas como tuplas são sequências imutáveis (assim como as strings), não podemos aplicar nelas operações de modificação da sequência, como a atribuição de um valor para uma posição ou a adição de um novo valor à sequência. Veja no exemplo a seguir o erro gerado na tentativa de atribuição de um valor à uma posição de uma tupla:

In [2]:
aluno = (18632827, "John Lennon", 19, "Bacharelado em Ciência da Computação")  # (NUSP, Nome, Idade, Curso)
aluno[1] = "Ringo Star"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-ae9421c5a45b> in <module>
      1 aluno = (18632827, "John Lennon", 19, "Bacharelado em Ciência da Computação")  # (NUSP, Nome, Idade, Curso)
----> 2 aluno[1] = "Ringo Star"

TypeError: 'tuple' object does not support item assignment

Atribuição de Tuplas

Python tem um mecanismo de atribuição de tuplas que nos permite atribuir os valores de uma tupla (à direita do operador de atribuição =) para uma tupla de variáveis (à esquerda do =), como ilustrado no exemplo abaixo:

In [3]:
aluno = (18632827, "John Lennon", 19, "Bacharelado em Ciência da Computação") 
nusp, nome, idade, curso = aluno   # atribuição de tupla
print("NUSP: ", nusp)
print("Nome: ", nome)
print("Idade:", idade)
print("Curso:", curso)
NUSP:  18632827
Nome:  John Lennon
Idade: 19
Curso: Bacharelado em Ciência da Computação

Observe que, na atribuição do exemplo acima, acontece um "desempacotamento" dos valores em aluno, para a atribuição para as variáveis do lado esquerdo do operador =.

Também é possível fazer esse "desempacotamento" de tuplas em laços for ... in ...:

In [4]:
alunos = [("Paul", 19), ("Ringo", 21), ("John", 18)]    # lista de tuplas de alunos
for nome_aluno, idade_aluno in alunos:
    print(nome_aluno, idade_aluno)
    
Paul 19
Ringo 21
John 18

Usando a atribuição de tuplas, é possível escrever a troca dos valores de duas variáveis de uma forma bem elegante:

In [5]:
x = 10
y = 20
print("Antes da troca: x = %d, y = %d" %(x,y))
x,y = y,x    # troca os valores de x e y
print("Depois da troca: x = %d, y = %d" %(x,y))
Antes da troca: x = 10, y = 20
Depois da troca: x = 20, y = 10

Em outras linguagens de programação, seria preciso usar uma variável auxiliar para efetuar a troca:

In [6]:
x = 10
y = 20
print("Antes da troca: x = %d, y = %d" %(x,y))
aux = x
x = y 
y = aux
print("Depois da troca: x = %d, y = %d" %(x,y))
Antes da troca: x = 10, y = 20
Depois da troca: x = 20, y = 10

Nesses dois últimos exemplos, é interessante observar também que o operador de formatação de strings % (que está sendo usado para criar a string passada para a função print()) "entende" tuplas e trata cada elemento nelas como um campo separado.

Tuplas como Valor de Retorno de Funções

Tuplas também são usadas quando se quer devolver mais de um valor no return de uma função. Veja o exemplo abaixo:

In [7]:
import math

def infosCirculo(r):
    """ (numero) -> (float,float)
    Recebe um número r e retorna a circunferência
    e a área do círculo de raio r.
    """
    circunferencia = 2 * math.pi * r
    area = math.pi * r * r
    return (circunferencia, area)


c, a = infosCirculo(10)
print(c, a)
    
62.83185307179586 314.1592653589793

Tuplas Nomeadas (tipo namedtuple)

Uma tupla nomeada funciona como as tuplas que vimos nos exemplos acima, mas tem uma facilidade adicional: a possibilidade de se acessar os seus campos por meio de nomes atribuídos a eles.

Para criar tuplas nomeadas, é preciso importar a classe namedtuple do módulo collections. O construtor de namedtuple recebe como parâmetro o nome da classe de tuplas nomeadas a ser criada e uma string com os nomes dos campos dessa classe, separados por espaços. O construtor devolve a nova classe criada (uma subclasse de namedtuple) com os campos especificados. A nova classe então pode ser usada para criar as tuplas nomeadas.

Veja o exemplo abaixo que ilustra a criação e uso de uma classe de tuplas nomeadas chamada Aluno:

In [8]:
from collections import namedtuple

Aluno = namedtuple("Aluno", "nusp nome idade curso") 

aluno1 = Aluno(18632827, "John Lennon", 19, "Bacharelado em Ciência da Computação")
print("Primeiro aluno: ", aluno1)
print("Tipo: ", type(aluno1))
print()

aluno2 = Aluno(19817271, "Ringo Star", 21, "Licenciatura em Matemática")
print("Segundo aluno: ", aluno2)
print("Tipo: ", type(aluno2))
print()

# Exemplos de acesso a campos de uma tupla por meio de seus nomes
print("Nome do primeiro aluno: ", aluno1.nome)
print("Idade do primeiro aluno: ", aluno1.idade)

# Exemplos de acesso a campos de uma tupla por meio de suas posições
print("Nome do segundo aluno: ", aluno2[1])
print("Idade do segundo aluno: ", aluno2[2])
Primeiro aluno:  Aluno(nusp=18632827, nome='John Lennon', idade=19, curso='Bacharelado em Ciência da Computação')
Tipo:  <class '__main__.Aluno'>

Segundo aluno:  Aluno(nusp=19817271, nome='Ringo Star', idade=21, curso='Licenciatura em Matemática')
Tipo:  <class '__main__.Aluno'>

Nome do primeiro aluno:  John Lennon
Idade do primeiro aluno:  19
Nome do segundo aluno:  Ringo Star
Idade do segundo aluno:  21

Para quem não conhece orientação a objetos ainda, a descrição da criação de tuplas nomeadas e o exemplo acima podem não ter ficado muito claros. Mas as próximas aulas (que serão sobre orientação a objetos em Python) ajudarão a esclarecê-los!

Dicionários (tipo dict)

Já vimos alguns tipos de dados (str, list e tuple) para lidar com coleções sequenciais, nas quais os elementos na coleção estão ordenados da esquerda para a direita e os seus valores são acessados por meio de índices que são números inteiros.

Agora veremos um outro tipo de dados do Python - o dict (de dictionary, ou dicionário em português) - que também é usado para representar coleções. Entretanto, em um dict a organização dos elementos não é sequencial.

Um dicionário é um tipo abstrato de dados que associa uma chave a um valor. A chave precisa ser única, ou seja, não pode haver chaves repetidas dentro do dicionário. A chave também deve ser imutável, ou seja, uma vez criada, ela permanece a mesma. Portanto, pode-se usar como chave strings, números e tuplas, mas não listas. Já o valor pode ser de qualquer tipo do Python.

Para criar um dicionário vazio chamado dic, você pode fazer:

In [9]:
dic = {}

Observe que os elementos de um dicionário são delimitados por chaves ({}), enquanto que os itens de uma lista são delimitados por colchetes ([]).

Para criar um dicionário já com elementos dentro, basta colocar os elementos entre as chaves separados por vírgulas, e usando : (dois pontos) para separar a chave do valor, como mostrado no exemplo abaixo:

In [10]:
disciplinas = {'MAC2166':'Introdução à Computação', 'MAT2453':'Cálculo Diferencial e Integral I'}
print("O conteúdo do meu dicionário de disciplinas é:\n", disciplinas)
O conteúdo do meu dicionário de disciplinas é:
 {'MAC2166': 'Introdução à Computação', 'MAT2453': 'Cálculo Diferencial e Integral I'}

Para ler, criar ou modificar um elemento do dicionário, temos que usar a chave do elemento entre colchetes, como nos exemplos abaixo:

In [11]:
estoque = {'banana':50, 'maçã':4, 'abacaxi':10}    # cria um dicionário com 3 elementos
print("Dicionário inicial:", estoque)
estoque['melancia'] = 7    # adiciona no dicionário um novo elemento: o valor 7 associado à chave 'melancia'  
print("Dicionário depois da adição de novo elemento:", dic)
estoque['banana'] += 15    # modifica o valor assocido à chave 'banana'
print("Dicionário depois da modificação do valor de um elemento:", estoque)
print("Valor associado à chave 'abacaxi' no dicionário:", estoque['abacaxi'])   # lê o valor associado à 'abacaxi'
Dicionário inicial: {'banana': 50, 'maçã': 4, 'abacaxi': 10}
Dicionário depois da adição de novo elemento: {}
Dicionário depois da modificação do valor de um elemento: {'banana': 65, 'maçã': 4, 'abacaxi': 10, 'melancia': 7}
Valor associado à chave 'abacaxi' no dicionário: 10

Quando tentamos obter o valor associado à uma chave que não existe no dicionário, uma exceção do tipo KeyError é gerada (veja o exemplo abaixo):

In [12]:
estoque = {'banana':50, 'maçã':4, 'abacaxi':10} 
print(estoque['pera'])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-12-ec3e5c988a1d> in <module>
      1 estoque = {'banana':50, 'maçã':4, 'abacaxi':10}
----> 2 print(estoque['pera'])

KeyError: 'pera'

Podemos verificar se uma dada chave existe em um dicionário usando o operador in, ou ainda verificar se uma chave não existe no dicionário usando not in. Veja o exemplo abaixo:

In [13]:
estoque = {'banana':50, 'maçã':4, 'abacaxi':10} 
if 'pera' in estoque:   # verifica se a chave 'pera' existe no dicionário estoque
    print("A quantidade de peras no estoque é:", estoque['pera'])
else:
    print("Não vendemos peras!")
    if 'abacate' not in estoque:
        print("E também não vendemos abacates.")
Não vendemos peras!
E também não vendemos abacates.

Percorrendo os elementos de um dicionário

Há diferentes formas de se percorrer todos os elementos de um dicionário usando um laço for. Podemos usar o for para iterar sobre as chaves contidas no dicionário e, a partir de cada chave, acessar o valor associado a ela no dicionário. Veja abaixo um exemplo disso:

In [14]:
estoque = {'banana': 50, 'laranja': 12, 'melancia': 7, 'kiwi': 23}

for chave in estoque:
    valor = estoque[chave]
    print("Temos %d unidades de %s no estoque."%(valor, chave))
Temos 50 unidades de banana no estoque.
Temos 12 unidades de laranja no estoque.
Temos 7 unidades de melancia no estoque.
Temos 23 unidades de kiwi no estoque.

Uma forma menos compacta para se iterar sobre as chaves de um dicionário (mas talvez mais legível para alguns) é usando o método keys() de dict, como mostrado abaixo:

In [15]:
for chave in estoque.keys():
    valor = estoque[chave]
    print("Temos %d unidades de %s no estoque."%(valor, chave))
Temos 50 unidades de banana no estoque.
Temos 12 unidades de laranja no estoque.
Temos 7 unidades de melancia no estoque.
Temos 23 unidades de kiwi no estoque.

É possível também iterar sobre os valores de um dicionário utilizando o método values() de dict. Assim, para saber quantas frutas há no estoque podemos escrever:

In [16]:
total = 0
for valor in estoque.values():
    total += valor

print("Temos %d frutas no estoque."%(total))
Temos 92 frutas no estoque.

Por fim, é possível iterar sobre os pares (chave,valor) de um dicionário usando o método items() de dict. Veja o exemplo abaixo:

In [17]:
for (fruta,quantidade) in estoque.items():
    print("Temos %d unidades de %s no estoque."%(quantidade, fruta))
Temos 50 unidades de banana no estoque.
Temos 12 unidades de laranja no estoque.
Temos 7 unidades de melancia no estoque.
Temos 23 unidades de kiwi no estoque.

O conteúdo apresentado nesta aula é baseado no material disponível aqui e também aqui.

Problema - Frequência relativa das letras em uma frase

Escreva um programa que leia uma frase digitada pelo usuário e calcule a frequência relativa de cada letra na frase, usando dicionários. Ignore caracteres brancos e pontuação.

Solução

In [18]:
def main():
    frase = input("Digite uma frase:\n")
    
    freq_letras = {}   # cria um dicionário vazio

    for caractere in frase:   # para cada caractere na frase, faça

        if caractere not in ' .,;:?!\t\n':    # se o caractere atual não é um caractere que deve ser ignorado
            if caractere in freq_letras:      # verifica se o caractere já está no dicionário
                freq_letras[caractere] += 1   # Sim, então incrementa a sua frequência
            else:
                freq_letras[caractere] = 1    # Não, então inclui o caractere no dicionário, 
                                              # indicando 1 como frequência

    for (letra,freq) in freq_letras.items():
        print("A frequência da letra '%c' na frase é %d" %(letra, freq))
        

main()
Digite uma frase:
Batatinha quando nasce, espalha a rama pelo chão...
A frequência da letra 'B' na frase é 1
A frequência da letra 'a' na frase é 10
A frequência da letra 't' na frase é 2
A frequência da letra 'i' na frase é 1
A frequência da letra 'n' na frase é 3
A frequência da letra 'h' na frase é 3
A frequência da letra 'q' na frase é 1
A frequência da letra 'u' na frase é 1
A frequência da letra 'd' na frase é 1
A frequência da letra 'o' na frase é 3
A frequência da letra 's' na frase é 2
A frequência da letra 'c' na frase é 2
A frequência da letra 'e' na frase é 3
A frequência da letra 'p' na frase é 2
A frequência da letra 'l' na frase é 2
A frequência da letra 'r' na frase é 1
A frequência da letra 'm' na frase é 1
A frequência da letra 'ã' na frase é 1

Matrizes Esparsas como Dicionários

Matrizes esparsas tipicamente possuem zero em quase todas as posições, e valores diferentes de zero em apenas algumas posições.

Uma matriz esparsa pode ser representada como um dicionário, utilizando as coordenadas como chave.

Por exemplo, a matriz esparsa abaixo

00005
0130-10
00000
004200

poderia ser representada pelo seguinte dicionário em Python:

In [19]:
mat_esparsa = {(0,4):5, (1,1):13, (1,3):-1, (3,3):42}

for (coordenada,valor) in mat_esparsa.items():
    print("A posição", coordenada, "da matriz esparsa contém o valor ", valor)
A posição (0, 4) da matriz esparsa contém o valor  5
A posição (1, 1) da matriz esparsa contém o valor  13
A posição (1, 3) da matriz esparsa contém o valor  -1
A posição (3, 3) da matriz esparsa contém o valor  42

Problema - Jogo Batalha Naval

Como exemplo, vamos implementar um jogo de batalha naval onde o mapa será representado como uma matriz esparsa mantida em um dicionário, com:

  • um submarino valendo 100 pontos,
  • um cruzador valendo 30 pontos (cada pedaço),
  • um porta-aviões valendo 20 pontos (cada pedaço).

O “mar” onde estão as embarcações tem coordenadas de 0 a 99, tanto no eixo x quanto no eixo y.

A partir do esqueleto abaixo, escreva um programa que carrega um mapa em um dicionário usando a função dada carrega_mapa e leia coordenadas do usuário até que ele desista ou até que não reste mais pontos para serem ganhos (ou seja, até que ele afunde todas as embarcações).

Para facilitar a detecção do fim do jogo, implemente uma função total_pontos que retorna o total de pontos ainda disponível no dicionário.

Antes de terminar o programa, imprima quantos pontos o usuário ganhou.

In [20]:
def main():
    # edite o código abaixo para resolver este exercício
    mapa = carrega_mapa()
    print(mapa)

    
def total_pontos( dic ):
    ''' (dict) -> int '''
    # escreva a função
    

def carrega_mapa():
    ''' Retorna um dicionário contendo as coordenadas das
    embarcações. O mapa poderia ser lido, por exemplo, de um
    arquivo, ou as posições das embarcações poderiam ser
    sorteadas.
        Na implementação atual, o mapa está fixo.
    '''
    return {(2,5):100, # submarino
            (20,60):30, (21,60):30, # cruzador
            (42,71):20, (43,71):20, (44,71):20, (45,71):20} # porta-aviões
            

main()
{(2, 5): 100, (20, 60): 30, (21, 60): 30, (42, 71): 20, (43, 71): 20, (44, 71): 20, (45, 71): 20}

Solução

Na estratégia de implementação mostrada abaixo, modifica-se o valor associado a uma posição definida no dicionário para zero toda vez que o usuário acertar uma embarcação.

In [21]:
def main():

    mapa = carrega_mapa()
    fim = False
    pontos = 0
    total = total_pontos(mapa)
    print("Batalha Naval")
    print("Você pode fazer até %d pontos\n"%total)

    print("Digite as coordenadas separadas por vírgula")
    while not fim:
        x, y = input("Digite x, y (ou -1,-1 para terminar): ").split(',')
        x, y = int(x), int(y)
        print("Você jogou em: %d,%d" %(x,y))
        
        if (x,y) == (-1,-1):
            # Usuário pediu para encerrar o jogo
            fim = True
        elif (x,y) in mapa and mapa[x,y] > 0:   
            # (x,y) é a coordenada de um pedaço de embarcação ainda não atingido
            valor = mapa[x,y]
            print("Voce acertou uma embarcação e fez %d pontos" %valor)
            pontos += valor
            mapa[(x,y)] = 0    # troca o valor da posição para zero para indicar que foi atingida
            
            if total_pontos(mapa) == 0:
                # Usuário já ganhou todos os pontos possíveis
                fim = True
        else:
            print("Água!!")

    print("Você fez %d pontos" %pontos)

#----------------------------------------------------
def total_pontos(dic):
    ''' (dict) -> int '''
    soma = 0
    for coordenada in dic:
        soma += dic[coordenada]
    return soma

#----------------------------------------------------
def carrega_mapa():
    ''' Retorna um dicionário contendo as coordenadas das
    embarcações. O mapa poderia ser lido, por exemplo, de um
    arquivo, ou as posições das embarcações poderiam ser
    sorteadas.
        Na implementação atual, o mapa está fixo.
    '''
    return {(2,5):100, # submarino
            (20,60):30, (21,60):30, # cruzador
            (42,71):20, (43,71):20, (44,71):20, (45,71):20} # porta-aviões
            
#----------------------------------------------------
main()
Batalha Naval
Você pode fazer até 240 pontos

Digite as coordenadas separadas por vírgula
Digite x, y (ou -1,-1 para terminar): 21, 60
Você jogou em: 21,60
Voce acertou uma embarcação e fez 30 pontos
Digite x, y (ou -1,-1 para terminar): 42, 73
Você jogou em: 42,73
Água!!
Digite x, y (ou -1,-1 para terminar): -1, -1
Você jogou em: -1,-1
Você fez 30 pontos

Funções Anônimas (Lambdas)

Lambdas em Python são pequenas funções anônimas, definidas com uma sintaxe mais concisa (porém mais restritiva) que a de funções regulares. Uma função anônima é uma função sem nome.

O corpo de uma função lambda precisa ser uma única expressão. Portanto, funções lambda não podem fazer atribuições ou usar comandos como while, return, etc. Formato geral de uma lambda:

lambda parâmetros : expressão

Exemplos:

In [22]:
# Cria uma lambda que recebe como parâmetros dois números 
# e devolve a soma deles
# Armazena na variável funcSoma a referência para a lambda 
funcSoma = lambda x,y: x + y    

# Chama a lambda para calcular a soma de 5 e 10 e
# exibe o resultado
print(funcSoma(5,10))

# Na expressão da atribuição abaixo, uma função lambda é 
# definida e imediamente chamada
valor = (lambda x: x + 1)(20)  
print(valor)
15
21

Quando usadas da forma mostrada nos dois exemplos acima, as lambdas não parecem muito úteis. Aliás, esse tipo de uso não é mesmo recomendado.

Lambdas são mais úteis quando passadas como parâmetros para funções. Veja abaixo um exemplo de uso de uma função lambda como parâmetro numa chamada à função sorted() (que ordena sequências):

In [23]:
alunos = [("Paul", 21), ("Ringo", 18), ("John", 19)]    # lista de tuplas de alunos
print("Alunos:", alunos)

alunos_ordenados = sorted(alunos)
print("Alunos ordenados:", alunos_ordenados)

alunos_ordenados_por_idade = sorted(alunos, key=lambda aluno: aluno[1])
print("Alunos ordenados por idade:", alunos_ordenados_por_idade)
Alunos: [('Paul', 21), ('Ringo', 18), ('John', 19)]
Alunos ordenados: [('John', 19), ('Paul', 21), ('Ringo', 18)]
Alunos ordenados por idade: [('Ringo', 18), ('John', 19), ('Paul', 21)]

No parâmetro opcional key da função sorted(), podemos passar uma função para personalizar a ordem de ordenação, como foi feito no exemplo acima.

Nesse exemplo, a lista de tuplas de alunos é ordenada de acordo com a idade (2° campo de uma tupla de aluno). Ao parâmetro key, foi atribuída uma função lambda que recebe uma tupla como parâmetro de entrada (aluno) e devolve como saída o valor que está no posição 1 da tupla (aluno[1]), que é a idade. Assim, na ordenação dos itens em alunos, a função sorted() aplicará essa função lambda sobre cada item em alunos para obter sua respectiva chave de ordenação.

Na primeira chamada à sorted() no exemplo, nada foi passado ao parâmetro key. Então, a função usou como chave de ordenação para cada tupla de aluno o primeiro elemento da tupla (que é o nome do aluno).

Veja abaixo a documentação da função `sorted():

In [24]:
help(sorted)
Help on built-in function sorted in module builtins:

sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.
    
    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

Abaixo, vemos um outro exemplo de uso de lambdas. No exemplo, definimos a função gera_multiplicador que cria lambdas. A nova lambda é definida em função do parâmetro n passado para gera_multiplicador. A lambda criada é uma função que devolve o valor da multiplicação do seu parâmetro a por n.

In [25]:
def gera_multiplicador(n):
  return lambda a : a * n

meu_duplicador = gera_multiplicador(2)   # lambda a : a * 2
meu_triplicador = gera_multiplicador(3)  # lambda a : a * 3

print(meu_duplicador(11))  #imprime o resultado da chamada (lambda a : a * 2)(11)
print(meu_triplicador(11)) #imprime o resultado da chamada (lambda a : a * 3)(11)
22
33

Funções map(), reduce() e filter()

Funções lambda com frequência são usadas como parâmetros para as funções map(), reduce() e filter() do Python.

  • map(função, sequência) : devolve um iterador para uma sequência gerada a partir da aplicação da função em cada elemento no objeto iterável;
  • filter(função, sequência) : devolve um iterador para uma sequência com os elementos do iterável nos quais a aplicação da função devolve True;
  • reduce(função, sequência[, inicialização]): aplica a função (que deve ter dois parâmetros) de forma acumulativa aos elementos de uma sequência, da esquerda para a direita, de modo a reduzir a sequência a um único valor. A reduce() é uma função do módulo functools.

Exemplos:

In [26]:
lista = [1,2,3,4,5]

# Usa função map para gerar nova sequência 
# com os quadrados dos números da lista original
lista_quadrados = map(lambda x: x*x, lista)
print(list(lista_quadrados))
[1, 4, 9, 16, 25]
In [27]:
lista = [1,2,3,4,5,6,7,8,9,10]

# Usa função filter para gerar nova sequência 
# com os números pares da lista original
lista_pares = filter(lambda x: x % 2 == 0, lista)
print(list(lista_pares))
[2, 4, 6, 8, 10]
In [28]:
import functools

lista_pares = [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

# Usa a função reduce para agregar os elementos 
# da lista de pares em um único valor, que neste exemplo
# é a soma do primeiro valor dos pares 
# O acumulador começa com o valor zero.
soma = functools.reduce(lambda acumulador,par: acumulador + par[0], lista_pares, 0)
print("A soma é:", soma)
A soma é: 10

No exemplo acima, a reduce() calcula a soma (((1+2)+3)+4), por meio da seguinte sequência de chamadas à função lambda:

chamada 1: acumulador = 0, par = (1, 'a'), resultado: 0+1 = 1

chamada 1: acumulador = 1, par = (2, 'b'), resultado: 1+2 = 3

chamada 1: acumulador = 3, par = (3, 'c'), resultado: 3+3 = 6

chamada 1: acumulador = 6, par = (4, 'd'), resultado: 6+4 = 10

Resultado final da reduce(): 10

In [29]:
import functools

lista = [3,2,6,5,1,4]

# Usa a função reduce para agregar os elementos 
# da lista em um único valor, que neste exemplo
# é o maior elemento da lista
maior = functools.reduce(lambda x,y: x if (x > y) else y, lista)
print("O maior valor na lista é:", maior)
O maior valor na lista é: 6

No exemplo acima, a reduce() realiza a seguinte sequência de chamadas à lambda, para obter o valor da redução:

chamada 1: x = 3, y = 2, resultado: 3 (3 > 2)

chamada 2: x = 3, y = 6, resultado: 6 (3 <= 6)

chamada 3: x = 6, y = 5, resultado: 6 (6 > 5)

chamada 4: x = 6, y = 1, resultado: 6 (6 > 1)

chamada 5: x = 6, y = 4, resultado: 6 (6 > 4)

Resultado final da reduce(): 6

Manipulação de Arquivos de Texto

Mudando um pouco de assunto, agora vamos tratar da manipulação de arquivos.

Leitura

Para abrir um arquivo para leitura, podemos usar a função open(), passando como parâmetros para ela o nome (com caminho, se necessário) do arquivo a ser aberto e a string 'r' (de read, para indicar que o arquivo será aberto para leitura). A função devolve um objeto do tipo file.

Para ler o conteúdo inteiro de um arquivo var_arquivo já aberto para leitura, podemos chamar var_arquivo.read(), que devolve uma string (str).

Depois que o uso de um arquivo já tiver sido encerrado, é preciso fechar o arquivo por meio da função close().

Veja o exemplo a seguir, que abre um arquivo chamado 'meu_arquivo.txt', lê cada linha do arquivo e a imprime.

In [30]:
nome_do_arquivo = "meu_arquivo.txt"
arquivo = open(nome_do_arquivo, 'r')  # abre o arquivo para leitura
print("Conteúdo do arquivo '%s'\n" %nome_do_arquivo)

conteudo = arquivo.read()       # lê o conteúdo do arquivo
print(conteudo)
    
arquivo.close()  # depois do uso, fecha o arquivo
Conteúdo do arquivo 'meu_arquivo.txt'

Batatinha quando nasce
Espalha a rama pelo chão

Menininha quando dorme
Põe a mão no coração

Menininha quando dorme
Põe a mão no coração

Quando o arquivo é muito grande, ler o seu conteúdo inteiro de uma só vez não é uma boa ideia, pois ele pode ocupar toda a memória do computador. Nesse caso, o melhor é ler uma linha do arquivo por vez.

Para ler uma linha de um arquivo var_arquivo já aberto para leitura, podemos chamar var_arquivo.readline(), que também devolve uma string (str). Quando se está no final do arquivo, a função readline() devolve uma string vazia ('').

In [31]:
nome_do_arquivo = "meu_arquivo.txt"
arquivo = open(nome_do_arquivo, 'r')  # abre o arquivo para leitura
print("Conteúdo do arquivo '%s'\n" %nome_do_arquivo)

linha = arquivo.readline()      # lê uma linha do arquivo
while linha != "":              # enquanto não chegou ao final do arquivo
    print(linha)          # imprime a linha 
    linha = arquivo.readline()      # lê uma nova linha do arquivo
    
arquivo.close()  # depois do uso, fecha o arquivo
Conteúdo do arquivo 'meu_arquivo.txt'

Batatinha quando nasce

Espalha a rama pelo chão



Menininha quando dorme

Põe a mão no coração



Menininha quando dorme

Põe a mão no coração

Obs.: No exemplo acima, é possível observar que linhas em branco que não existem no arquivo original foram impressas na saída. Quando lemos uma linha do arquivo com readline, ela vem com o caracter de quebra ('\n') de linha no final. E o comando print acima, depois imprimir a string toda em linha, imprime uma quebra de linha (no seu comportamento padrão) - o que faz com que as quebras de linha apareçam dobradas na saída. Para resolver o problema, podemos passar à função print o parâmetro end='', como mostrado a seguir, para indicar ao print que ele deve imprimir nada (string vazia '') depois de imprimir linha. O valor default do parâmetro end é \n.

In [32]:
nome_do_arquivo = "meu_arquivo.txt"
arquivo = open(nome_do_arquivo, 'r')  # abre o arquivo para leitura
print("Conteúdo do arquivo '%s'\n" %nome_do_arquivo)

linha = arquivo.readline()      # lê uma linha do arquivo
while linha != "":              # enquanto não chegou ao final do arquivo
    print(linha, end="")          # imprime a linha 
    linha = arquivo.readline()    # lê uma nova linha do arquivo
    
arquivo.close()  # depois do uso, fecha o arquivo
Conteúdo do arquivo 'meu_arquivo.txt'

Batatinha quando nasce
Espalha a rama pelo chão

Menininha quando dorme
Põe a mão no coração

Menininha quando dorme
Põe a mão no coração

Agora a impressão ficou correta.

Podemos também resolver o problema eliminado a quebra de linha do final de uma linha lida do arquivo. Essa solução envolve o uso do método rstrip(), de strings.

In [33]:
nome_do_arquivo = "meu_arquivo.txt"
arquivo = open(nome_do_arquivo, 'r')  # abre o arquivo para leitura
print("Conteúdo do arquivo '%s'\n" %nome_do_arquivo)

linha = arquivo.readline()      # lê uma linha do arquivo
while linha != "":              # enquanto não chegou ao final do arquivo
    print(linha.rstrip())          # imprime a linha 
    linha = arquivo.readline()     # lê uma nova linha do arquivo
    
arquivo.close()  # depois do uso, fecha o arquivo
Conteúdo do arquivo 'meu_arquivo.txt'

Batatinha quando nasce
Espalha a rama pelo chão

Menininha quando dorme
Põe a mão no coração

Menininha quando dorme
Põe a mão no coração

A chamada linha.rstrip() que devolve uma cópia da string linha sem os "caracteres brancos" que ela tiver em seu final. São considerados caracteres brancos os caracteres espaço (' '), tabulação (\t) e quebra de linha (\n).

Temos também as funções lstrip() (que devolve uma cópia da string eliminando todos os caracteres brancos do início da string) e strip() (que devolve uma cópia da string eliminando todos os caracteres brancos do início e do fim da string). Veja os exemplos abaixo:

In [34]:
texto = "\n\n  \t  Pequeno texto de teste! \t\t \n\n"
print(texto)
print("--------------")
print(texto.rstrip())
print("--------------")
print(texto.lstrip())
print("--------------")
print(texto.strip())
print("--------------")

  	  Pequeno texto de teste! 		 


--------------


  	  Pequeno texto de teste!
--------------
Pequeno texto de teste! 		 


--------------
Pequeno texto de teste!
--------------

Para fazer um laço que lê todas as linhas de um arquivo (uma por vez), também podemos usar o comando for var_linha in arquivo:, onde var_linha é o nome da variável que armazenará o conteúdo de uma linha e var_arquivo é o arquivo já aberto para leitura, como ilustrado abaixo:

In [35]:
nome_do_arquivo = "meu_arquivo_utf8.txt"
arquivo = open(nome_do_arquivo, 'r', encoding="utf8")  # abre o arquivo para leitura
print("Conteúdo do arquivo '%s'\n" %nome_do_arquivo)

for linha in arquivo:      # percorre cada linha do arquivo
    print(linha, end="")    # imprime a linha
    
arquivo.close()  # depois do uso, fecha o arquivo
Conteúdo do arquivo 'meu_arquivo_utf8.txt'

    Para ser grande, sê inteiro: nada
    Teu exagera ou exclui.
    Sê todo em cada coisa. Põe quanto és
    No mínimo que fazes.
    Assim em cada lago a lua toda
    Brilha, porque alta vive.

        Ricardo Reis, 14-2-1933 
        (heterônimo de Fernando Pessoa)	

No exemplo acima, passamos um terceiro parâmetro para a função open(), o parâmetro chamado encoding. Esse parâmetro é opcional; quando não passamos nenhum valor para ele, o valor usado é a codificação padrão do Python 3, que é 'utf8'. Um outro valor possível para o parâmetro encoding é o 'latin1'. O exemplo abaixo lê o arquivo 'meu_arquivo_latin1.txt', que tem codificação latin1.

In [36]:
nome_do_arquivo = "meu_arquivo_latin1.txt"
arquivo = open(nome_do_arquivo, 'r', encoding="latin1")  # abre o arquivo para leitura
print("Conteúdo do arquivo '%s'\n" %nome_do_arquivo)

for linha in arquivo:      # percorre cada linha do arquivo
    print(linha, end="")    # imprime a linha
    
arquivo.close()  # depois do uso, fecha o arquivo
Conteúdo do arquivo 'meu_arquivo_latin1.txt'

    Para ser grande, sê inteiro: nada
    Teu exagera ou exclui.
    Sê todo em cada coisa. Põe quanto és
    No mínimo que fazes.
    Assim em cada lago a lua toda
    Brilha, porque alta vive.

        Ricardo Reis, 14-2-1933 
        (heterônimo de Fernando Pessoa)	

É possível evitar de ter que fechar explicitamente um arquivo (com close()) usando o comando with (...) as <nome_var> : na sua abertura, como no exemplo abaixo. Ao final do corpo do with, o comando with fecha automaticamente o arquivo.

In [37]:
nome_do_arquivo = "meu_arquivo.txt"

with open(nome_do_arquivo, 'r') as arquivo:     # abre o arquivo para leitura
    # Corpo do WITH
    print("Conteúdo do arquivo '%s'\n" %nome_do_arquivo)

    for linha in arquivo:       # percorre cada linha do arquivo
        print(linha, end="")    # imprime a linha

print("Fim!")
Conteúdo do arquivo 'meu_arquivo.txt'

Batatinha quando nasce
Espalha a rama pelo chão

Menininha quando dorme
Põe a mão no coração

Menininha quando dorme
Põe a mão no coração
Fim!

Importante: Para poder reler a partir da primeira linha um arquivo que está aberto e já foi lido, é preciso reabri-lo com a função open(). Todo arquivo aberto tem um marcador que indica a posição do primeiro caracter que ainda não foi lido do arquivo (ou seja, a posição de início da próxima leitura a ser realizada). A função open() põe o marcador no início do arquivo. A cada vez que chamamos a função read() ou a função readline(), o marcador se move para o caractere que vem em seguida do último caractere retornado. No caso de readline, o marcador se move para o primeiro caractere da próxima linha. No caso de read, o marcador se move para o final do arquivo.

Gravação

Também podemos usar a função open() para criar um arquivo de texto para a gravação de dados. Passamos como parâmetros para a função open o nome (com caminho, se necessário) do arquivo a ser criado e a string 'w' (de write, para indicar que o arquivo será aberto para escrita).

Para gravar uma string em um arquivo var_arquivo já aberto para escrita, podemos usar a função var_arquivo.write().

Assim como na leitura de arquivos, depois que o uso de um arquivo aberto para escrita já tiver sido encerrado, é preciso fechar o arquivo por meio da função close().

Veja o exemplo a seguir, que cria um arquivo chamado 'meu_poema.txt' e grava um poema nele.

In [38]:
nome_do_arquivo = "meu_poema.txt"
arquivo = open(nome_do_arquivo, 'w')     # Cria ou abre o arquivo para escrita

poeminha = ["Batatinha quando nasce","Espalha a rama pelo chão","Menininha quando dorme"]

for linha in poeminha:
    arquivo.write(linha)   # grava uma linha do poema no arquivo
    arquivo.write('\n')    # coloca uma quebra de linha no final da linha do poema
    
arquivo.close()  # depois do uso, fecha o arquivo

Quando abrimos um arquivo com a opção de escrita 'w', se o arquivo não existe previamente, ele será criado pela função open(). Entretanto, caso o arquivo já exista, o seu conteúdo atual será sobreescrito (ou seja, perdido).

Para acrescentarmos conteúdo no final de um arquivo que já existe, podemos abrir o arquivo com a função open() passando como parâmetros para ela o nome (com caminho, se necessário) do arquivo a ser aberto e a string 'a' (de append, para indicar que o arquivo será aberto para escrita preservando o seu conteúdo atual). Caso o arquivo que se tenta abrir com opção 'a' não exista previamente, ele será criado pela função open().

Para gravar uma string no final de um arquivo aberto com a opção 'a', também usamos a função write().

Veja o exemplo a seguir, que adiciona novas frases ao final do arquivo chamado 'meu_poema.txt':

In [39]:
nome_do_arquivo = "meu_poema.txt"
arquivo = open(nome_do_arquivo, 'a')   # Abre o arquivo para append (acréscimo)

fim_poeminha = "Põe a mão no coração"
arquivo.write(fim_poeminha)  
    
arquivo.close()  # depois do uso, fecha o arquivo

Captura e Tratamento de Exceções

Quando tentamos usar a função open() para abrir para leitura um arquivo que não existe ou passamos um caminho inválido para um arquivo a ser aberto, a função gera o erro FileNotFoundError, como mostrado abaixo:

In [40]:
nome_do_arquivo = "bla_bla.txt"  # nome que não corresponde a um arquivo existente
arquivo = open(nome_do_arquivo, 'r')
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-40-6678d43cde73> in <module>
      1 nome_do_arquivo = "bla_bla.txt"  # nome que não corresponde a um arquivo existente
----> 2 arquivo = open(nome_do_arquivo, 'r')

FileNotFoundError: [Errno 2] No such file or directory: 'bla_bla.txt'

O programa abaixo pede que o usuário digite o nome de um arquivo a ser aberto, mas caso o usuário digite um nome de arquivo que não existe, em vez da execução da função ser interrompida pelo erro FileNotFound (gerado pela tentativa de abertura do arquivo), o programa pede para que o usuário digite novamente um nome de arquivo e tenta abri-lo novamente. O programa repete essas operações até que o usuário digite o nome de um arquivo que exista.

In [41]:
# Lê o nome do arquivo de dados e o abre para leitura
abriu = False
while not abriu:
    nome_do_arquivo = input("Digite o nome do arquivo de dados: ")
    
    try: 
        arquivo = open(nome_do_arquivo, 'r') 
    except FileNotFoundError:
        print(nome_do_arquivo, " não encontrado. Você digitou o nome direito?")
    else:
        abriu = True
        
print("Conteúdo do arquivo '%s'\n" %nome_do_arquivo)

for linha in arquivo:
    print(linha,end="")
    
arquivo.close()  # depois do uso, fecha o arquivo
Digite o nome do arquivo de dados: arq.txt
arq.txt  não encontrado. Você digitou o nome direito?
Digite o nome do arquivo de dados: meu_poema.txt
Conteúdo do arquivo 'meu_poema.txt'

Batatinha quando nasce
Espalha a rama pelo chão
Menininha quando dorme
Põe a mão no coração

Para evitar que a execução do programa seja interrompida quando o erro FileNotFoundError acontece, o programa usa o comando para tratamento de exceções try.

O Python começa tentando executar o bloco de comandos associado à cláusula try e caso tudo corra bem (no exemplo acima, se o arquivo existe e foi aberto para leitura com sucesso), então pula o bloco de comandos associado à cláusula except e executa o bloco de comandos associado à cláusula else. Mas se o erro FileNotFoundError ocorrer durante a execução dos comandos no try, então o bloco de comandos associado à cláusula except é executado e depois o bloco de comandos do else é pulado.

A cláusula else é opcional. Portanto, podemos ter um comando try só com except. Além disso, no except podemos não especificar o tipo de erro que se deseja capturar. Nesse caso, qualquer erro que for gerado durante a execução dos comandos no bloco do try causará a execução dos comandos no bloco do except.

Veja outro exemplo a seguir:

In [42]:
divisor = 0
try:
    print("Agora eu vou dividir...")
    divisao = 100 / divisor
    print("Resultado da divisão:", divisao )
except:
    print("Não consegui calcular a divisão!")
    
Agora eu vou dividir...
Não consegui calcular a divisão!

Problema - Cálculo das médias a partir de notas em arquivo

Escreva um programa que leia um arquivo onde cada linha contém o nome e as notas de um estudante e, para cada estudante, calcule e imprima a média das notas. Ao final, o programa deve imprimir também a média da turma.

Exemplo: para o arquivo notas.txt com:

jose 10 15 20 30 40 pedro 23 16 19 22 suzana 8 22 17 14 32 17 24 21 2 9 11 17 gisela 12 28 21 45 26 10 joao 14 32 25 16 89

a saída deve ser:

Digite o nome do arquivo: notas.txt Aluno: jose Média = 23.000000 Aluno: pedro Média = 20.000000 Aluno: suzana Média = 16.166667 Aluno: gisela Média = 23.666667 Aluno: joao Média = 35.200000 Média da turma = 23.606667

Solução

In [44]:
def main():
    nome = input("Digite o nome do arquivo: ")

    try:
        arquivo = open(nome, 'r')   # abre o arquivo para leitura
    except:
        print("Não consegui abrir o arquivo %s" %(nome))
    else:
        total = 0
        cont_alunos = 0
        for linha in arquivo:
            dados = linha.split()
            notas = dados[1:]
            soma = 0
            for n in notas:
                soma += float(n)
                
            media = soma/len(notas)
            print("Aluno: %s \t Média = %f"%(dados[0],media))
            
            total += media
            cont_alunos += 1

        print("Média da turma = %f"%(total/cont_alunos))

        arquivo.close()  # fecha o arquivo 

main()
Digite o nome do arquivo: notas.txt
Aluno: jose 	 Média = 23.000000
Aluno: pedro 	 Média = 20.000000
Aluno: suzana 	 Média = 16.166667
Aluno: gisela 	 Média = 23.666667
Aluno: joao 	 Média = 35.200000
Média da turma = 23.606667

Obs.: Quando fazemos uma chamada do tipo S.split(), ou seja, chamamos a split a partir de uma string S sem passar para a função nenhum separador, a função separará S nos caracteres brancos (espaços, tabulações e quebras de linhas); além disso, as strings vazias são removidas da lista de resultado. Veja os exemplos abaixo:

In [45]:
texto = "Para ser  grande,\t sê inteiro:\tnada\nTeu exagera    ou exclui."
print(texto)
print("----------")
print("Separando pelo caracter de espaço: ", texto.split(" "))
print("Separando pelo caracter de tabulação: ", texto.split("\t"))
print("Separando pelos caracteres brancos: ", texto.split())
Para ser  grande,	 sê inteiro:	nada
Teu exagera    ou exclui.
----------
Separando pelo caracter de espaço:  ['Para', 'ser', '', 'grande,\t', 'sê', 'inteiro:\tnada\nTeu', 'exagera', '', '', '', 'ou', 'exclui.']
Separando pelo caracter de tabulação:  ['Para ser  grande,', ' sê inteiro:', 'nada\nTeu exagera    ou exclui.']
Separando pelos caracteres brancos:  ['Para', 'ser', 'grande,', 'sê', 'inteiro:', 'nada', 'Teu', 'exagera', 'ou', 'exclui.']

Módulos

Um módulo é um arquivo contendo definições e instruções Python. O nome do arquivo é o nome do módulo acrescido do sufixo .py. Por exemplo, use seu editor de texto favorito para criar um arquivo chamado ponto.py no diretório atual com o seguinte conteúdo:

In [46]:
import math
 
def distancia(Pa, Pb):
    """ Calcula a distância entre dois pontos Pa = [xa,ya] e Pb = [xb,yb]"""
    xa,ya = Pa #xa,ya = Pa[0],Pa[1]
    xb,yb = Pb #xa,ya = Pb[0],Pb[1]
    dx = xb-xa
    dy = yb-ya
    d = math.sqrt(dx*dx + dy*dy)
    return d

Agora, crie um segundo arquivo chamado poligono.py no mesmo diretório com o seguinte conteúdo:

In [ ]:
# Arquivo poligono.py 
import ponto
 
def perimetro(Pol):
    """Calcula o perímetro de um polígono Pol = [P1, P2, ..., Pn] composto por n pontos."""
    peri = 0.0 # perímetro
    Pant = Pol[-1] # ponto anterior
    for Pi in Pol:
        dist = ponto.distancia(Pant, Pi)
        peri += dist
        Pant = Pi
    return peri

Considerando um polígono como uma lista dos seus vértices consecutivos, o módulo poligono acima define uma função para calcular o perímetro de um polígono fornecido, ou seja, a soma de todos os seus lados. Porém, como cada vértice é o ponto comum entre os lados do polígono, podemos calcular o tamanho de cada lado usando a função distancia do módulo ponto. Para isso, precisamos primeiramente importar o módulo ponto, usando o comando import ponto. Feito isso, podemos então chamar a função distancia usando a sintaxe ponto.distancia.

No import, o interpretador procura módulos em uma lista de diretórios incluídos na variável sys.path. É possível incluir diretórios na sys.path usando append():

In [48]:
import sys
print(sys.path)

# Adiciona no sys.path o diretório que contém os
# módulos ponto.py e poligono.py
sys.path.append("/home/kellyrb/mac216-aula24")
print(sys.path)
['/home/kellyrb/Drive/Insync/MAC216', '/home/kellyrb/anaconda3/lib/python38.zip', '/home/kellyrb/anaconda3/lib/python3.8', '/home/kellyrb/anaconda3/lib/python3.8/lib-dynload', '', '/home/kellyrb/.local/lib/python3.8/site-packages', '/home/kellyrb/anaconda3/lib/python3.8/site-packages', '/home/kellyrb/anaconda3/lib/python3.8/site-packages/IPython/extensions', '/home/kellyrb/.ipython']
['/home/kellyrb/Drive/Insync/MAC216', '/home/kellyrb/anaconda3/lib/python38.zip', '/home/kellyrb/anaconda3/lib/python3.8', '/home/kellyrb/anaconda3/lib/python3.8/lib-dynload', '', '/home/kellyrb/.local/lib/python3.8/site-packages', '/home/kellyrb/anaconda3/lib/python3.8/site-packages', '/home/kellyrb/anaconda3/lib/python3.8/site-packages/IPython/extensions', '/home/kellyrb/.ipython', '/home/kellyrb/mac216-aula24']

Agora é possível importar o módulo polígono e usar a função perímetro:

In [49]:
import poligono

poligono.perimetro([[0,0],[1,0],[1,1],[0,1]])
4.0
Out[49]:
4.0

Dentro de um módulo, o nome do módulo (como uma string) está disponível como o valor da variável global __name__:

In [50]:
 print(poligono.__name__)
poligono

No entanto, quando você roda um módulo Python no modo script (clicando em "Run Module" tal como indicado na figura abaixo), o código no módulo será executado, da mesma forma que quando é importado, mas com a variável name com valor "main", ao invés de valer a string do nome do módulo "poligono".

Isto significa que adicionando este código abaixo ao final do módulo poligono.py, você pode tornar o arquivo utilizável tanto como script quanto como um módulo "importável", porque esse código no if só roda se o módulo é executado como arquivo "principal" (como, por exemplo, executando o comando python3 poligono.py num terminal). Se o módulo é importado, o código dentro do if não será executado.

In [ ]:
# Trecho a ser adicionado em poligono.py, para torná-lo utilizável 
# tanto como script quanto como módulo

if __name__ == "__main__":
    # só executa em modo script
    per = perimetro([[0,0],[1,0],[1,1],[0,1]])
    print("Perímetro:",per)

Referências:

  1. Videoaulas de Introdução à Ciência da Computação com Python, do prof. Fabio Kon: https://www.youtube.com/playlist?list=PLcoJJSvnDgcKpOi_UeneTNTIVOigRQwcn
  2. Notas de aula para cursos de Introdução à Computação em Python do IME-USP: https://panda.ime.usp.br/cc110/static/cc110/index.html
  3. Livro: Como Pensar Como um Cientista da Computação - Aprendendo com Python: Versão Interativa https://panda.ime.usp.br/pensepy/static/pensepy/index.html
  4. Notas de aula da disciplina MAC2166: https://www.ime.usp.br/~mac2166/1s20/index.html
  5. Livro Fluent Python: Clear, Concise, and Effective Programming: https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/

Sugestões de leitura para outros recursos importantes de Python não vistos em MAC0216: