Supresas divertidas em Python – Easter Eggs

Existem algumas brincadeiras que os desenvolvedores do interpretador Python “esconderam” como surpresas engraçadas para o usuário. Por exemplo, abra um shell Python e digite:

>>> import antigravity

Após digitar tal comando, será aberta uma janela do navegador com uma tirinha do xkcd, que brinca com Python: http://xkcd.com/353/

Outra brincadeira bem engraçada é a seguinte:

>>> from __future__ import braces
File "<stdin>", line 1
SyntaxError: not a chance

Repare na mensagem de erro. Ela faz uma brincadeira com o fato de programas em Python não precisarem e não utilizarem { e } para delimitar os blocos de código, isso porque Python define os blocos através da indentação do código.

O easter egg que é provavelmente o mais famoso é o seguinte:

>>> import this

Ao digitar esse comando, será impresso na tela um texto conhecido como “The Zen of Python”, que apresenta em alguns versos a filosofia empregada no desenvolvimento da linguagem.

Faça os testes você mesmo. Se você conhece algum outro easter egg em Python, poste aqui nos comentários.

Variáveis e valores

Ao criarmos uma variável em nosso código, na realidade estamos é criando um objeto e uma referência para esse objeto. Por exemplo, considere o código abaixo:

>>> x  = 100

O que fizemos foi “criar uma variável” com o valor 100 dentro dela, certo? Mais ou menos…  O que acontece, na realidade, é que 100 é um objeto do tipo inteiro que é alocado em algum lugar na memória. E x nada mais é do que uma referência (um ponteiro) para aquele objeto.

O código abaixo cria uma lista de 3 elementos e faz com que a variável x a referencie.

>>> x = [1, 2, 3]

Ao executar o código seguinte, o que irá acontecer? Errou quem acha que estamos fazendo uma cópia da lista [1, 2, 3] em y.

>>> y = x

Antes de explicar, vejamos seu conteúdo.

>>> print y
[1, 2, 3]

Ué, não é cópia mesmo? Vejamos:

>>> x.append(10)
>>> print y
[1, 2, 3, 10]

O que aconteceu? Adicionamos um valor ao final da “lista x” e ele apareceu milagrosamente na “lista y”? Não. Quando executamos y = x, fizemos com que a variável y passasse a apontar para o mesmo objeto apontado por x (a lista [1,2,3]). Assim, ao executarmos o append(10) sobre o objeto apontado por x, estamos modificando o objeto apontado por y também (que na realidade é o mesmo!).

Ou seja, ao executar y = x não fizemos uma cópia da lista, mas sim da referência a tal lista.

E como fazemos para copiar a lista? Bem, em Python existe um tipo de operação chamado de slicing, que funciona sobre objetos como as listas e que gera um novo objeto do mesmo tipo, com os elementos que estão contidos entre as posições especificadas pelos índices. Por exemplo:

>>> x = [1, 2, 3, 4, 5, 6, 7]
>>> print x[1:5]
[2, 3, 4, 5]

Essa operação gerou uma nova lista, contendo os valores das posições 1 até 5-1 (4). Se omitirmos os índices, estaremos nos referindo à lista como um todo, do início ao fim. Assim:

>>> print x[:]
[1, 2, 3, 4, 5, 6, 7]
>>> y = x[:]
>>> x.append(10)
>>> print x
[1, 2, 3, 4, 5, 6, 7, 10]
>>> print y
[1, 2, 3, 4, 5, 6, 7]

Como podemos ver no código acima, ao executarmos y = x[:], estamos gerando uma nova lista, com conteúdo idêntico à lista apontada por x, e fazendo com que y faça referência a ela. Então, para que façamos uma cópia de uma lista apontada pela variável x em uma variável y, fazemos o seguinte:

>>> y = x[:]

Então, lembre-se, para fazer uma cópia de uma lista inteira, podemos usar o “truque” do slicing e lembre-se também de que uma simples atribuição de uma variável para outra não copia a lista, mas sim a referência.

Porém, nem tudo são flores… Se a lista em questão contiver referências a outros objetos, ao invés dos objetos em si, a “cópia” feita será uma cópia rasa, pois os objetos referenciados dentro da lista não serão copiados. O contrário disso seria a cópia profunda (deep copy), onde os objetos referenciados na lista é que serão copiados, ao invés de apenas as suas referências.

Leia mais sobre as listas: http://docs.python.org/tutorial/datastructures.html

Número de argumentos variável em funções

Para quem não sabe, Python possui uma sintaxe que permite que definamos uma função com um número indefinido de argumentos. Isso pode ser feito através do * (asterisco). Por exemplo:

def func(x, *args):
    print x
    print args
func(1, 2, 3, 4, 5)

Execute o código acima, e verá a seguinte saída:

1
(2, 3, 4, 5)

Ou seja, o primeiro argumento fornecido pelo usuário (o inteiro 1) é atribuído ao primeiro parâmetro da função. Os argumentos restantes (os inteiros 2, 3, 4 e 5) são empacotados em uma tupla e passados ao parâmetro *args. Desse modo, os elementos de args podem ser acessados como os elementos de uma tupla qualquer, seja por índice, ou realizando uma travessia com for in. Por exemplo, uma função que realiza a soma de todos os argumentos recebidos:

def soma(*args):
    result = 0
    for elem in args:
        result += elem
    return result

soma(1, 2, 3, 4, 5)

A saída da execução do código acima será a soma dos valores 1, 2, 3, 4 e 5, que é 15.

Também podemos usar o * em frente ao nome de sequências (como tuplas, listas,) para fazer com que os valores que compõem tais sequências sejam desmembrados e passados como argumentos para funções. Considere e função func() abaixo:

def func(a,b,c,d):
    print a,b

Ela possui 4 parâmetros (aqui representados pelas variáveis a, b, c, d) e imprime os valores de a e de b. Certo. Imagine agora que você possui uma lista com 4 valores, que deseja passar para a função func(). Poderia fazer o seguinte:

l = [1, 2, 3, 4]
func(l[0], l[1], l[2], l[3])

Correto? Sim. Mas dá pra fazer melhor. E se eu quisesse passar a lista toda para a função, sem precisar acessar os elementos individuais.

l = [1, 2, 3, 4]
func(l)

Errado! Afinal, a função espera 4 valores e estamos passando somente um, uma lista. É aí que podemos usar o *. Veja:

l = [1, 2, 3, 4]
func(*l)
Legal, não? Em um próximo post, vou falar de dicionários com argumentos nomeados.

Por que “for i in range(x, y): … “?

Quem aprendeu a programar em uma linguagem diferente de Python, pode achar meio estranha a forma que usamos tradicionalmente para fazer uma iteração sobre um conjunto de números. Em linguagem C, por exemplo, se quisermos realizar  10 repetições de algo, poderíamos fazer:

int i;
for (i = 0; i < 10; i++) {
        // código a ser repetido
}

O código é bastante claro:

Para i, cujo valor inicial é 0 (i = 0), repita a execução do código que está entre { e } enquanto o valor de i for menor que 10 (i < 10), e ao final de cada execução do código entre { e } incremente o valor de i em uma unidade (i++).

Temos três declarações bem explícitas em nosso for:

  1. O que vai ser executado inicialmente, antes de começarmos a execução da repetição em si (setup);
  2. O que vai ser verificado antes de cada repetição, para decidir se o código fará ou não mais uma repetição (condição);
  3. O que vai ser executado ao final de cada uma das repetições que forem executadas.

Em Python, como usamos o for?

for i in range(0, 10):
        # código a ser repetido

E aí? Cadê o valor inicial de i? Cadê a condição? Cadê a instrução que altera o valor de i ao final de cada repetição?

Antes de mais nada, precisamos entender o que faz a função range() isoladamente. Abra um console Python e faça o teste:

>>> range(0, 10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

Isso mesmo, range(x, y) gera e retorna uma lista de números de x até y, sem incluir o y. Vamos confirmar então:

>>> l = range(0, 10)
>>> type(l)
<type 'list'>

Agora que sabemos que range() gera e retorna uma lista de números, vamos voltar à questão do for. Poderíamos então reescrever o for da seguinte forma:

for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:
    # código a ser repetido

Esse código poderia ser lido como:

Para cada elemento da lista de números, faça algo (o código a ser repetido).

Assim, em um primeiro momento, o valor de i será o valor do primeiro elemento da lista (0). Então, o código a ser repetido é executado e o valor de i passa a ser o valor do próximo elemento da lista, se houver próximo, caso contrário, encerra a repetição. No caso do exemplo, i passa a ter o valor 1. Então o código a ser repetido é executado e os passos anteriores são repetidos até que não existam mais elementos na lista.

A sequẽncia de passos também poderia ser descrita como:

  1. O valor de i é setado para o valor do primeiro elemento da lista (valor 0).
  2. O código a ser repetido é executado.
  3. O valor de i passa a se o valor do elemento seguinte da lista, se houver um próximo elemento. Se não houver, encerra a execução do for. Se houver, volta ao passo 2, com o valor de i já atualizado.

Tudo parece meio automágico, não? Mas não é. Só podemos fazer isso com as listas porque elas são iteráveis (iterables). Objetos desse tipo retornam um elemento seu de cada vez, permitindo esse mecanismo automático do for. Veja o que o glossário da documentação Python [1] fala sobre Iterable:

iterable

An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict and file and objects of any classes you define with an __iter__() or __getitem__() method. Iterables can be used in a for loop and in many other places where a sequence is needed (zip(), map(), …). When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object. This iterator is good for one pass over the set of values. When using iterables, it is usually not necessary to call iter() or deal with iterator objects yourself. The for statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop. See also iterator, sequence, and generator.

Espero que o “for i in range(x, y):” tenha ficado mais claro, principalmente para aqueles que, assim como eu, aprenderam programação com uma linguagem na qual a estrutura do for é diferente.

[1] http://docs.python.org/glossary.html

A função enumerate()

Como percorremos os elementos de uma lista? Podemos utilizar a construção for in. Para cada elemento da lista, faça algo com tal elemento.

l = ['hello', 'world', 'hi', 'earth']
for word in l:
    print word

Outra forma de fazermos uma travessia em uma lista é através do acesso via índices.

l = ['hello', 'world', 'hi', 'earth']
for i in range(0, len(l)):
    print l[i]

O que é semelhante a:

l = ['hello', 'world', 'hi', 'earth']
i = 0
while i < len(l):
    print l[i]
    i = i + 1

Qual é a diferença básica entre os dois últimos códigos (que utilizam for i in range e while) para o primeiro trecho de código apresentado? É o acesso ao índice, à posição do elemento na lista. Utilizando a estrutura for elem in, não sabemos qual é a posição do item atual. Isso poderia ser necessário, por exemplo, se precisarmos escrever uma função que retorne uma lista contendo os índices das palavras que começam pelo caractere ‘a‘.

Para contornar esse problema, existe a função enumerate(). Essa função pode receber como entrada uma lista e irá retornar um objeto do tipo enumerate, que poderá ser percorrido pelo for. Vejamos um exemplo simples:

l = ['hello', 'world', 'hi', 'earth']
for i, word in enumerate(l):
    print i, word

Isso irá gerar a seguinte saída:

0 hello
1 world
2 hi
3 earth

E se quisermos escrever a função que retorna uma lista contendo as posições das palavras de uma lista que começam com a letra ‘a’?

def posicoesQueIniciamCom(lista, letra='a'):
    result = []
    for i, palavra in enumerate(lista):
        if palavra.startswith(letra):
            result.append(i)
    return result

nomes = ['abc', 'hua', 'aaa', 'asdfg', 'bnm']
print posicoesQueIniciamCom(nomes)

O resultado da execução do código acima será:

[0, 2, 3]

Assim, sempre que precisarmos dos índices dos elementos das listas (ou outros iteráveis) que estivermos percorrendo, podemos utilizar a função enumerate() para construir uma estrutura composta por pares índice-elemento.

filter() – filtrando elementos de uma lista

Imagine uma situação na qual você tenha uma lista composta por uma grande quantidade de números, e deseja utilizar esses números para a realização de um cálculo. Porém, alguns desses valores devem ser descartados, por não se enquadrarem na faixa de valores que você pode utilizar nos cálculos. Como retirar os elementos que não cumprem os requisitos?

A solução mais óbvia seria percorrer a lista removendo os elementos que não cumpram o requisito, ou então, criar uma nova lista somente com os elementos que cumprem o requisito. Considere que o requisito seja: somente números positivos podem compor a lista. Vamos considerar a lista lista como exemplo:

lista = [8, 9, -1, -3, 3, -5, -4, 5, -4, 2, 5, 91, -11, 5, 10, 93, -75]
lista_valida = []
for elem in lista:
    if elem > 0:
        lista_valida.append(elem)
print lista_valida

O código acima cria uma nova lista (lista_valida) somente com os elementos positivos da lista lista. Funciona, certo? Sim, mas Python oferece uma forma mais prática e elegante para fazermos isso, a função filter(). Como o nome já diz, ela filtra uma lista. Ou seja, só “passam” os elementos que cumprirem os requisitos. A função filter() recebe dois parâmetros: uma função para validação de cada elemento, e uma sequência de valores (como uma lista, por exemplo). Vamos implementar o exemplo anterior utilizando filter().

lista = [8, 9, -1, -3, 3, -5, -4, 5, -4, 2, 5, 91, -11, 5, 56, 93, 13, 15, -75, 98]
def maior_que_zero(num):
    if num > 0:
        return True
 else:
        return False

lista_valida = filter(maior_que_zero, lista)
print lista_valida

Criamos uma função chamada maior_que_zero() que retorna True caso o valor recebido como parâmetro seja maior que 0 e False, caso contrário. Essa função vai ser chamada para cada elemento da lista lista, quando executarmos a função filter(), e essa, por sua vez, só vai incluir na lista nova (a ser retornada) os valores para os quais a execução de maior_que_zero() resultaram em True.

Vamos a outro exemplo: temos uma lista composta por strings e queremos obter uma lista contendo somente as strings que começam pela letra ‘a’ ou pela letra ‘e’.

palavras = ['teste', 'assistente', 'estou', 'aqui', 'onde', 'ouro', 'adeus']
def f(p):
    return p.startswith('a') or p.startswith('e')

print filter(f, palavras)

No código acima, aplicamos a função f() a cada elemento de palavras para filtrar essa lista. Ao final, filter() retorna uma lista contendo somente os elementos para os quais a expressão p.startswith(‘a’) or p.startswith(‘e’) retorna True (que são os elementos que iniciam por ‘a’ ou por ‘e’).

Esse é mais um dos tantos recursos interessantes que Python nos oferece e que podemos utilizar em nosso benefício. Teste os códigos acima e faça seus próprios testes.

Acessando o último elemento de uma lista

Essa vai ser uma dica rápida, mas muito útil…

Consideremos uma lista qualquer contendo alguns números inteiros:

>>> L = [1, 1, 2, 3, 5, 8, 13, 21]

Como podemos acessar o último elemento dessa lista? Nessa lista específica, poderíamos acessar o último elemento assim: L[7]. Mas é claro que isso funciona só para a lista L com 8 elementos. E para acessar o último elemento de uma lista qualquer?

>>> L[len(L)-1]
21

O código acima é uma das formas de acessar o último elemento de uma lista, mas não é a forma mais conveniente. Python nos oferece uma forma muito melhor para fazer isso:

>>> L[-1]
21

Também podemos acessar o penúltimo elemento através do índice -2, e assim por diante. Legal né? Pois é, mais uma das coisas que tornam Python tão legal. 🙂

maketrans – Tabela de tradução de Strings

Já ouviu falar do alfabeto Leet (ou l337)? É um alfabeto empregado principalmente na internet e usado para comunicação entre pessoas, onde algumas letras do alfabeto latino são substituídas por símbolos graficamente parecidos. Utilizando alfabeto leet, a palavra STREET ficaria 57r337, por exemplo. Ou seja, o número 5 substitui a letra S, o número 7 substitui a letra T, 3 substitui a letra E.

Vamos criar uma ferramenta que traduza texto escrito usando o alfabeto tradicional para texto usando alfabeto leet. Uma forma tosca de fazermos isso é substituir todas as ocorrências de determinada letra do alfabeto tradicional por seu correspondente leet. Exemplo:

s = raw_input('Digite uma frase:')
s = s.replace('a', '4')
s = s.replace('t', '7')
s = s.replace('e', '3')
s = s.replace('s', '5')
...
print s

Porém, existe outra forma mais simples e mais elegante, usando uma Tabela de Tradução. Considere a tabela abaixo. Nela temos duas colunas, uma chamada de Entrada e outra chamada de Saída. Usando a tabela de tradução, cada letra da coluna Entrada encontrada no texto será substituída pela sua correspondente na coluna Saída.

Entrada Saída
A 4
B 8
T 7
E 3
S 5
I 1
O 0
Z 2

No módulo string, fornecido juntamente com a biblioteca padrão do Python, temos um método chamado maketrans, que, dadas duas entradas, cria uma tabela de tradução. Por exemplo, para criar a tabela de tradução apresentada acima, utilizamos o código abaixo:

from string import maketrans
entrada = 'ABTESIOZ'
saida   = '48735102'
tabela = maketrans(entrada, saida)
s = raw_input('Digite uma frase para ser convertida para leet:')
print s.translate(tabela)

As variáveis entrada e saida são strings que irão representar as colunas da tabela de tradução. Para cada ocorrência de um caractere da string entrada em uma string que será traduzida, tal caractere será substituído pelo elemento correspondente na variável saida. Por exemplo, todo caractere ‘A’ em uma string a ser traduzida será substituído pelo caractere ‘4’, e assim por diante.

Usando a mesma idéia, podemos escrever um programinha que cifre uma mensagem usando a Cifra de Caesar. Nesse tipo de cifra, cada letra de uma frase é substituída por outra letra, de acordo com um deslocamento do alfabeto tradicional. Considere como exemplo os dois alfabetos abaixo:

a b c d e f g h i j k l m n o p q r s t u v w x y z
c d e f g h i j k l m n o p q r s t u v w x y z a b

O alfabeto da segunda linha possui um deslocamento de 2 em seus caracteres. Agora, podemos cifrar textos de acordo com tais alfabetos. Por exemplo:

hello world  ---->  jgnnq yqtnf

Cada caractere da string original é substituído pelo correspondente da tabela de tradução. ‘h’ é substituído por ‘j’, e assim por diante. Quem vê a mensagem  jgnnq yqtnf  não consegue descobrir qual o significado desta, a não ser que conheça ou descubra o algoritmo utilizado para cifrá-la. É claro que, nesse caso, é muito simples descobrir. Outro exemplo de cifragem usando os mesmos dois alfabetos:

estou saindo ---->  guvqw uckpfq

Como implementar uma Cifra de Caesar simples, com deslocamento 2, em Python?

from string import maketrans

entrada = 'abcdefghijklmnopqrstuvwxyz'
saida   = 'cdefghijklmnopqrstuvwxyzbc'

def cifra_de_caesar(texto):
    tabela = maketrans(entrada, saida)
    return texto.translate(tabela)

print cifra('hello world')

Como vimos nos exemplos anteriores, o método maketrans, combinado com o translate, nos facilita muito a vida na hora de fazer a cifragem/decifragem de uma mensagem. Poderíamos fazer o mesmo com o método replace da string, mas o código ficaria muito maior e difícil de manter.

Maiores informações sobre maketrans: http://docs.python.org/library/string.html#string-functions

Retorno de múltiplos valores em funções – com tuplas

Quando aprendemos o conceito de funções no estudo de programação, nos é ensinado que uma função é um trecho de código ao qual damos um nome, que realiza uma tarefa específica, e que pode receber várias entradas de uma vez e retornar apenas um valor por vez. Até aí, tudo certo. Mas, quando estamos programando em Python, poderemos nos deparar com códigos semelhantes ao seguinte:

def func(x, y):
    return x*y, x+y

mult, soma = func(2, 3)

Pera aí! Uma função que retorna 2 valores de uma só vez? O valor da multiplicação das entradas será retornado e atribuído à variável mult, e o valor da soma das entradas atribuído à variável soma. Outro exemplo:

def func(x, y):
    return x+y, x-y, x*y, x/y, x%y, "hello"

Uma função com seis valores de retorno ao mesmo tempo? Quem, num momento de desespero e extrema gambiarra, nunca pensou em definir uma struct só para agrupar dois ou três valores que desejava que fossem retornados todos de uma vez por uma única função? Pois é, em Python é possível retornar mais de um valor em uma função. Porém, nem tudo é mágica. Para entender, vamos começar por um exemplo. Abra um shell Python e crie uma variável da seguinte forma:

>>> x = 2, 4, 10

Você deve estar pensando: “Isso não vai funcionar! Como posso eu atribuir 3 valores para apenas uma variável?”. O que está esperando para testar? Viu? Funcionou. Mas como? Calma, o próximo passo é vermos o conteúdo de x após a atribuição:

>>> print x
(2, 4, 10)

Agora, vamos verificar qual é o tipo de dados da variável x:

>>> type(x)
<type 'tuple'>

Opa! Então, quer dizer que quando eu faço return x*y, x+y, estou na verdade retornando uma TUPLA? Isso mesmo. Mais legal que isso somente o fato de Python suportar a seguinte forma de atribuição de elementos de tuplas:

x = 1 , 2, 10
a, b, c = x

Após as atribuições da segunda linha, o valor de a será 1, de b será 2 e de c será 10. Legal né? É exatamente isso que acontece quando criamos uma função com “mais de um valor de retorno”. Na realidade, nossa função possui apenas um valor de retorno, que é uma tupla. E os elementos dessa tupla são atribuídos à variáveis individuais no chamador da função.

Mais sobre tuplas: http://www.franciscosouza.com.br/aprendacompy/capitulo_09.html

Leitura de arquivos de configuração .ini em Python

Embora não seja a melhor forma possível de descrevermos a configuração de algo, os arquivos .INI ainda existem em grande quantidade. Quem nunca viu ou precisou que seu programa lesse um arquivo parecido com o seguinte?

[section1]
config1=100
config2=0
[section2]
confign=-1

Por mais que queiramos evitar tais arquivos, por serem até considerados um formado obsoleto, às vezes é necessário ler um arquivo desses para obter informações para nosso programa. Como fazer isso em Python? Ler o arquivo linha por linha? Não! Vamos usar o módulo ConfigParser.

Primeiramente, devemos importar o módulo, instanciar um objeto ConfigParser e realizar a leitura do arquivo de configuração desejado (no nosso caso, config.ini):

import ConfigParser
cfg = ConfigParser.ConfigParser()
cfg.read('config.ini')

Feito isso, o arquivo de configuração já está lido, agora basta que obtenhamos os valores que queremos extrair do arquivo. Para obter um valor do arquivo, é preciso especificar a seção e a propriedade que queremos obter. Por exemplo, o código abaixo obtém o valor da propriedade confign da seção section2 e o armazena na variável x:

x = cfg.getint('section2', 'confign')

Veja que utilizamos um método chamado getint( ) para fazer a leitura de um valor inteiro do arquivo .INI. Caso os dados a serem lidos fossem de outro tipo, poderíamos usar um dos seguintes métodos: getboolean( ), getfloat( ), ou simplesmente get( ) no caso de strings.

Com o mesmo módulo, também é possível realizarmos a escrita de arquivos .ini. Veja mais em: http://docs.python.org/library/configparser.html