Um blog com Google App Engine

Sumário

Este é um post-tutorial, onde vou descrever o desenvolvimento de um sistema de blogging bem simples usando os recursos nativos do Google App Engine (GAE, para os amigos). Caso não tenha familiaridade com o GAE, sugiro a leitura dos textos anteriores que publiquei aqui no Python Help sobre o assunto:

Com as leituras acima, você terá a base necessária para entender o tutorial aqui apresentado.

O que vamos desenvolver?

O objetivo é implementar um sistema de blogging bem simples, com as seguintes funcionalidades:

  • Tela principal com listagem de todos os posts
  • Tela para adicionar um novo post
  • Tela para visualizar um post
  • Restrição de acesso para a tela de criar post

A tela para adicionar posts deve redirecionar para o post recém criado.

Mãos à obra

A primeira coisa que temos que fazer é criar o projeto, ou seja, definir a estrutura de arquivos que nosso projeto vai ter e configurá-lo no arquivo app.yaml, como já vimos nos posts anteriores. Veja abaixo a estrutura de diretórios que vamos utilizar em nosso projeto:

├── app.yaml
├── main.py
├── models.py
├── static
│   └── main.css
└── templates
    ├── base.html
    ├── detail.html
    ├── form.html
    └── front.html

Crie o arquivo de configurações app.yaml com as seguintes opções:

application: pythonhelp-blog
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /static
  static_dir: static

- url: /.*
  script: main.app

Como você deve lembrar, a diretiva handlers no arquivo de configuração app.yaml configura como nossa aplicação vai tratar as requisições HTTP vindas do cliente. Aqui configuramos nossa aplicação para servir arquivos estáticos a partir do diretório static e para publicar a aplicação criada na variável app dentro do módulo main.

Ah, não se esqueça de alterar o seu application id (valor de application no app.yaml), pois esse identificador deve ser único no GAE. Ou seja, se eu colocar esse sisteminha no ar com o app id "pythonhelp-blog", você não poderá colocá-lo no ar com o mesmo identificador.

O modelo de dados

Agora, vamos modelar o nosso problema. Como iremos representar as informações no nosso banco de dados? De acordo com os requisitos, iremos precisar de um model para representar um post, que tenha vinculado a si alguns atributos, incluindo o autor do texto (que deve ser um usuário cadastrado).

Graças ao GAE, não precisaremos criar uma entidade User, pois ele já fornece um serviço de gerenciamento e autenticação de usuários (usando contas de usuário na google mesmo). Dessa forma, teremos apenas um model de uma entidade que iremos chamar de BlogPost, contendo um atributo do tipo UserProperty, fornecido pelo GAE. Crie um model BlogPost de acordo com o código a seguir e salve no arquivo models.py:

from google.appengine.ext import db

class BlogPost(db.Model):
    subject = db.StringProperty(required=True)
    author = db.UserProperty(required=True,
                             auto_current_user=True)
    content = db.TextProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)
    last_updated = db.DateTimeProperty(auto_now=True)

Observe  o campo author. Passamos um atributo nomeado auto_current_user com o valor True. Isso faz com que o campo author seja setado com o usuário atualmente logado quando um objeto do tipo BlogPost for gravado no datastore. Barbadinha, né?

Implementação das ações

Agora vamos definir as URLs que serão utilizadas e para quais classes elas serão mapeadas em nosso projeto.

Defina o mapeamento das URLs para as classes manipuladoras no arquivo main.py:

    app = webapp2.WSGIApplication(
        [("/blog", BlogMainHandler),
         ("/blog/newpost", BlogNewPostHandler),
         ('/blog/post/(\d+)', BlogPostViewHandler),
         ],
        debug=True)

Dessa forma, nosso esquema de URLs será assim:

  • a página principal (que lista todos os posts), será acessível pela URL "/blog";
  • o formulário para criação de um novo post será acessível via "/blog/newpost";
  • um post em específico será acessível pela URL "/blog/post/id_do_post". Por exemplo, "/blog/post/19" irá abrir uma página de visualização do post cujo id seja 19.

Agora vamos à implementação das classes listadas acima. A primeira delas será a mais simples: BlogMainHandler, que será a página inicial do blog, listando todos os posts.

template_dir = os.path.join(os.path.dirname(__file__),
                            'templates')


class BlogMainHandler(webapp2.RequestHandler):
    def get(self):
        self.response.out.write(
            template.render(template_dir +
                            '/front.html',
                            {'posts': BlogPost.all()}))

A segunda classe que iremos implementar é BlogPostViewHandler, responsável pela visualização de um post em específico.

class BlogPostViewHandler(webapp2.RequestHandler):
  def get(self, post_id):
    try:
      post = BlogPost.get_by_id(int(post_id))
      self.response.out.write(
          template.render(template_dir + '/detail.html',
                          {'post': post}))
    except:
      self.response.out.write("Erro: post não encontrado!")

Veja que o método get recebe um parâmetro post_id. Esse parâmetro é passado pelo URL dispatcher, pegando o pedaço da URL que está entre parênteses na definição ('/blog/post/(\d+)') e repassando como valor ao método (\d+ significa um ou mais dígitos e os parênteses ao redor fazem com que esse valor seja passado ao método get). Por exemplo, um acesso a /blog/post/19 irá fornecer ao parâmetro post_id o valor 19.

Você deve ser capaz de entender os códigos listados acima. Caso tenha dificuldades, reveja os posts em que explico como criar um projeto na GAE, como utilizar o Datastore e o mecanismo de templates.

Depois temos a classe responsável pela criação de novos posts: BlogNewPostHandler. Veja o código abaixo:


class BlogNewPostHandler(webapp2.RequestHandler):

  def get(self):
    user = users.get_current_user()
    if user is not None:
      self.response.out.write(
          template.render(template_dir + '/form.html',
                          {'title': 'Escrever post'}))
    else:
      self.redirect(
          users.create_login_url(self.request.uri))

  def post(self):
    author = users.get_current_user()
    if author is not None:
      subject = self.request.get('subject')
      content = self.request.get('content')
      if subject and content:
        b = BlogPost(subject=subject, content=content)
        b.put()
        self.redirect("/blog/post/" + str(b.key().id()))
      else:
        error = 'Ambos os campos são obrigatórios'
        self.response.out.write(
            template.render(template_dir + '/form.html',
                            {'title': 'Escrever post', 
                             'error': error,
                             'subject': subject,
                             'content': content}))
    else:
      self.redirect(
          users.create_login_url(self.request.uri))

Essa classe tem alguns detalhes que valem a pena ser observados. Em primeiro lugar, ela possui dois métodos: get e post. Como já vimos anteriormente, o método get será chamado pelo ambiente subjacente quando houver uma requisição do tipo GET à URL mapeada para a classe BlogNewPostHandler e o método post será chamado quando houver uma requisição do tipo POST (normalmente usada para fazer escrita de dados no servidor). Dessa forma, a classe possui duas funcionalidades:

  1. get: ao acessar a url /blog/newpost, o usuário irá receber como resposta um formulário para criação de um novo post.
  2. post: quando o formulário for submetido, é feito uma requisição POST, passando os dados do formulário para criar um objeto BlogPost no servidor.

Repare como é simples a autenticação do usuário no código usando o serviço provido pelo GAE. Veja que a primeira linha de cada método chama users.get_current_user para obter o usuário logado atualmente. Caso não haja usuário logado (se o objeto retornado for None), então redirecionamos o usuário para uma URL própria para fazer o login. Quando estiver rodando no ambiente externo, é apresentada a tela de autenticação dos serviços da Google mesmo. No ambiente de testes, é apresentada uma tela de login como a mostrada na figura abaixo.

Tela de login no ambiente local (powered by GAE)

Tela de login no ambiente local (powered by GAE)

Os templates

Em um projeto GAE, os templates contém o código HTML que será enviado para o usuário nas requisições. Como visto no post anterior sobre o assunto, é possível a introdução de campos dinâmicos para serem preenchidos de acordo com os parâmetros passados na renderização dos templates.

Como todas as páginas terão um cabeçalho padrão, definimos um template base que os demais irão estender. Veja abaixo o código de cada um dos templates:

templates/base.html:

<!DOCTYPE html>
<html>
  <head>
    <link type="text/css" rel="stylesheet" href="/static/main.css" />
    <title>{{ title }}</title>
  </head>
  <body>
    <div class="main-title"><a href="/blog">stummjr Blog</a></div>
    {% block content %}
    {% endblock %}
  </body>
</html>

Dessa forma, basta aos subtemplates detail.html, form.html e front.html a descrição do bloco content.

templates/detail.html (usado para visualização de um post específico):

{% extends 'base.html' %}

{% block content %}
<div class="post">
  <div class="post-heading">
    <div class="post-title">
      {{ post.subject }}
    </div>
    <div class="post-date">
      {{ post.created}}
    </div>
  </div>
  <pre class="post-content">
    {{ post.content }}
  </pre>
</div>
{% endblock %}

templates/form.html (usado para apresentar a interface de criação de um novo post):

{% extends 'base.html' %}
{% block content %}
<form method="post">
  <label for="subject">Assunto:
    <input type="text" name="subject" value="{{ subject }}">
  </label>
  <label for="content">Conteúdo:
    <textarea name="content" rows="10" cols="50">
      {{ content }}
    </textarea>
  </label>
  <button type="submit">Salvar</button>
  <div class="error">
    {{ error }}
  </div>
</form>
{% endblock %}

templates/front.html (lista todos os posts existentes no banco de dados do blog):

{% extends 'base.html' %}

{% block content %}
{% for post in posts %}
  <div class="post">
    <div class="post-heading">
      <div class="post-title">
        <a href="/blog/post/{{ post.key.id }}">{{ post.subject }}</a>
      </div>
      <div class="post-date">
        {{ post.created }}
      </div>
    </div>
    <pre class="post-content">
      {{ post.content }}
    </pre>
  </div>
{% endfor %}
{% endblock %}

Como podemos ver, o código base.html referencia o arquivo de estilos main.css, que não será listado aqui devido ao seu tamanho. Todos os arquivos do projeto podem ser baixados no link que consta no final do post.

Testando o blog

Tendo as classes e os templates definidos, agora basta testar nosso projeto. Para isso, vamos usar o ambiente de testes fornecido pelo GAE. Veja como instalar e usar esse ambiente no primeiro post sobre o GAE feito no blog.

Próximos passos

Não pare por aqui. Leia a documentação oficial do GAE (em pt-br, inclusive) e incremente o projeto, adicionando comentários, categorias, etc. Bom trabalho!

Download dos arquivos do projeto

Obrigado ao Elias Dorneles, pela baita revisão que fez no texto!

Dicas de produtividade no IPython

O IPython é o meu shell Python favorito. É cheio de recursos que facilitam o dia-a-dia de quem passa parte de sua vida imerso em um shell Python. Neste post, vou listar alguns recursos que me ajudam bastante diariamente.

Recuperando o resultado da última operação

É coisa bem comum estar em uma sessão no shell Python, testar uma determinada operação e logo depois perceber que queria atribuir o resultado daquela operação para alguma variável. No IPython é barbada, com o _:

In [5]: 10 * 2 + 4 * 4
Out[5]: 36
In [6]: print _
36
In [7]: x = _
In [8]: print x
36

Ou seja, o resultado da execução do último comando é sempre referenciado pelo _ (underscore). Além disso, dois underscores referem-se sempre ao resultado obtido pela execução do penúltimo comando e três underscores ao resultado do antepenúltimo comando. Assim:

  • _: resultado do último comando.
  • __: resultado do penúltimo comando.
  • ___: resultado do antepenúltimo comando.

Isso é particularmente útl quando estamos imersos em uma sessão de descoberta usando o IPython. Torna o processo muito mais ágil. Veja:

In [18]: 1
Out[18]: 1
In [19]: 2
Out[19]: 2
In [20]: 3
Out[20]: 3
In [21]: _ + __ + ___
Out[21]: 6

Além disso, podemos nos referir à execuções específicas, usando a numeração que o IPython usa para diferenciar um par entrada-saída de outro. A sintaxe é bem simples: _ix, onde x é o número da entrada correspondente. Veja:

In [18]: 1
Out[18]: 1
In [19]: 2
Out[19]: 2
In [20]: 3
Out[20]: 3
In [21]: _ + __ + ___
Out[21]: 6
In [22]: print _i19 + 20
Out[22]: 22

Cool, huh?

Chamando o help de um objeto

Já falei sobre isso em um post anterior, mas veja de novo o uso do ponto de interrogação (?) para ver a documentação relacionada a determinado objeto:

In [31]: import math
In [32]: math.sqrt?
Type: builtin_function_or_method
String Form:<built-in function sqrt>
Docstring:
sqrt(x)
Return the square root of x.

Isso por si só já me faz usar o IPython ao invés do shell padrão.

As funções mágicas

O IPython é repleto de funções mágicas (magic functions) que fornecem enormes facilidades pro usuário. Elas são precedidas pelo caractere %. Vamos ver alguns exemplos.

Em um post anterior, falei sobre o módulo timeit que é usado para medir tempo de execução de programas Python. Existe uma função mágica pra quebrar esse galho pra gente. Por exemplo, se eu estiver na dúvida sobre qual trecho de código executaria de forma mais rápida, poderia fazer o seguinte:

In [35]: %%timeit sum = 0
 ...: for i in range(0, 10000):
 ...: sum += i
 ...: 
1000 loops, best of 3: 324 us per loop
In [36]: %%timeit sum = 0
 ...: for i in xrange(0, 10000):
 ...: sum += i
 ...: 
1000 loops, best of 3: 268 us per loop

Se o código a ser testado for de uma linha só, podemos usar %timeit (modo linha) ao invés de %%timeit (modo célula).

Outra função interessante é relacionada ao logging da sessão atual. %logstart faz com que a sessão atual passe a ser gravada em um arquivo .py com os comandos digitados dentro dele. %logon e %logoff servem para pausar e recomeçar o logging em uma sessão.

Executando comandos no sistema

Dentro de uma sessão IPython, podemos invocar comandos no sistema operacional usando o caractere !:

In[1]: !ls
 bin
 Desktop
 music
 pictures
 src
 videos
In[2]: !ps
 PID TTY TIME CMD
 25277 pts/0 00:00:00 bash
 25446 pts/0 00:00:00 ipython
 25458 pts/0 00:00:00 sh
 25459 pts/0 00:00:00 ps

Assim fica fácil interagir com o SO quando necessário for.

É isso. Se você tiver alguma outra dica interessante sobre o IPython, poste nos comentários.

Atributos em objetos do tipo function

Em um post anterior, nós vimos porque se diz que “tudo em Python é objeto”. Agora, vamos ver uma consequência interessante desse modelo.

Você já deve saber que o comando def é o comando de criação de um objeto do tipo function em Python. Ao interpretar o código abaixo

def funcao_qualquer():
    a = 0
    return a

o interpretador Python cria em memória um objeto chamado funcao_qualquer do tipo function. Isso ocorre da mesma maneira que o código abaixo cria um objeto do tipo int chamado x:

x = 42

A diferença entre o objeto function e o objeto int é que o primeiro é um objeto chamável (callable) e o segundo não. Dessa forma, o objeto function contém código executável dentro de si próprio. Vamos dar uma olhadinha no que temos dentro desse objeto:

>>> dir(funcao_qualquer)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

Sabendo dos campos e métodos disponíveis em objetos function, poderíamos fazer o seguinte (só para exemplificar):

>>> print funcao_qualquer.func_code.co_varnames
('a', )

Mas, mais do que acessar os atributos já existentes em um objeto function, nós também poderemos adicionar atributos a esses objetos. Por exemplo, se quisermos que a função que criamos (o objeto function) possua um contador indicando quantas vezes já foi chamada desde que foi criada, poderíamos defini-la da seguinte forma:

def minha_funcao():
    minha_funcao.contador_de_chamadas += 1
    # faça algo
    return 42
minha_funcao.contador_de_chamadas = 0

Assim, não precisamos criar variáveis globais para representar a contagem, e nem poluir a interface da função com parâmetros desnecessários. Cada vez que minha_funcao for chamada, o atributo interno contador_de_chamadas desse objeto será incrementado. Veja:

for i in range(0, 100):
    minha_funcao()
print minha_funcao.contador_de_chamadas  # imprime 100

Podemos até mesmo “pendurar” uma função como atributo de outra função:

>>> def f(x): return x*x
>>> minha_funcao.f = f
>>> minha_funcao.f(10)
100

Ou, usando uma função anônima:

>>> def f(): return 1
>>> minha_funcao.f = lambda x: x*x
>>> minha_funcao.f(9)
81

Dessa forma, podemos manipular nossas funções, isto é, nossas variáveis do tipo função, da mesma maneira que manipulamos variáveis do tipo lista, dicionário, string, etc. Isso porque elas realmente se tratam de um objeto como qualquer outro.

E lembre-se: com grandes poderes, vêm grandes responsabilidades. 🙂

Obrigado ao @eliasdorneles pela revisão.

Pegadinha com funções e variáveis globais

Diga aí, o que você acha que o código abaixo irá imprimir na tela?

def func():
    print x

x = 42
func()

A resposta é óbvia: 42. OK, sem pegadinhas por enquanto. E o código abaixo, o que irá imprimir?

def func():
    print x
    x = 1

x = 42
func()

Pelo que sabemos até então, somos levados a crer que o código acima irá imprimir 42 na tela, correto? Mas, veja a saída que recebemos na cabeça ao executar o código:

Traceback (most recent call last):
File "<pyshell#3>", line 6, in 
    func()
File "<pyshell#3>", line 2, in func
    print x
UnboundLocalError: local variable 'x' referenced before assignment

wat

UnboundLocalError significa que estamos acessando uma variável antes dela ter sido criada no escopo da função func(). A linha que está gerando o erro é a linha 2, que contém print x. Entretanto, observe com cuidado os dois exemplos de código acima. No primeiro deles também estamos fazendo print x na primeira linha da função. Inclusive, o valor impresso pela função foi o valor da variável x definida no escopo global.

A culpada por esse erro é a linha 3 (x = 1) e a explicação é simples: o código da função é analisado e x é definido estaticamente pelo compilador como uma variável (ou melhor, como um nome) local ao escopo da função quando este encontra a atribuição x = 1. E isso vale para a função inteira, mesmo que a atribuição ocorra somente na última linha dela. Essa análise é feita estaticamente durante a criação da função func, quando o compilador encontra a declaração de criação de função def. Assim, mais tarde quando func() é chamada, print x tenta imprimir o valor de uma variável local antes dela ter algum valor atribuído a si.

Existe uma regra bem simples:

Sempre que o compilador encontra uma atribuição a um nome dentro de uma função, ele assume que tal nome é local em toda a função, não importando onde a atribuição ocorre.

O código a seguir funciona

def func():
    x = 1
    print x

x = 42
func()

mas imprime 1, pois ao encontrar x = 1, o compilador assume que x é uma variável local. Mas e se precisarmos alterar o valor do x global dentro da função? Antes de qualquer coisa, vamos ver se o x global (de valor 42, inicialmente) é alterado:


def func():
    x = 1
    print x

x = 42
func()
print x

O programa acima irá imprimir:

1
42

Ou seja, o x global não foi alterado. Se quiser mesmo alterar o valor de uma variável global dentro de uma função, Python exige que identifiquemos a mesma como global dentro da função. Isso pode ser feito com a estranhíssima construção global:


def func():
    global x
    x = 1
    print x

x = 42
func()
print x

O código acima imprime:

1
1

Ou seja, a global x foi alterada pela atribuição feita dentro de func().

ALERTA: criar variáveis globais e alterar o valor delas dentro de funções é uma péssima prática.  O código fica mais difícil de ler e modificar (você precisa saber todos os lugares onde a variável global está sendo usada) e pode levar a bugs bem difíceis de serem encontrados. Não é por menos que Python força o programador a explicitar a referência a uma variável global quando quiser alterá-la fora do escopo global. Soa quase como uma autodeclaração do programador: “sim, eu sou tosco e estou prestes a fazer mer** no meu código”. 😀

O que mudou no Python 3?

Uma das primeiras coisas que alguém que vai testar o Python 3.x ao invés do Python 2.x percebe é a mudança do print(), que deixou de ser um comando e passou a ser uma função. O primeiro impacto disso é que o tradicional “Hello, world!” muda. O “Hello World” com Python 3 fica:

print("Hello, world!")

Muita gente, quando pensa em mudar pro Python 3, já lembra de cara dessa diferença. Mas, existem várias outras mudanças que são bem importantes. Vou listar aqui as que considero que vão ter maior impacto na maioria dos programadores.

Operador de divisão inteira

A partir do Python 3, 3/2 resulta em 1.5, e não mais em 1. Ou seja, o operador / não é mais dependente dos tipos dos operados. No Python 2.x, o resultado de uma divisão de dois números inteiros era também um número inteiro, arredondado para baixo, sempre.

O operador // foi inserido no Python 3 para representar a divisão inteira. Assim, podemos ver alguns exemplos abaixo:

>>> 3 / 2
1.5
>>> 3 // 2
1
>>> -3 // 2
-2

(se você ficou confuso com o resultado da última expressão, clique aqui e entenda o porquê)

Assim, programas Python 2.x que dependiam do arredondamento do operador /, não irão mais funcionar corretamente na versão 3.x. Fique de olho!

True, False agora são palavras reservadas (finalmente!)

Em Python 2.x, era possível fazermos coisas bizarras como:

>>> True = "Hello"
>>> False = "Hello"
>>> True == False
True

Ou então, tão estranho quanto:

>>> False = True
>>> True == False
True

Felizmente, em Python 3 isso não é mais possível. Veja uma tentativa:

>>> True = "Hello"
SyntaxError: assignment to keyword

xrange() se foi

A função xrange() deixou de existir no Python 3. Lembra que, no Python 2, range() retornava uma lista e xrange() retornava um objeto iterável? O impacto disso era que, para grandes sequências numéricas, xrange() acabava sendo mais eficiente do que range(), pois usava muito menos espaço em memória para gerar a sequência (leia mais aqui).

No Python 3, só existe a função range(), que retorna uma espécie de iterável (assim como xrange() fazia).

map, filter e zip também mudaram

As funções map, filter e zip, assim como range(), também retornavam uma lista com os valores de resultado. A partir do Python 3, elas passaram a retornar objetos iteráveis, ao invés de gerar listas enormes em memória. Isso impacta bastante em código Python 2.x, visto que coisas simples deixam de funcionar, como o exemplo abaixo:

>>> len(map(lambda x: x*x, [1, 2, 3]))
TypeError: object of type 'map' has no len()

Isso pode ser corrigido com uma “conversão” do iterável retornado pelo map() para lista:

>>> len(list(map(lambda x: x*x, [1,2,3])))
3

E o mesmo vale para as funções zip e filter também. Ah, outra mudança forte é que a função reduce() foi “rebaixada” para o módulo functools, deixando de ser uma função global do Python.

has_key não existe mais nos dicionários

Em Python 2.x, era comum verificar se um determinado elemento já era chave em um dicionário usando o método has_key():

>>> d = {'nome': 'jose', 'idade': 18}
>>> d.has_key('nome')
True
>>> d.has_key('email')
False

Em Python 3.x, para fazer a mesma verificação, usamos o operador in:

>>> 'nome' in d
True
>>> 'email' in d
False

Ainda falando sobre dicionários, os métodos que em Python 2.x retornavam listas, agora retornam espécies de iteráveis (na verdade, são views dinâmicas, sobre as quais pretendo falar em um próximo post).

Toda string é unicode

Em Python 3.x, só existe um tipo de String: as strings unicode. Assim, não é mais preciso especificar em uma string literal que ela é do tipo unicode. Em Python 2.x, um literal unicode era declarado como:

>>> string_unicode = u"olá mundo"

(repare no ‘u’ precedendo o literal)

Em Python 3.x, isso não é preciso, pois toda string é unicode:

>>> string_unicode = "olá mundo"

Agora só existe input()

Em Python 2.x, existiam duas funções para fazer a leitura de valores do teclado: input() e raw_input(). A primeira lia expressões Python e as executava e a segunda lia strings.

Em Python 3.x, só existe uma função: input(). Ela lê uma string do teclado, que então pode ser convertida para o tipo apropriado. Por exemplo, para ler um valor numérico inteiro:

>>> idade = int(input('Digite sua idade:'))
Digite sua idade:10
>>> type(idade)
int

Por fim…

Além das modificações apresentadas acima, foram feitas inúmeras outras, principalmente na reestruturação de bibliotecas, deixando-as com interfaces mais consistentes. Até agora, achei muito interessantes as alterações feitas da versão 2 para a 3, pois elas deixaram tudo mais Pythônico e consistente.

Para portar aquele seu programa escrito usando Python 2 para Python 3, foi criada uma ferramenta bem interessante chamada de 2to3, que pega seu código legado e o transforma em código compatível com a versão 3 da linguagem. É claro que ela não faz milagre e, na maioria dos casos, é preciso intervenção manual. Mas já é uma ajuda para códigos mais simples.

Por exemplo, o programa hello.py:

print "hello, world!"

Pode ser convertido usando o 2to3:

$ 2to3 -w hello.py

O resultado é o programa hello.py, agora pronto pra versão 3:

print("hello, world!")

😀

Leia mais

Introdução ao memcache

O problema

Imagine um portal como o globo.com, com suas inúmeras chamadas para matérias que constam já na página principal.

Apelação milenar

Apelação milenar

Cada imagem, título e descrição de reportagem ou comercial que aparecem na página são informações dinâmicas dela. A cada vez que acessamos a página, podemos obter notícias mais recentes, resultando em uma página diferente. Assim, imagina-se que os dados apresentados na página estejam armazenados em um banco de dados e que, a cada acesso de usuário, temos várias consultas sendo realizadas pelo servidor ao banco de dados. Isso seria o mais natural, considerando-se a alta taxa de criação de novas notícias no site. Porém, você já deve saber que o disco é o grande gargalo dos computadores modernos e que acessos a disco são o grande vilão da performance da maioria dos programas de computador. Então, como fazer um sistema web com foco no conteúdo dinâmico sem realizar frequentes acessos ao BD (e consequentemente, ao disco)?

Uma possível solução

Uma solução pode ser fazer o caching de alguns dados na memória. Em um portal de notícias, os usuários que acessarem a página mais ou menos no mesmo horário vão receber o mesmo conteúdo, que são as últimas notícias postadas. Então por que repetir as consultas ao banco de dados para todo e qualquer acesso de usuário ao portal? Seria mais inteligente se o nosso sistema consultasse o banco de dados somente uma vez, armazenasse os resultados da consulta em memória (na nossa cache) e então passasse a responder às requisições subsequentes usando o conteúdo armazenado em memória sem precisar buscar nada no disco até que o conteúdo mude novamente. Assim, as requisições podem ser respondidas com o conteúdo da memória. Quando o conteúdo do BD sofrer alguma alteração, a cache pode ser invalidada e atualizada com o novo conteúdo.

Um exemplo

Vamos seguir com o foco em um portal de notícias, já que existem muitos casos de uso similares. Como já vimos em outro post, o Google App Engine suporta Python; e o mecanismo de datastore que ele oferece é legal pra caramba. Suponhamos que nesse portal existe um modelo de dados que contenha uma entidade Noticia, conforme representado abaixo:

class Noticia(db.Model):
    titulo = db.StringProperty(required=True)
    conteudo = db.TextProperty(required=True)
    url = db.LinkProperty(required=True)
    autor = db.UserProperty(required=True)
    thumbnail = db.LinkProperty()
    data_publicacao = db.DateTimeProperty()

Como todo bom portal de notícias, esse também vai ter aquela página inicial carregadíssima, cheia de notícias e outros elementos. Haja barramento pra aguentar tantos acessos ao disco!

O código a seguir poderia ser a view que gera a sua página principal:

    class PostHandler(webapp2.RequestHandler):

        def get(self):
            ultimas = get_noticias(100, '-data_publicacao')
            categorias = get_categorias()
            comerciais = get_comerciais_ativos()
            for n in ultimas:
                # lista e formata as noticias, categorias, comerciais
                ...

    def get_noticias(limit, order_by):
        return Noticia.all().order(order_by).fetch(limit)

    def get_categorias():
        return Categoria.all()

    def get_comerciais_ativos():
        return Comercial.all().filter('ativa = ', True)

Beleza, funciona. O problema começa quando o site começa a ficar popular e os usuários começam a reclamar de lentidão e de indisponibilidade constante. O que fazer? Bom, a resposta certa é fazer profiling da aplicação, isto é, medir o tempo que estão levando as operações necessárias para carregar a página — tanto no lado cliente quanto no lado servidor — e então você poderá decidir melhor como resolver o problema. Mas quando um site está realizando várias consultas ao banco para cada acesso do usuário, frequentemente uma solução é evitar a realização dessas consultas usando uma cache.

Let’s cache them all!

mecanismo de cache que vou apresentar aqui é o memcache específico para o Google AppEngine, embora existam várias implementações do memcache para as mais variadas plataformas e linguagens. O memcache é um mecanismo que permite o armazenamento de pares chave-valor (assim como um dicionário, ou uma tabela hash) em memória, de forma que o acesso aos valores ocorre de forma bem eficiente.

Agora, vamos modificar o método get_noticias() para fazer caching do conteúdo da página principal. O princípio de funcionamento é bem simples: antes de qualquer consulta ao banco, verifique se os dados que queremos estão presentes na cache. Veja o código:

from google.appengine.api import memcache

def get_noticias(limit, order_by):
    # busca na cache por um valor de chave 'noticias'
    ultimas = memcache.get('noticias')
    if ultimas is None: # valor ainda não está em cache
        # busca no BD
        ultimas = Noticia.all().order(order_by).fetch(limit)
        # e inclui o resultado na cache para os futuros acessos
        if not memcache.add('noticias', ultimas):
            logging.error('Erro ao setar memcache.')

O que foi feito acima para os dados das notícias pode ser feito também para os comerciais e para as categorias. Assim, o primeiro de todos os acessos ao site pode demorar um pouquinho mais, mas os acessos seguintes de todos os usuários vão ser muito rápidos.

Cuidado com a Sincronização

Uma vez que começamos a usar a cache, nós passamos a ter informações redundantes (BD e cache). Dessa forma, basta que alguém insira uma notícia nova no BD para que tenhamos os dados fora de sincronia. Uma alternativa para solucionar esse problema pode ser: logo após salvar uma nova notícia no BD, atualizar os valores da cache. Se isso for algo demorado, poderíamos iniciar uma tarefa em background para fazer isso.

Quando usar cache?

Como já comentei anteriormente, a cache não é a solução para todo e qualquer problema de um sistema web. O problema pode estar no plano de hospedagem, no excesso de arquivos estáticos, em lógica duplicada resultante de um mau hábito de copiar-e-colar, em algoritmos pouco otimizados, etc.

A cache é um mecanismo bem simples de ser implementado, em alguns casos, mas isso não quer dizer que você já deve sair de cara utilizando cache em todo e qualquer projeto a partir de agora. Afinal, premature optimization is the root of all evil. 😛

Para finalizar…

No curso Web Development, ministrado pelo grande Steve Huffman no Udacity.com, aprendi o seguinte:

Um simples acesso de usuário a uma página jamais deveria gerar um acesso ao banco de dados.

Ou seja, se um usuário qualquer quiser apenas visualizar sua página, blog, ou portal, esse acesso não deveria exigir do sistema um acesso ao banco de dados. É um princípio bem simples e que pode evitar que o seu site caia quando se tornar mega-popular. 🙂

Grandes sites como Twitter, Facebook e Reddit usam largamente os mecanismos de cache, de forma que seja possível responder aos tsunamis de requisições que eles recebem a cada segundo.

Leia mais sobre o memcache na Wikipedia.

 

Obrigado ao Elias pela revisão!

Serialização de Objetos em Python

Sumário

Vez por outra precisamos enviar dados via rede, seja através de uma já tradicional conexão HTTP, ou até mesmo através de um socket UDP cruzão, e é bem comum que esses dados estejam representados em nosso programa através de uma instância de uma classe por nós mesmos definida. No entanto, na hora de enviar esse objeto pela rede, é preciso que tenhamos esses dados representados de forma contínua (diferentemente de simples referências a posições de memória) e, muitas vezes, de uma forma que possa ser lida por um sistema diferente do sistema no qual o objeto foi criado. Para atender a esses requisitos, é necessária a serialização de dados, que trata da representação de objetos ou estruturas de dados em um formato que permita que estas sejam armazenado em um disco (ou enviadas pela rede) para posterior recriação do objeto em memória.

Veja mais sobre serialização no artigo da Wikipedia sobre o assunto.

Como serializar?

Em Python, existem diversos mecanismos disponíveis para serialização de dados. A escolha vai depender do tipo de aplicação exigida. Veremos a seguir alguns mecanismos para serialização de objetos:

Pickle

pickle é um módulo que provê a serialização de objetos Python, transformando objetos quaisquer em sequências de bytes. No exemplo a seguir, vamos serializar uma lista:

>>> import pickle
>>> lista = [1, 'hello!', [1, 2, 3]]
>>> s = pickle.dumps(lista)
>>> print s
"(lp0\nI1\naS'hello!'\np1\na(lp2\nI1\naI2\naI3\naa."
>>> type(s)
<type 'str'>

O método dumps() é o responsável por pegar os dados do objeto em questão e gerar uma sequência de bytes capaz de representar tais dados, de forma que estes possam ser transmitidos pela rede, armazenados em um arquivo, e depois, recuperados para o seu formato original (em nosso caso, um objeto list).

>>> print s
"(lp0\nI1\naS'hello!'\np1\na(lp2\nI1\naI2\naI3\naa."
>>> lista_recuperada = pickle.loads(s)
>>> print lista_recuperada
[1, 'hello!', [1, 2, 3]]
>>> type(lista_recuperada)
<type 'list'>

Já o método loads() é responsável por pegar uma sequência de bytes (representada por uma string) e convertê-la de volta para o objeto Python que originalmente representava (veja o exemplo acima).

O dumps() e o loads() serializam e de-serializam os objetos para strings e a partir de strings, respectivamente. Existem também as versões dos mesmos métodos que lidam com dados serializados que estão armazenados em arquivos. São eles os métodos dump() e load() (sem o s no final do nome).

Para serializar um objeto usando a função dump(), é preciso passar a ela o arquivo no qual queremos que o objeto serializado seja gravado:

>>> pickle.dump(lista, open('data.pkl', 'wb'))

(Repare que passamos uma referência ao arquivo já aberto, não somente o nome do arquivo)

Podemos agora verificar o conteúdo do arquivo data.pkl pelo shell do sistema operacional:

user@host$ cat data.pkl
(lp0
I1
aS'hello!'
p1
a(lp2
I1
aI2
aI3
aa.

Para reconstruir as informações contidas no arquivo em um objeto Python, vamos usar o método load():

>>> recuperada = pickle.load(open('data.pkl'))
>>> print recuperada
[1, 'hello!', [1, 2, 3]]

Barbada, não? Ainda existe também uma implementação do mesmo protocolo no módulo cPickle, que, por ser implementado em C, possui um desempenho muito superior ao do Pickle (de acordo com a documentação, pode ser até 1000 vezes mais rápido). Porém, por não se tratar de código Python, existem algumas restrições nele, como não podermos subclasseá-lo (estendê-lo).

Apesar de ser fácil de utilizar, o pickle serializa os dados em um formato próprio (não-popular com outras linguagens). Sendo assim, o pickle será uma boa opção para serializar objetos para envio/gravação somente para outros programas também escritos em Python.

Serializando objetos customizados

É comum criarmos classes novas em nossos projetos e muitas vezes é necessário serializar instâncias dessas classes. O Pickle pode ser usado para isso também. Veja o exemplo abaixo, onde criamos uma classe Objeto, e em seguida serializamos uma instância dela:

>>> class Objeto(object):
....:
....:    def __init__(self):
....:        self.x = 42
....:        self.s = 'hello, world'
....:        self.l = [1, 2, 3]
>>> o = Objeto()
>>> s = pickle.dumps(o)
>>> print s
ccopy_reg
_reconstructor
p0
(c__main__
Objeto
p1
c__builtin__
object
p2
Ntp3
Rp4
(dp5
S'x'
p6
I42
sS's'
p7
S'hello, world'
p8
sS'l'
p9
(lp10
I1
aI2
aI3
asb.
>>> obj = pickle.loads(s)
>>> print obj
<__main__.Objeto object at 0x1c12790>

Marshal

O módulo marshal tem uma interface bem semelhante ao pickle, com seus métodos load, loads, dump e dumps. Porém, não é recomendado o seu uso para serialização de objetos em aplicações, por não ser garantida a compatibilidade entre versões do interpretador Python (de acordo com a documentação, esse módulo existe para uso interno no interpretador).

Exemplos de uso:

>>> import marshal
>>> print marshal.dumps(lista)
[ishello![iii
>>> print marshal.loads(s)
[1, 'hello!', [1, 2, 3]]

As informações são serializadas para um formato binário. Não vamos nos alongar muito nesse módulo, visto que ele não deve ser usado em aplicações do dia-a-dia.

Struct

struct é um módulo que faz o meio de campo entre objetos Python e estruturas em C. Agora, ao invés dos métodos dump e load, temos pack e unpack.

>>> import struct
>>> p = struct.pack('i5s', 42, 'hello')
>>> p
'*\x00\x00\x00hello'

O seu uso é mais complicado do que o pickle ou o marshall. Vamos rever a chamada à função pack na linha 2 do trecho de código acima:

pack('i5s', 42, 'hello')

O primeiro argumento passado para a função pack deve ser o formato que irá definir como será a estrutura C que irá armazenar os dados dos nossos objetos Python. No exemplo acima, informamos através da string 'i5s' que a estrutura possui 2 campos:

  • um int (representato por i);
  • uma string (char * em C) com 5 posições ('5s');

Para recriar os objetos Python através do dado serializado em forma de struct, vamos usar a função unpack:

>>> num, s = struct.unpack('i5s', p)
>>> print num
42
>>> print s
'hello'
>>> struct.unpack('i5s', p)
(42, 'hello')

Perceba que a função unpack retorna uma tupla contendo os dados que estavam empacotados dentro da estrutura.

Esse formato também não é o melhor para transporte de dados, pois é dependente da arquitetura do computador. Assim sendo, um pacote empacotado em uma máquina poderia ter problemas para ser desempacotado em uma máquina de arquitetura diferente. Além disso, ele só é capaz de empacotar dados dos tipos mais simples, como os tipos numéricos, strings e booleanos.

JSON

O JSON talvez seja hoje o formato para dados intercambiáveis mais utilizado. Esse formato de dados é muito usado em serviços web, e também para o transporte de dados usando outros protocolos. Como ele já foi visto em outros posts (aqui e aqui), não vamos nos aprofundar muito na sua utilização. Vamos ver somente um exemplo simples:

>>> import json
>>> lista = [1, 'hello!', [1, 2, 3]]
>>> s = json.dumps(lista)
>>> s
'[1, "hello!", [1, 2, 3]]'
>>> print type(s)
<type 'str'>
>>> l = json.loads(s)
>>> l
'[1, u'hello!', [1, 2, 3]]'
>>> print type(l)
<type 'list'>

Como já foi visto nos posts anteriormente referidos, JSON pode ser usado para trafegar estuturas de dados mais complexas, em um formato parecido com o de dicionários Python. Assim, esse post aqui mostra praticamente nada da capacidade desse formato (se quiser saber mais, veja os posts anteriores).

Caso você não conheça o JSON, sugiro fortemente que procure documentação sobre ele, pois é um formato muito bom para tráfego de dados entre ambientes heterogêneos.

Shelve

O shelve é um módulo que provê um tipo de dados com uma interface similar a de um dicionário (chamado de shelf), e que agrega a funcionalidade de persistir esse dicionário em um arquivo para uso posterior. Ou seja, o shelve nos provê dicionários persistentes.

Vamos ver um exemplo:

>>> import shelve
>>> user = shelve.open('data.txt')
>>> user['nickname'] = 'stummjr'
>>> user['city'] = 'Blumenau'
>>> user['twitter'] = 'stummjr'
>>> print user
{'city': 'Blumenau', 'twitter': 'stummjr', 'nickname': 'stummjr'}
>>> user.close()

Perceba que a chamada a shelve.open() abre um shelf (se ainda não existir, ele é criado). Depois, podemos manipular o objeto retornado por esta chamada como se fosse um dicionário. Para persistir os dados no arquivo data.txt, é necessário fechar o shelf em questão (user.close()).

Em outro momento, poderíamos recuperar os dados da seguinte forma:

>>> import shelve
>>> user = shelve.open('data.txt')
>>> print user
{'city': 'Blumenau', 'twitter': 'stummjr', 'nickname': 'stummjr'}
>>> user['blog'] = 'pythonhelp.wordpress.com'
>>> user.close()

Legal, né? O shelve nos dá uma forma bem prática de persistir dados. O exemplo acima mostra um caso bem simplificado, mas poderíamos usar um shelf para armazenar dados de várias pessoas, por exemplo:

>>> users = shelve.open('users.dat')
>>> users['stummjr'] = {'nickname': 'stummjr', 'blog': 'pythonhelp.wordpress.com', 'city': 'Blumenau'}
>>> users['eliasdorneles'] = {'nickname': 'eliasdorneles', 'blog': 'eljunior.wordpress.com', 'city': 'Floripa'}
>>> print users
{
    'eliasdorneles': {
        'blog': 'eljunior.wordpress.com',
        'city': 'Floripa',
        'nickname': 'eliasdorneles'
    },
    'stummjr': {
        'blog': 'pythonhelp.wordpress.com',
        'city': 'Blumenau',
        'nickname': 'stummjr'
    }
}

>>> users.close()
>>> users = shelve.open('users.dat')
>>> print users['stummjr']['blog']
pythonhelp.wordpress.com

Então, qual devemos usar?

Antes de mais nada, fique atento às restrições que cada abordagem possui. Por exemplo, dentro das opções apresentadas acima, a única que possui implementação em uma ampla variedade de linguagens é o JSON. Por outro lado, o shelve nos provê essa facilidade de manipular dados em dicionários e persistí-los no disco depois. Tudo irá depender do seu objetivo ao serializar os dados.

Interoperabilidade é importante? Então vá no JSON de olhos fechados. Quer uma forma de serializar dados para uma única plataforma e que seja econômica no tamanho dos dados? Talvez struct seja a sua escolha. Enfim, leia a documentação e descubra qual das alternativas acima melhor se encaixa em suas necessidades.

Dicas para lidar com JSON

Você já deve ter descoberto como funciona o formato JSON — muito usado para trocar informações entre aplicações Web, como já foi mostrado aqui no blog anteriormente. Hoje vamos mostrar algumas dicas para facilitar sua vida quando estiver lidando com esse formato.

1) Use python -mjson.tool para formatar saídas JSON na linha de comando.

Às vezes cai no nosso colo um conteúdo JSON não-formatado, com todo o conteúdo em uma linha só, algo parecido com isso:

{"assunto":"Dicas para lidar com JSON","metadados":{"data":"07/07/2013 14:10","site":"https://pythonhelp.wordpress.com","numero_acessos":3},"conteudo":"Voc\u00ea j\u00e1 deve ter descoberto como funciona o formato JSON -- muito usado para trocar informa\u00e7\u00f5es entre aplica\u00e7\u00f5es Web..."}

Geralmente, trata-se da resposta de uma API, que é “limpado” para economizar alguns bytes e por conseguinte, reduzir o uso de banda do servidor. Até aí tudo bem, o problema é que fica bem mais complicado de ver a estrutura dos dados retornados. Fear not! O módulo json da API padrão de Python contém uma ferramenta para você formatar os resultados diretamente na linha de comando. Se o conteúdo acima estiver dentro de um arquivo com o nome post.json, você pode fazer:

$ python -m json.tool post.json
{
    "assunto": "Dicas para lidar com JSON",
    "conteudo": "Voc\u00ea j\u00e1 deve ter descoberto como funciona o formato JSON -- muito usado para trocar informa\u00e7\u00f5es entre aplica\u00e7\u00f5es Web...",
    "metadados": {
        "data": "07/07/2013 14:10",
        "numero_acessos": 3,
        "site": "https://pythonhelp.wordpress.com"
    }
}

That’s cool, right?

Se você é que nem eu, provavelmente vai querer colocar um alias (apelido ou atalho) no ~/.bashrc, para ficar ainda mais fácil:

$ echo "alias jsonfmt='python -mjson.tool'" >> ~/.bashrc
$ source ~/.bashrc
$ echo "[1, 2, 3]" | jsonfmt
[
    1,
    2,
    3
]
$ curl -s http://api.joind.in | jsonfmt
{
    "events": "http://api.joind.in/v2.1/events",
    "hot-events": "http://api.joind.in/v2.1/events?filter=hot",
    "open-cfps": "http://api.joind.in/v2.1/events?filter=cfp",
    "past-events": "http://api.joind.in/v2.1/events?filter=past",
    "upcoming-events": "http://api.joind.in/v2.1/events?filter=upcoming"
}

2) Estenda json.JSONEncoder para converter objetos em JSON:

Como você já sabe, é fácil converter dicionários Python no formato JSON. Mas e no caso de variáveis de classes que você mesmo definiu?

Observe esse exemplo:

import json, datetime

class BlogPost:
    def __init__(self, titulo):
        self.titulo = titulo
        self.data = datetime.datetime.now()

post = BlogPost('Dicas para lidar com JSON')

Se tentarmos fazer:

print json.dumps(post)

obtemos um erro parecido com:

TypeError: <__main__.BlogPost instance at 0x1370ab8> is not JSON serializable

Isto é porque o método json.dumps não sabe converter objetos do tipo BlogPost em strings no formato JSON. Felizmente, o método json.dumps permite que você informe um “encodificador” JSON alternativo, de forma que você pode customizar a geração do resultado JSON para permitir a conversão de outros objetos:

class BlogPostEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, BlogPost):
            return {'titulo': obj.titulo, 'data': obj.data}
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        return json.JSONEncoder.default(self, obj)

print json.dumps(post, cls=BlogPostEncoder)

Agora sim, funciona:

{"titulo": "Dicas para lidar com JSON", "data": "2013-07-07T16:26:40.636950"}

Minha sugestão é usar um encodificador mais genérico, que permita converter em JSON qualquer objeto que implemente um método to_json, segue um exemplo completo:

import json, datetime

class Site:
    def __init__(self, url):
        self.url = url
    def to_json(self):
        return {"url": self.url}

class BlogPost:
    def __init__(self, titulo, site):
        self.titulo = titulo
        self.data = datetime.datetime.now()
        self.site = site
    def to_json(self):
        return {"titulo": self.titulo, "data": self.data.isoformat(), "site": self.site}

class GenericJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, 'to_json'):
            return obj.to_json()
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        return json.JSONEncoder.default(self, obj)

print json.dumps(Site("http://www.google.com.br"), cls=GenericJsonEncoder)
post = BlogPost('Dicas para lidar com JSON', Site('https://pythonhelp.wordpress.com'))

print json.dumps(post, cls=GenericJsonEncoder)

Note que você não precisa de nada disso se estiver usando o framework Django e tentando serializar uma instância de um modelo. Nesse caso, basta usar o mecanismo de serialização para XML e JSON do próprio Django.

3) Para necessidades mais complexas de serialização, mude a estratégia

Se você precisar converter objetos em JSON e vice-versa para coisas mais complicadas que o nosso exemplo, você pode considerar usar o módulo jsonpickle. Este módulo consegue converter quase qualquer objeto Python em JSON e vice-versa, usando uma representação própria baseada no módulo pickle para serialização de objetos. Essa representação própria acaba tendo algumas desvantagens, porque nem sempre o JSON gerado fica legível ou facilmente tratado no caso de outras linguagens, a portabilidade é garantida praticamente só para outras aplicações Python.

Por isso, se a serialização e deserialização de objetos complicados for um ponto muito importante para o seu projeto, considere usar outros formatos — JSON pode não ser o formato mais adequado. (UPDATE: aqui tem uma visão geral sobre alternativas de serialização em Python).

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.