Práticas de desenvolvimento

Pythonismo

Python tem muitas funcionalidades ou sintaxes interessantes que outras linguagens não possuem, ou que são implementadas de maneira diferente. É muito comum entrar em fóruns online e ver discussões sobre códigos "pytônicos", isto é, códigos que foram otimizados para legibilidade e funcionamento com Python.

Nesta disciplina, queremos aprender algoritmos, então vamos evitar construções como essas, mas nesta seção vamos tentar implementar coisas de jeitos pytônicos.

  1. Sendo A uma lista. Qual a diferença entre for valor in A: e for i in range(len(A)):? Explique com palavras o que a variável i assume nos dois fors.

  2. Quando estamos trabalhando com listas, é normal utilizarmos List Comprehension para facilitar o trabalho. O que o código abaixo imprime?

    A = [-2, 3, -5, 1, 0, - 11]
    B = [i for i in A if i < 0]
    
    print(B)
    
  3. Agora, baseado no exemplo acima, usando List Comprehension que copia em B apenas os elementos múltiplos do primeiro elemento da lista A. O código de B deve possuir apenas 1 linha e pode ter repetições. Preencha o modelo:

    A = [5, 8, 3, 0, 15, 7, 20]
    B = ...
    
    print(B)
    

    Depois, teste para uma lista aleatória onde o primeiro elemento é 7. Verifique se funciona.

  4. Também é possível utilizar funções na compreensão de listas. O que o código abaixo imprime?

    def soma(c):
        if c + 3 >= 8:
             return True
        else:
             return False
    
    A = [2, 8, 3, 11, 4, 5, 6, 7]
    B = [i for i in A if soma(i)]
    
    print(B)
    
  5. Baseado no exemplo acima, faça um código que receba uma string e transforme-o em uma lista de caracteres, depois, copie para uma outra lista b apenas os caracteres seguidos de vogais.

  6. Funções dentro de funções são algo que você pode encontrar em python. "Nested Functions" servem uma série de razões, principalmente para organizar funções que só serão usadas para coisas pequenas e possuem escopo local para funções. Veja este exemplo:

    def externa(A):
        def interna(B):
            return B + 1
        C = interna(A)
        print(A, C)
    

    Qual é a saida de externa(4)? E o que acontece se eu tentar chamar interna(5) a partir da função principal?

  7. Baseado no que você viu até agora na secção de Pythonismo, faça uma função que recebe uma lista com números, alguns repetidos, utilizando duas funções internas, faça uma função que adicione 1 em cada valor e outra função interna para tirar repetições para copiar a uma outra lista.

  8. Funções lambda são funções "anônimas" em python que são de grande uso para reduzir código ou comprimir operações. Por exemplo:

        def add_one(x):
            return x + 1
    

    Pode ser reescrito como:

        add_one = lambda x: x + 1
    

    E eu posso utilizar print(add_one(2)) sem maiores problemas no meu código. Faça uma função multiplica_5 que dado um número, o resultado é multiplicado por 5. Depois, faça uma função lambda que inverta uma lista.

  9. O jeito mais poderoso de usar funções lambda é criando funções genéricas que podem ser "instanciadas". Veja esse exemplo:

    def myfunc(n):
        return lambda a : a * n
    
    mydoubler = myfunc(2)
    mytripler = myfunc(3)
    
    print(mydoubler(11))
    print(mytripler(11))
    

    Sabendo disso, faça uma função genérica que que pega o módulo de um n genérico. Depois, instancie n para 5 e 7 e teste com números.

  10. Por fim, existe em python uma função chamada zip(), que cria um iterador que agrega duas ou mais iteráveis (listas, tuplas, etc...) e é muito útil para percorrer listas simultaneamente. Veja um exemplo:

    A = [1, 2, 3, 4, 5]
    B = ['a', 'b', 'c', 'd', 'e']
    
    for a, b in zip(A, B):
      print(a, b)
    

    Essa função irá imprimir "1 a", "2 b", etc... Utilize zip para receber três listas A,B,C, com valores inteiros e coloque em uma lista D o resultado D[i] = A[i]*B[i]*C[i], porém, utilizando zip. Depois, descubra o que acontece quando as listas não possuem o mesmo tamanho.

Lidando com erros e tratando exceções

Vamos nos basear em um programa que dado dois números, A e B, ele divide A por B.

Perceba que ao tentarmos dividir por zero, o programa retorna o erro: ZeroDivisionError. Desse modo, o programa termina a execução com uma mensagem de erro. Pode ser que alguns erros sejam de fato esperados; a esses erros damos o nome de exceção. Por exemplo, dividir por zero é um erro que pode acontecer em um programa de divisão, particularmente quando o divisor é um número lido do usuário. Assim, precisamos tratar essa exceção para que o programa continue rodando e não seja interrompido por um erro.

Para isso, podemos usar as construções try e o except para tratarmos erros em Python. No bloco de try testamos um bloco de código no fluxo normal de execução. Se ocorrer alguma exceção, o fluxo de execução será desviado para o bloco de except correspondente; nesse bloco, devemos tratar o erro. Por exemplo, adaptando o código acima para tratar a divisão por 0, temos o código a seguir.

Observe que o nome do erro é colocado após a palavra-chave except. Essa palavra-chave indica qual exceção estamos esperando; pode haver vários blocos. Portanto, para qualquer tipo de erro, ZeroDivisionError, EOFError, entre outros, podemos tratá-lo para o nosso programa executar ao nosso critério.

  1. Escreva uma função que leia do teclado um número de ponto flutuante. Para converter uma string em número, use a função float. Se a linha digitada não for uma string que possa ser convertida em ponto flutuante, a função deve tentar novamente até que um valor válido seja digitado.

Testes de mesa

  1. Qual a saída quando você fornece os dados de seu RA?

    def func(a):
        n = a % div
        a = a / div
        x = 0
        for i in range(n):
            x = x + a
        return x
    
    def main():
        n = int(input("Os 2 últimos dígitos do seu RA: "))
    
        a = 1
        div = 2
        n = func(n + 15)
        for i in range(3):
            for j in range(a):
                print(n,end = ' ')
                n += 1
            print("")
            a += 1
        return;
    
  2. Uma matriz em Python é apenas uma lista de listas! Ainda vamos aprender mais sobre esta estrutura de dados, mas você já sabe simular códigos usando matrizes. Considere a matriz:

    $$ mat = \begin{bmatrix} 1 & 0 & 2\\ 2 & 2 & 0\\ 0 & 1 & 0\\ \end{bmatrix} $$

    a) Execute o comando (faça sem o computador):

    mat = [
        [1, 0 , 2,],
        [2, 2 , 0,],
        [0, 1 , 0,],
    ]
    mat1 = [
        [0, 0 , 0,],
        [0, 0 , 0,],
        [0, 0 , 0,],
    ]
    
    for i in range(3):
        for j in range(3):
            mat1[i][j] = mat[mat[i][j]][mat[j][i]]
    

    Supondo que todas as variáveis tenham sido declaradas corretamente, responda às perguntas:

    b) Qual o valor da variável mat1 resultante?

    c) Substitua no comando a variável mat1 por mat. Qual o valor de mat resultante?

  3. Descreva textualmente o que o programa a seguir faz. Faça um teste de mesa se necessário.

    def main():
        n = int(input())
        m = 2*n + 1;
        for i in range(n):
            j = 2*i - 1;
            for l in range( int((m - j) / 2), 0, -1)::
                print(" ", end = "")
            for l in range(j, 0, -1):
                print("*", end = "")
            print("")
    
    main()
    
  4. Qual a saída do código abaixo quando o usuário digita o último dígito do seu RA.

    def f(a, b):
        c = 3*a + 4*b
        c = c % d
        return c
    
    def main():
        global d
        d = 20
        a = int(input())
        b = 3
        print(f(b, a))
        return
    
    main()
    

Legibilidade de código

Para um código ser legível, ele deve ter as seguintes características.

Características:

  • Comentários que resumem o que faz um conjunto de comandos.
  • Formatação bem definida (indentação, espaçamentos, nomes)
  • Documentação adequada de cada programa ou algoritmo.

Um pouco de maniqueísmo - Comentários

Mal:

import math

def main():
    # lê um valor do teclado
    x = input()
    # lê outro valor
    y = input()
    # calcula o x ao quadrado
    # mais y ao quadrado
    z = x*x + y*y;
    # tira a raiz de z
    z = math.sqrt(z)
    # mostra o valor de z
    print(z)

main()

Bem:

import math

def main():
    # lê valores dos catetos
    x = float(input())
    y = float(input())

    # aplica Pitágoras
    z = x*x + y*y
    z = math.sqrt(z)

    # devolve a hipotenusa
    print(z)

main()

Um pouco de maniqueísmo - Indentação

Mal:

def main():
    print("n:") ; n = int(input())
    if n==1: print("Unidade")
    elif(n  >=0):
        if(n% 3): print("Deixa resto")
        else:    print("Não deixa")
    else: print("Negativo")
main()

Bem:

def main():
    n = int(input("n:"))
    if n == 1:
        print("Unidade")
    elif n >= 0:
        if n % 3 == 1:
            print("Deixa resto")
        else:
            print("Não deixa")
    else:
        print("Negativo")

main()

Um pouco de maniqueísmo - Nome de variáveis

Mal:

def main():
    exercises = int(input())
    n1 = float(input())
    n3 = float(input())
    n2 = 4.0
    n4 = 6.0
    if exercices > 7:
        n4 = 7.0
    aluno = (n1*n2 + n3*n4)/10
    print(aluno)

main()

Bem:

def main():
    exercicios = int(input())
    nota1 = float(input())
    nota2 = float(input())
    peso1 = 4.0
    peso2 = 6.0
    if exercicios > 7:
        peso2 = 7.0
    media = (nota1*peso1 + nota2*peso2)/10
    print(media)

main()

Um pouco de maniqueísmo - Documentação

Mal:

def main():
    r = float(input())
    v = (4.0*3.1425*r*r*r)/3.0
    print(v)
main()
  • O que esse programa faz?
  • O valor de $\pi$ está errado, com quem eu falo?

Mal:

'''
Calcula o volume de uma esfera.

Lê o raio de uma esfera pelo
teclado e imprime na tela o
volume correspondente.

ENTRADA:
    - o raio da esfera
SAÍDA:
    - o volume da esfera
PRÉ-CONDIÇÃO:
    - O raio deve ser maior que zero.
AUTOR: Joãozinho
'''

PI = 3.1415

def main():
    r = float(input())
    v = (4.0*PI*r*r*r)/3.0;
    print(v)

main()

Exercício

  1. Escolhendo um estilo de programação

    a) Não existe um jeito certo de escrever código: cada programador adota um estilo diferente, o importante é manter-se consistente. Pesquise sobre a importância da legibilidades de programas de computador: posição das chaves, indentação, uso de espaço, etc. Depois reflita e disserte brevemente sobre o seguinte tema: “Um código é escrito só uma vez, mas é lido diversas vezes”.

    b) Manter o código organizado pode ser trabalhoso, especialmente para programadores iniciantes. Mas felizmente o existem ferramentas que ajudam nessa tarefa. Pesquise e experimente ferramentas de formatação automática de códigos, como plugins ou extensões de editores de texto e IDEs.

Lidando com casos especiais

Olhando os casos especiais: Algumas vezes, um algoritmo “funciona” para vários casos, mas não para todos. Esses casos “problemáticos” geralmente são casos especias, que devem ser tratados individualmente.

Alguns exemplos:

  • Os primeiros dois termos da sequência de Fibonacci.
  • Primeiro elemento no algoritmo para descobrir o maior número.

Lição: Sempre lembrar dos possíveis casos especiais, como o primeiro ou o último elemento de uma sequência, lista, etc.

  1. Existem $n$ lâmpadas numeradas ordenadamente. Elas estão ligadas em um circuito de tal modo que toda vez que uma lâmpada é acesa ou apagada, outras lâmpadas podem são apagadas ou acendidas. Quando uma lâmpada é

    • ACENDIDA, as posteriores invertem.
    • APAGADA, as anteriores invertem.

    Inicialmente:

    Lâmpada 3 acendida:

    Lâmpada 4 apagada:

    a) Escreva um programa que receba:

    • a quantidade de lâmpadas, $n$, de um circuito como no anterior,
    • um número de uma lâmpada específica, $k$, que deverá ficar acesa.

    O programa deve instruir o usuário a acender ou apagar lâmpadas para que, no final, apenas a lâmpada de número $k$ fique acesa. Para acender uma lâmpada de número XX , imprima “Acender lampada XX”, ou, para apagar uma lâmpada de número XX, imprima “Apagar lampada XX”

    b) Modifique o programa anterior para receber

    • a quantidade de lâmpadas, $n$, de um circuito como no anterior,
    • a quantidade de lâmpadas que devem ficar acesas, $m$,
    • uma lista de $m$ números de lâmpadas que devem ficar acesas.

    No final, apenas as lâmpadas listadas devem permanecer acesas.

    c) Verifique se o seu programa está correto. Para isso, verifique se existe algum caso especial. Faça um teste de mesa se necessário.