Tutorial 2 - Como funciona o Python

Secção dedicada à linguagem de programação favorita dos quarkianos: Python!

Tutorial 2 - Como funciona o Python

Mensagempor jap em Quarta Fev 01, 2012 6:43 pm

Vou aqui mostrar alguns aspectos básicos do Python, recorrendo como exemplo, a um pequeno programa que escrevi para resolver o problema dos números automórficos (ver nesta thread). Boa parte do conteúdo deste tópico encontra-se também aí, mas acrescentei alguns detalhes que podem ser clarificadores.

Problema:

Um número inteiro positivo n é dito automórfico se a representação decimal de n^2 terminar no número n. Por exemplo, 76 é automórfico pois 76 \times 76 = 5776. Estes números também são conhecidos por números curiosos, por possuírem várias propriedades notáveis. Existem três números automórficos de um só dígito (1, 5, 6) e apenas dois números automórficos de 2 dígitos (25, 76).

Problema: escrever um programa em Python que calcule os primeiros 10 números automórficos. :wink:

Ora aqui vai a minha resolução (pitónica) para o problema:

Código: Seleccionar Todos
#!/usr/bin/env python
"""
Automorphic numbers
"""

if __name__ == "__main__":

    nmax = 10
    auto_numbers = []
    n,nauto = 1,0
   
    while nauto < nmax:
        if str(n*n).endswith(`n`):
            auto_numbers.append(n)
            nauto += 1
        n += 1
   
    print auto_numbers


OK, o que é uma resolução pitónica? :lol:

É um jargão muito usado pelos utilizadores de Python: código pitónico é aquele que utiliza ao máximo a expressividade e as potencialidades do Python, e também algumas das suas idiossincrasias... :lol:

Um código não pitónico é aquele em que se vislumbra que o programador pensou no algoritmo em C, Java ou outra linguagem e simplesmente o transcreveu para Python...

Uma das características mais fantásticas do Python é conhecida por "batteries included", o que significa que estão à disposição do utilizador um grande número de ferramentas de alto nível para lidar com inúmeros problemas comuns (por exemplo estruturas de dados como listas e dicionários, ferramentas para tratamento de strings, expressões regulares, acesso à net, etc). Assim fica muito mais fácil resolver problemas como este! :wink:

Programadores de C, gostaram de
Código: Seleccionar Todos
str(n*n).endswith(`n`)
? :lol:

Vou então dar algumas dicas de Python, usando o meu programazito como exemplo. :wink:

As primeiras linhas de código

Código: Seleccionar Todos
#!/usr/bin/env python
"""
Automorphic numbers
"""

if __name__ == "__main__":


podem, na realidade ser omitidas! :lol:

Passo a explicar.

A primeira linha

Código: Seleccionar Todos
#!/usr/bin/env python


é um comentário. Tudo o que se segue numa linha ao sinal # é ignorado pelo interpretador de Python, e portanto podem usar este sinal para introduzir comentários no vosso código. :wink:
O comentário que eu escrevi é um comentário algo especial. Nada diz ao Python, que o ignora, mas é útil para quem correr este programa num sistema UNIX (ou derivado, como o Linux). É completamente inútil no Windows, mas também não faz mal estar lá. O que é que significa para um sistema UNIX? Bem, neste sistema operativo quando executam este ficheiro como uma "shell script" o sistema lê a primeira linha de texto e fica a saber que tem de procurar o programa python para interpretar o ficheiro. Mas, como disse, isto é uma característica do UNIX e nada tem a ver com o Python, que simplesmente ignora esta linha porque começa com um #. :lol:

As 3 linhas seguintes,

Código: Seleccionar Todos
"""
Automorphic numbers
"""


são o que em Python se designa por uma "doc string". É de bom tom pitónico iniciar sempre um programa com uma doc string, que explique a quem lê o programa o que ele faz. É uma espécie de documentação do programa. Uma doc string pode ter quantas linhas quiserem, desde que esteja entre duas linhas com """. Exemplo de uma doc string mais completa:

Código: Seleccionar Todos
"""
Automorphic numbers
jap@ 1/12/2008
version 1.0.0.0
Use at your own risk!

This is a copyleft program to be shared by quarkonia.
"""


Notem que um comentário não é uma doc string e uma doc string não é um comentário. Um comentário é ignorado pelo programa (embora como está no código, possa ser usado para documentá-lo). Uma doc string faz parte do vosso programa, fica guardada na RAM do computador quando o programa corre, e podem aceder a ela (doc string) de dentro do programa! A sério! :lol: Veremos mais tarde como isto pode ser útil - trata-se de uma ferramenta de metaprogramação.

A 'bizarra' linha

Código: Seleccionar Todos
if __name__ == "__main__":


também pode ser omitida, se preferirem. O que é que ela significa?

É para ser interpretada à letra: se quando o Python interpretar este ficheiro o programa que estiver a correr for o programa principal (que tem internamente o nome de __main__), então (e só então) executa o código que se segue a esta instrução. (A propósito os dois "underscores" que antecedem e precedem __name__ e __main__ têm um significado especial, mas não me vou alargar agora sobre isso - até porque esta linha pode ser omitida, certo? :wink:)

Como vocês verão em breve, os ficheiros de Python podem correr na forma de programa principal ou serem importados por outros programas (neste caso não são, obviamente, o programa principal). Se se habituarem a incluir esta linha "bizarra" poderão mais tarde utilizar estes ficheiros como módulos para serem importados de outros programas, o que, verão, é imensamente útil.

Ora vamos então à explicação do "core" do programa...

Código: Seleccionar Todos
    nmax = 10
    auto_numbers = []
    n,nauto = 1,0
   
    while nauto < nmax:
        if str(n*n).endswith(`n`):
            auto_numbers.append(n)
            nauto += 1
        n += 1
   
    print auto_numbers


Primeira observação: este bloco de código está indentado 4 espaços para a direita; isto é necessário porque se trata de um bloco "if":

Código: Seleccionar Todos
if __name__ == "__main__":
    nmax = 10
    auto_numbers = []
    n,nauto = 1,0
   
    while nauto < nmax:
        if str(n*n).endswith(`n`):
            auto_numbers.append(n)
            nauto += 1
        n += 1
   
    print auto_numbers

A identação é muito importante (obrigatória em Python!). Podem usar 4 espaços, um "tab", ou um número diferente de espaços, mas é preciso identar o código!

Ora a primeira instrução do bloco que se segue ao if é um exemplo da instrução mais básica do python:

Código: Seleccionar Todos
nmax = 10


O que é que esta instrução faz? Esta instrução (simbolizada pelo sinal = que nada tem a ver com igualdade matemática) significa atribuir um nome a um objecto. O nome é o que está à esquerda do sinal e o objecto é o que está à direita. O que é um objecto? É um conjunto de dados que reside algures na memória do computador. Imaginem uma caixa com "coisas" lá dentro e uma grande etiqueta na caixa com o nome do conjunto dessas coisas. É que um objecto pode ser uma grande colecção de coisas muito díspares que residem na memória do computador mas que de alguma forma estão "agrupadas" numa caixa. Neste caso o que está dentro da caixa é o número 10, mas podia haver lá muito mais coisas. Bem, para dizer a verdade, o python quando cria a caixa para colocar lá dentro o número "10" coloca também lá dentro mais coisas, que não são visíveis na instrução acima, como por exemplo uma espécie de manual de instruções sobre como usar o que está dentro da caixa. :shock: Por exemplo no caso acima, no manual de instruções figuram o tipo do objecto (um número inteiro), e uma lista das operações que ele suporta.

Em python todos os objectos (números e outras "coisas") são colocados dentro de caixas, caixas essas que têm etiquetas com um "nome". Se, por alguma razão, a etiqueta for arrancada (isto pode acontecer!!! :shock: ) e a caixa ficar sem etiqueta, a caixa vai ser destruída (com o objecto lá dentro!). Vou explicar como isto funciona.

Imaginem a memória do vosso computador (onde são armazenados todos os objectos) como um grande armazém onde tudo está escrupulosamente arrumado. Há um gestor do armazém que é um "Extreme Overly Protective Memory Manager (EEMO)", que não nos deixa interagir directamente com nenhum objecto do armazém - toda e qualquer operação tem de passar por ele, e é ele quem tem a seu cargo a gestão do "stock". Por exemplo, podemos pedir-lhe para guardar um objecto no armazém, mas não podemos especificar em que sítio (prateleira) é que ele irá ser colocado. Ao depositarmos um objecto nas mãos do gestor ele coloca-o dentro de uma caixa, cola-lhe uma etiqueta com o nome que nós atribuímos ao objecto, ele regista-o num livro de entradas (estilo o objecto de nome X está na prateleira Y do enésimo andar do aramzém) e finalmente armazena-o no local que lhe parece mais conveniente. É possível pedir ao gestor do armazém para ir buscar (a qualquer altura) o objecto de um dado nome. Ele verifica se esse objecto existe e, em caso aformativo vai buscá-lo ao armazém para nosso uso. Também aceita que se rebatize o objecto, atribuindo-lhe um outro nome. Se o nome que pretendermos atribuir a um objecto já estiver a ser usado por um outro objecto no armazém, ele vai a este objecto, arranca-lhe a etiqueta com o seu nome e coloca-a no objecto para o qual pretendemos reutilizar o nome. Como não é possível haver dois objectos com o mesmo nome, ele arranca mesmo a etiqueta do objecto mais antigo para reutilizar no mais novo.

Mas atenção! O EOMM (Extremely Overprotective Memory Manager) tem como ajudante um igualmente zeloso funcionário da limpeza. E esse funcionário para o tempo a varrer a pente fino a memória do vosso computador (reservada ao python) à procura de caixas sem etiqueta, e se encontrar uma, destrói-a de imediato, libertando o espaço de memória. Chama-se a este funcionário da limpeza o "colector de lixo", em inglês "Garbage Collector", ou GC. O GC é mesmo muito zeloso: entre cada passagem para limpeza de caixas sem nome decorrem apenas alguns milisegundos! :lol:

Imagem

Em resumo:

a) Em python os dados e outras "coisas", que chamamos genericamente de objectos, são guardados dentro de "caixas"; é preciso dar um nome à caixa o que se faz com a instrução de atribuição (de nome) que tem a forma

Código: Seleccionar Todos
nome = objecto


b) Um objecto sem nome irá ser destruido implacavelmente pelo colector de lixo dentro dos próximos milisegundos!

c) Uma curiosidade: é possível dar dois ou mais nomes a um mesmo objecto, se acharem que isso pode ser útil.

Por exemplo a instrução

Código: Seleccionar Todos
jose = ze = "o meu colega de turma"


faz um duplo batismo do objecto que é a cadeia de caracteres "o meu colega de turma". Esta cadeia de caracteres pode ser referenciada, daqui para a frente, com um dos dois nomes, jose, ou ze.

Agora que já sabemos o que faz a primeira instrução que se segue ao if (cria um objecto com o nome nmax que referencia o número 10, que é o número máximo de números automórficos que pretendemos gerar), vejamos a segunda instrução:

Código: Seleccionar Todos
auto_numbers = []


Esta instrução cria um novo tipo de objecto, uma lista (vazia) que vai servir para guardar os números automórficos. Esta lista vai também ser guardada dentro de uma caixa com o nome auto_numbers.

Vamos então ao núcleo do código...

Código: Seleccionar Todos
    while nauto < nmax:
        if str(n*n).endswith(`n`):
            auto_numbers.append(n)
            nauto += 1
        n += 1


Este núcleo consiste de um ciclo while que tem embutido lá dentro um bloco if.

Acho que o código é bastante auto-explicativo! :lol:

O valor de nauto, que é o nome do objecto que guarda o número de números automórficos que já se encontraram num dado instante da execução do programa, é inicialmente 0. Sempre que é encontrado um número automórfico, o valor de nauto é incrementado de 1. É o que faz a instrução

Código: Seleccionar Todos
nauto += 1


Na realidade se o objecto de nome xxx for um número inteiro a instrução

xxx += 1

faz com que o nome xxx passe a designar o número inteiro original incrementado de uma unidade.

No ciclo while o número n, que inicialmente é 1, é incrementado em cada iteração do ciclo de uma unidade ( n+=1 ), percorrendo todos os números, um a um, até que se encontre o número desejado de números automórficos.

Como é que se testa se o número n é automórfico? Esta é a parte gira. :D
O teste de automorfismo é

Código: Seleccionar Todos
str(n*n).endswith(`n`)


ou seja, multiplicamos n por n e convertemos esse número numa string. Todas as strings têm um conjunto de funções próprias que lhes podem ser aplicadas, os chamados métodos de string. Esssas funções são invocadas pela sintaxe "string.f", onde f é o método desejado. Felizmente as strings em Python têm o método ".endswith(substring)"! E qual é a terminação da string que buscamos? Procuramos que a string termine nos dígitos do próprio número n, não é? Então podemos resolver o problema de duas formas:

Código: Seleccionar Todos
str(n*n).endswith(str(n))


ou ainda

Código: Seleccionar Todos
str(n*n).endswith(`n`)


porque em python

Código: Seleccionar Todos
`objecto`
é o mesmo que

Código: Seleccionar Todos
str(objecto)


ou seja, converte o objecto numa string, se tal for possível.

Claro que podíamos ainda escrever

Código: Seleccionar Todos
`n*n`.endswith(`n`)


mas a versão que eu apresentei é a mais pitónica - mais tarde explicarei porquê! :lol:

Está tudo claro ou há dúvidas? :roll:

Agora é só instalar o Python, correr o programazito, e desobrir a resposta ao problema! :wink:
José António Paixão
Departamento de Física da FCTUC
Avatar do utilizador
jap
Site Admin
Site Admin
 
Mensagens: 6790
Registado: Quinta Nov 09, 2006 9:34 pm
Localização: Univ. de Coimbra

Voltar para Pitónica

Quem está ligado

Utilizadores a navegar neste fórum: Nenhum utilizador registado e 1 visitante

cron