Comportamento inesperado na divisão inteira

Alerta de versão: esse post foi escrito com base na versão 2 da linguagem Python. Na versão 3, o operador de divisão inteira é o //.

Para quem já estudou um pouco de programação, o seguinte resultado não é surpresa alguma:

>>> 3 / 2
1

Por se tratar de uma divisão de números inteiros, o resultado é truncado em um número inteiro também. Até aí, está tudo dentro do esperado, não? Então, abra um shell Python e teste a seguinte operação:

>>> -3 / 2
-2

Quem imaginava que o resultado seria -1, levante a mão: \o_

Por que -2 ?!

Em Python, a divisão inteira arredonda o resultado para baixo, ou seja, sempre para o menor número inteiro mais próximo. Por exemplo: 3 / 2 seria 1.5, mas o resultado é arredondado para 1 (e não 2), pois 1 < 2. Já no caso de -3 / 2, o resultado seria -1.5, mas por se tratar de uma divisão inteira, ele é arredondado para -2 e não para -1, pois -2 < -1.

Isso não é muito comum nas linguagens de programação. Em C e Java, por exemplo, uma divisão inteira tem o seu resultado sempre arredondado em direção ao 0. Python, como já vimos, faz com que o resultado de uma divisão inteira seja arredondado para baixo. Veja a ilustração abaixo:

drawing

Mas por que Python faz dessa forma? Ninguém melhor para explicar isso do que o criador da linguagem, o Guido Van Rossum. Em um post no blog Python History, ele explica que resultados negativos de divisão inteira são arredondados em direção a -∞ para que a seguinte relação entre as operações de divisão (/) e de módulo (%) se mantenha também para as operações com resultados negativos:

quociente = numerador / denominador
resto = numerador % denominador
denominador * quociente + resto == numerador

Vamos testar?

>>> numerador = -3
>>> denominador = 2
>>> quociente = numerador / denominador
>>> resto = numerador % denominador
>>> print quociente, resto
-2 1
>>> print denominador * quociente + resto == numerador
True
# e agora, com numerador positivo
>>> numerador = 3
>>> quociente = numerador / denominador
>>> resto = numerador % denominador
>>> print quociente, resto
1 1
>>> print denominador * quociente + resto == numerador
True

Perceba que se o resultado fosse arredondado em direção ao zero, a propriedade não seria satisfeita.

Esse é um detalhe de implementação muito importante e que todo desenvolvedor Python deve conhecer para não introduzir bugs em seus códigos, para evitar de perder horas depurando algo que parecia fugir comportamento esperado e também para evitar sentimentos de “esse intepretador está errado!”.

Leia mais sobre o assunto no post do Guido Van Rossum no blog The History of PythonWhy Python’s Integer Division Floors.

Brincando com Listas

Criando uma lista de números em sequência:

# python 2:
lista = range(100)
# python 3:
lista = list(range(100))

Criando uma lista com list comprehensions:

lista = [x*2 for x in range(100)]

Percorrendo uma lista com for in:

for x in lista:
    # faça algo com x

Percorrendo uma lista, obtendo os valores e seus índices:

for indice, valor in enumerate(lista):
    print "lista[%d] = %d" % (indice, valor)

Percorrendo um pedaço de uma lista usando slicing:

for x in lista[40:60]:
    # faça algo com x

Percorrendo uma lista de trás pra frente definindo o passo do slicing como -1:

for x in lista[::-1]:
    # faça algo com x

Ou:

for x in reversed(lista):
    # faça algo com x

Percorrendo uma lista ordenada:

for x in sorted(lista):
    # faça algo com x

Acessando o último elemento de uma lista com o índice -1:

print lista[-1]

Copiando uma referência para uma lista:

>>> nova_ref = lista
>>> nova_ref is lista
True

Copiando de verdade uma lista:

>>> nova_lista = lista[:]
>>> nova_lista is lista
False

Ou, usando o módulo copy:

>>> import copy
>>> nova_lista = copy.copy(lista)
>>> nova_lista is lista
False

Ou caso lista contivesse listas aninhadas e quiséssemos fazer com que estas também fossem completamente copiadas, e não somente referenciadas, usaríamos a função deepcopy():

>>> nova_lista = copy.deepcopy(lista)
>>> nova_lista is lista
False

Embaralhando os elementos de uma lista (in-place):

>>> import random
>>> random.shuffle(lista)  # altera a própria lista

Obtendo uma amostra aleatória (de 10 elementos) de uma lista:

>>> print random.sample(lista, 10)
[729, 9025, 2401, 8100, 5776, 784, 1444, 484, 6241, 7396]

Obtendo um elemento aleatório de uma lista:

>>> random.choice(lista)
7744

Gerando uma lista com 10 números aleatórios, com valores entre 0 e 99:

>>> lista_aleatoria = random.sample(range(0, 100), 10)

Obtendo o maior elemento de uma lista:

>>> lista = range(0, 10)
>>> print max(lista)
9

O menor:

>>> print min(lista)
0

Pegando somente os elementos de índice par:

>>> print lista[::2]
[0, 2, 4, 6, 8]

Os de índice ímpar:

>>> print lista[1::2]
[1, 3, 5, 7, 9]

Somando todos os elementos de uma lista:

>>> print sum([1, 2, 3, 4])
10

Juntando duas listas, formando pares de elementos:

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

Separando os elementos de uma lista de forma intercalada:

>>> lista = range(0, 10)
>>> intercaladas = lista[::2], lista[1::2]
>>> print intercaladas
([0, 2, 4, 6, 8], [1, 3, 5, 7, 9])

Transformando uma lista de strings em uma string CSV:

>>> lista = ["ola", "mundo", "aqui", "estamos"]
>>> csv_values = ','.join(lista)
>>> print csv_values
ola,mundo,aqui,estamos

Aplicando uma função (neste caso, anônima) a todos elementos de uma lista:

>>> lista = range(1, 11)
>>> print map(lambda x: x*-1, lista)
[-1, -2, -3, -4, -5, -6, -7, -8, -9, -10]

Filtrando os elementos de uma lista de acordo com um critério:

>>> def criterio(x): return x >= 0
>>> print range(-5, 5)
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
>>> print filter(criterio, range(-5, 5))
[0, 1, 2, 3, 4]

Retirando os elementos cujo valor é zero (ou melhor, cujo valor é avaliado como False):

>>> print filter(None, range(-2, 2))
[-2, -1, 1]

E você, tem alguma dica que poderia ser inserida aqui no post? Poste sua sugestão nos comentários.

Sugestão de livro: Two Scoops of Django

2scoops

O Django é uma baita ferramenta que auxilia muitos desenvolvedores a concretizar seus projetos web com agilidade e simplicidade impressionantes. A documentação do framework é bastante vasta. São blogs de desenvolvedores, listas de email, livros bem completos, a trilha no StackOverflow, além de muitos e muitos projetos abertos no GitHub e BitBucket, e é claro, a excelente e completíssima documentação oficial. Até aí, tudo perfeito. Material para iniciantes querendo aprender Django existe de monte, mas quando as dúvidas começam a ficar um pouco mais específicas, ou questões relacionadas à boas práticas em projetos Django, a coisa começa a ficar mais escassa. Felizmente para nós, Djangonautas, o Daniel Greenfeld e a Audrey Roy começaram a resolver um pouco desse problema escrevendo o excelente Two Scoops of Django: Best Practices for Django 1.5.

O livro não é um tutorial e tampouco uma documentação exaustiva do Django, mas sim uma valiosa coleção de dicas e conselhos sobre boas práticas em projetos Django, atualizada para a versão 1.5. Já nos primeiros capítulos, fiquei com aquela sensação de “putz, eu tô fazendo tudo do jeito mais difícil nos meus projetos!”. Os autores vão mostrando os problemas e apresentando as soluções de uma forma bem prática, passando dicas, alertas, e, o que achei mais legal de tudo, as Package Tips, que são dicas sobre pacotes de terceiros que os autores costumam usar em seus projetos e que são uma verdadeira mão-na-roda.

Talvez você esteja pensando consigo próprio: “ah, eu já vi várias coisas dessas espalhadas pela web…”. Aí é que está o ponto principal, pois os autores pegaram a vasta experiência que possuem e compilaram uma série de dicas em um só lugar. E quando falo de dicas, não pense que são trechinhos pequenos de texto com links para outros recursos. Pelo contrário, os autores se preocuparam em explicar bem o porquê das coisas, sem cansar o leitor.

Outra coisa que achei interessante é que, diferentemente de um monte de livros que a gente vê por aí, parece que os autores deixaram de lado a preocupação de que o livro deles possa ficar obsoleto por passar dicas pontuais de pacotes específicos para resolver determinados problemas. Me parece que muitos autores limitam a abrangência de seus livros por medo de abordar um assunto mais específico, que poderia sofrer mudanças em breve (talvez o sentimento de estar sendo eternizado pelo livro deixe alguns autores meio confusos). Os autores do Two Scoops of Django não se preocuparam muito com isso e até se comprometeram em publicar erratas caso alguns elementos sofram mudanças nos próximos tempos.

O livro em si é muito bem organizado, com um formato muito bom para a leitura. Os autores se preocuparam MUITO e conseguiram fazer um layout excelente para ser lido em e-readers. Eu comprei a versão para Kindle, e esse é o primeiro livro técnico que leio em que não é preciso ficar diminuindo o tamanho da fonte para conseguir ler decentemente os trechos de código. Parabéns aos autores pela preocupação com os leitores da versão digital do livro!

O conteúdo

Não vou fazer aqui uma análise completa do livro. Vou listar apenas algumas coisas importantes que aprendi com o livro:

  • Como estruturar meus projetos Django;
  • Que as class-based-views são muito fáceis de usar;
  • Que na versão 1.5 do Django ficou barbada estender o modelo User;
  • Que realizar processamento nos templates é roubada;
  • Que dá pra manter configurações (settings.py) específicas para diferentes ambientes;
  • Que import relativo existe; (isso mesmo, eu não conhecia esse recurso)
  • Que select_related() quebra um galhão pra consultas grandes;
  • E muitas outras coisas! (muitas mesmo!) 🙂

Enfim, o conteúdo do livro é fantástico! Recomendo a todo mundo que tem um pouquinho de experiência com o Django que compre e leia esse livro. Não é preciso ser especialista no framework para se aproveitar do conteúdo dele. Se você está na dúvida se o livro é adequado para você, dê uma conferida no conteúdo dele na página oficial.

Eu recomendo!

De 0 a 10, dou nota 10 para esse livro. Li ele apenas uma vez, mas já vou começar a reler para fixar bem as dicas, pois são muitas coisas novas.

Se quiser seguir minha dica, estão à venda as versões impressa e digital do livro. Comprando direto pela página do livro, é possível comprar o pacote digital (formatos PDF, mobi e ePub, tudo DRM-free) por 17 dólares (preço em 22/06/2013). Na Amazon americana, está à venda a versão impressa. E ainda, se quiser comprar pela Amazon Brasil, eles estão vendendo a versão para Kindle.

Se ainda estiver na dúvida se o livro vale mesmo a pena, leia os reviews dos leitores na Amazon.

Conjuntos em Python

Hoje percebi que o blog já tratou sobre listas, dicionários, tuplas; mas até agora não escrevi texto algum sobre os conjuntos (também conhecidos por sets).

Sets: o que são?

Sets (ou, como iremos chamar daqui para a frente, conjuntos) são estruturas disponíveis como builtins do Python, utilizadas para representar coleções desordenadas de elementos únicos. É importante sempre lembrar dos conjuntos por suas duas principais características:

  1. Os elementos não são armazenados em uma ordem específica e confiável;
  2. Conjuntos não contém elementos repetidos.

A característica número 1 é importante, porque o desenvolvedor jamais deve confiar na ordenação de um conjunto, visto que a ordem em que os elementos são mantidos nos conjuntos varia de implementação para implementação do interpretador Python. Não é a toa que conjuntos não suportam indexação nem fatiamento (slicing). Bom, chega de papo e vamos ver alguns exemplos de conjuntos.

Mãos na massa

Vamos começar definindo um conjunto, usando a sintaxe para literais de conjuntos (introduzida há pouco tempo nas versões 3.1 e 2.7):

>>> s = {1, 2, 3, 4}
>>> print s
set([1, 2, 3, 4])

Existem várias operações disponíveis nos conjuntos através de métodos, como as operações mais conhecidas de teoria dos conjuntos, como união, interseção e diferença.

União

Imagem da Wikipedia

A U B (Crédito da imagem: Wikipedia)

>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> print a.union(b)
set([1, 2, 3, 4, 5, 6])

Interseção

Imagem da Wikipedia

A ∩ B (Crédito da imagem: Wikipedia)

>>> print a.intersection(b)
set([3, 4])

Essa operação é muito útil quando precisamos descobrir elementos que duas listas possuem em comum:

>>> l1 = [1, 2, 3]
>>> l2 = [2, 4, 3]
>>> print set(l1).intersection(l2)
set([2, 3])

Perceba que convertemos l1 para conjunto para podermos usar o método intersection; já l2 não precisou ser convertida, pois esses métodos exigem que apenas o primeiro argumento seja um conjunto. Poderíamos obter o resultado da interseção como uma lista também:

>>> l3 = list(set(l1).intersection(l2))
>>> print l3
[2, 3]

O método intersection não modifica os conjuntos recebidos como parâmetro. Se quisermos que o resultado da interseção seja gravado como novo valor do primeiro conjunto, ao invés de retornar o novo conjunto como resultado, podemos usar o método intersection_update:

>>> a.intersection_update(b)
>>> print a
set([2, 3])

Diferença

Imagem da Wikipedia

B \ A (Crédito da imagem: Wikipedia)

A diferença entre dois conjuntos A e B retorna somente os elementos de A que não estão em B, ou seja, retira de A todos os elementos comuns a ambos os conjuntos:

>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> print a.difference(b)
set([1, 2])
>>> print b.difference(a)
set([5, 6])

Observe que a.difference(b) é o mesmo que a \ b e que b.difference(a) é o mesmo que b \ a.

Assim como o método anterior, difference também não altera o conjunto sobre o qual é chamado. Para alterá-lo, é necessário usar o método difference_update().

Diferença simétrica

Diferença simétrica é uma operação sobre os dois conjuntos, que retorna todos os elementos (de ambos os conjuntos a e b) que pertencem a somente um dos conjuntos.

>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> print a.symmetric_difference(b)
set([1, 2, 5, 6])

Diferença Simétrica

A △ B (Crédito da imagem: Wikipedia)

Pertinência

Além das operações tradicionais de união, interseção e diferença, também temos operações de verificação de pertinência. A seguir veremos algumas.

Para verificar se um determinado elemento pertence a um conjunto, podemos usar o já conhecido operador de pertinência in:

>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> 1 in a
True
>>> 5 in a
False

Também podemos verificar se um conjunto é um subconjunto de outro:

>>> a = {1, 2, 3, 4}
>>> c = {1, 2}
>>> c.issubset(a)
True
>>> a.issubset(c)
False

Além disso, podemos verificar se um conjunto é superconjunto de outro:

>>> a.issuperset(c)
True

Outra relação importante que podemos checar é a disjunção entre dois conjuntos. Dois conjuntos são disjuntos se tiverem interseção nula. Exemplo:

>>> c = {1, 2}
>>> d = {3, 4}
>>> c.isdisjoint(d)
True

Removendo elementos duplicados de uma sequência

Por definição, um conjunto é uma coleção de valores únicos (e desordenados). Assim sendo, se passarmos ao construtor de conjuntos uma lista com valores repetidos, esses valores serão eliminados de forma a permanecer apenas um deles. Exemplo:

>>> lista = [1, 1, 2, 3, 5, 8]
>>> conjunto = set(lista)
>>> print conjunto
set([8, 1, 2, 3, 5])

Ou, se quisermos ter de volta uma lista:

>>> lista = list(set(lista))

ATENÇÃO: a operação acima pode (e, com grandes chances, irá) bagunçar a lista. Ou seja, a ordem original irá se perder.

>>> print lista
[8, 1, 2, 3, 5]

Leia mais

Entendendo os decorators

Hoje me deparei com um excelente texto sobre decorators que me inspirou a escrever algo sobre o assunto que para muita gente ainda é um tanto quanto nebuloso. Vou tentar aqui explicar o funcionamento de um decorator e mostrar algumas possíveis aplicações.

Aviso aos iniciantes: esse assunto pode ser um pouco confuso para quem ainda está iniciando em programação. Caso sinta dificuldades, não desanime e pule antes para a seção que contém as referências para melhor entendimento do texto.

O que é um decorator?

Um decorator é uma forma prática e reusável de adicionarmos funcionalidades às nossas funções/métodos/classes, sem precisarmos alterar o código delas.

O framework para desenvolvimento web Django oferece diversos decorators prontos para os desenvolvedores. Por exemplo, para exigir que o acesso a determinada view seja feito somente por usuários autenticados, basta preceder o código da view (que em geral é uma funçãozinha ou classe) pelo decorator @login_required. Exemplo:

@login_required
def boas_vindas(request):
    return HttpResponse("Seja bem-vindo!")

É claro que isso não é mágica. Como a gente pode ver no código-fonte do decorator login_required, os detalhes estão apenas sendo ocultados do código-fonte do nosso projeto. Assim, ao invés de ter que, a cada view, escrever o código que verifica se determinado usuário está autenticado, basta usar o decorator. Isso faz com que adicionemos a funcionalidade de verificar se um usuário está ou não logado no site, com uma linha de código apenas. Que barbada, não?

O decorator é um açúcar sintático que Python oferece aos desenvolvedores desde a versão 2.4, facilitando o desenvolvimento de códigos reusáveis.

OK, mas como implementar um decorator?

Você já sabe como um decorator pode ser usado, então agora vamos entender as internas desse recurso do Python.

Um decorator é implementado como uma função que recebe uma função como parâmetro, faz algo, então executa a função-parâmetro e retorna o resultado desta. O algo é a funcionalidade que adicionamos a nossa função original através do decorator.

Vamos escrever um decorator que sirva para escrever na tela o nome da função a ser executada, antes da execução da mesma. Como descrito acima, precisamos definir uma função que receba outra função como parâmetro, imprima o nome dessa, execute a função e retorne o seu resultado. Veja o código:

def echo_funcname(func):

    def finterna(*args, **kwargs):
        print "Chamando funcao: %s()"  % (func.__name__)
        return func(*args, **kwargs)

    return finterna

@echo_funcname
def dobro(x):
    return x*2

dobro(10)

Antes de mais nada, observe atentamente a função echo_funcname, pois existem alguns conceitos importantes dentro dela.

def echo_funcname(func):

    def finterna(*args, **kwargs):
        print "Chamando funcao: %s()"  % (func.__name__)
        return func(*args, **kwargs)

    return finterna

Veja que ela receba um parâmetro func (que espera-se que seja uma função) e retorna outra função (finterna). A função retornada, finterna, é “configurada” para executar ao seu final a função recebida como argumento pela função externa (echo_funcname), bem como retornar o valor de retorno da função recebida. Em outras palavras, echo_funcname() cria dentro de si próprio uma função chamada finterna(), que no final (linha 5) chama a função recebida como parâmetro. Mas, é importante perceber que a palavra-chave def somente cria a função (isto é, instancia um objeto do tipo função), não executando ela. Ou seja, echo_funcname cria uma função, configura ela para executar func() ao seu final, não a executa, mas sim somente retorna o objeto função, que então poderá ser chamada por quem recebê-la. (um assunto muito importante para o entendimento desse conceito de função dentro de função é o conceito de closures).

Caso tenha ficado confuso, perceba que finterna é um objeto como qualquer outro que estamos acostumados a criar dentro de nossas funções, como uma lista, por exemplo. A diferença é que esse objeto é uma função, o que pode parecer um pouco estranho, em um primeiro momento. Sendo um objeto qualquer, a função é instanciada, recebe um nome (finterna), e pode ser retornada, assim como todo objeto (tudo isso sem ser executada, pois não chamamos finterna).

Veja um exemplo de visualização de uma função que define outra função internamente (visualização gerada pelo excepcional pythontutor.com):

func

Se quiser visualizar a versão interativa, clique aqui (powered by PythonTutor.com).

Tendo a função echo_funcname() definida, agora poderíamos fazer o seguinte:

def echo_funcname(func):

    def finterna(*args, **kwargs):
        print "Chamando funcao: %s()"  % (func.__name__)
        return func(*args, **kwargs)

    return finterna

def dobro(x):
    """ Uma funcao exemplo qualquer.
    """
    return 2*x

dobro_com_print = echo_funcname(dobro)
print dobro_com_print(10)

Ao executar o código acima, teremos como resposta na tela:

Chamando funcao: dobro()
20

Criamos uma função chamada dobro(), que recebe um número e retorna o dobro desse número. Depois, passamos esse objeto do tipo function para a função echo_funcname() e recebemos como retorno outro objeto do tipo function, ao qual referenciamos como dobro_com_print. Perceba que dobro_com_print nada mais é do que uma referência a uma função mais ou menos assim:

def finterna(*args, **kwargs):
    print "Chamando funcao: %s()"  % (dobro.__name__)
    return dobro(*args, **kwargs)

Essa função foi gerada dentro de echo_funcname() e retornada, já com dobro no lugar de func. Assim, quando chamamos a função como em print dobro_com_print(10), estamos chamando a função acima, e passando 10 como argumento.

Mas, esse negócio todo de passar uma função como parâmetro e receber uma função como retorno de uma chamada de função é um pouco confuso. Para abstrair um pouco esses detalhes, Python oferece a sintaxe do @nome_do_decorator que precede a definição de funções. Assim, ao invés de:

dobro_com_print = echo_funcname(dobro)
print dobro_com_print(10)

Poderíamos apenas preceder a definição da função dobro() com o decorator @echo_funcname:

@echo_funcname
def dobro(x):
    """ Uma funcao exemplo qualquer.
    """
    return 2*x

Agora, ao chamar a função dobro(), estaríamos chamando a função decorada (isto é, acrescida de funcionalidades). No nosso caso, o decorator apenas adiciona a impressão na tela de um aviso sobre a chamada da função.

Enfim, um decorator nada mais é do que uma função que recebe outra função como parâmetro, gera uma nova função que adiciona algumas funcionalidades à função original e a retorna essa nova função.

Concluindo …

Os decorators formam um recurso muito importante para diminuir a duplicação e aumentar o reuso de código em um projeto. O conceito pode ser um pouquinho complicado para entender de primeira, mas uma vez que você o domine, você começará a perceber diversas oportunidades para implementar e usar decorators em seus projetos.

Leia mais

Por se tratar de um assunto mais complicado para iniciantes, segue aqui uma lista de textos que poderiam ser lidos, possibilitando um melhor entendimento sobre o assunto.

Funções como objetos:

Closures: