Dicas para um bom programa em Python

Foto: Olivier H

Oi pessoal, desta vez eu vou pular as ‘desculpas’ por ter demorado tanto para postar aqui no blog e vamos direto ao assunto.

Recentemente eu tenho trabalhado bastante com Python (dã!) desenvolvendo projetos de diversos tipos e resolvi escrever aqui sobre algumas coisas que pratico enquanto desenvolvo.

Esse artigo é uma espécie resumo com boas práticas de programação Python que utilizo no meu dia-a-dia.

Código mais robusto

Deu certo ou errado?

O que você faz quando acontece algo errado na execução do seu método? O que você responde à requisição que lhe foi feita?

Eu tenho visto em muito código por aí os desenvolvedores retornando valores sentinela (None, null, 0, -1, etc.) para avisar que algo incorreto aconteceu na execução do método.

def f(arg):
   if not arg:
      return None
   return [ "resultado", "de", "um", "processamento"  ]

Algumas linguagens de programação não possuem estruturas de tratamento de exceção e, neste caso, o uso de sentinelas é válido. Mas quando a linguagem de programação te disponibiliza essa funcionalidade é bom usá-la.

def f(arg):
   if not arg:
      raise ValueError("Argumento Invalido")
   return [ "resultado", "de", "um", "processamento"  ]

Deixem as exceções fluirem.

Isso mesmo. A menos que você saiba exatamente o que você deve fazer quando uma exceção aparece deixe-a exceção “subir”. Pode ser que “lá em cima” alguém saiba cuidar dela adequadamente.

Quando não fazemos isso estamos ocultando informação importante para os usuários do nosso código (sejam eles usuários, outros desenvolvedores ou nós mesmos).

def f():
   try:
      return conecta()
   except ExcecaoQueDeveriaSerErro:
      return None

Quando eu implemento esse tipo de método/função eu faço assim (na verdade eu não implementaria f() e chamaria conecta()):

def f():
   return conecta()

O que seu método/função retorna?

Código que eu encontrei recentemente:

def get_fulanos():
   q = Q("select * from patuleia where alcunha like 'fulano%'")
   ret = [ str(fulano['nome']) for fulano in q ]
   if len(ret) == 1:
      return ret[0]
   return ret

Perceberam o que está errado? O seu método retorna uma lista de Fulanos ou retorna Fulano?

Isso está conceitualmente errado e pode fazer você perder horas preciosas do seu dia tentando achar um bug causado por esse tipo de código.

Aconteceu comigo. Note que str() implementa uma interface de sequence da mesma forma que list(). Então o erro passa silenciosamente no caso abaixo:

old_fulanos = [ "Ze Ruela", "Ze Mane" ]
old_fulanos.extend(get_fulanos())
print old_fulanos

Rodando esse código você vai obter ['Ze Ruela', 'Ze Mane', 'q', 'u', 'a', 'c', 'k'] sendo que, em mais de 90% dos casos, o que você gostaria de ter seria: ['Ze Ruela', 'Ze Mane', 'quack'].

“Nada” é diferente de “alguma coisa”.

Essa dica é só uma complementação da primeira e da segunda dica.

Quando o seu método/função retorna uma collection (seqüência, conjunto, hash, etc) vazia você deve retorná-la vazia e não um valor sentinela (como None). Isso facilita a vida de quem vai usar o seu método/função:

def vazio():
   return []
 
for elemento in vazio():
   pass #... faz algo se o conjunto contiver dados ...

Se você retorna um valor sentinela:

def vazio():
   return None
 
elementos = vazio()
if elementos:
   for elemento in elementos:
      pass # ...

Notou que tivemos que criar uma variável com o resultado da operação (para não precisar chamá-la duas vezes) e tratar a sentinela com um “if“? Se eu esqueço de tratar a sentinela meu programa vai quebrar.

Lembre-se sempre que uma collection vazia tem valor booleano “False“.

Todo ‘elif‘ tem um irmão ‘else‘.

Sempre que você precisar usar uma construção if/elif coloque uma cláusula ‘else‘.

Além de usar a cláusula ‘else‘ eu geralmente faço com que ela gere uma exceção. Desta forma eu sou obrigado a trabalhar todas as possibilidades nos ‘if/elif‘ evitando ocultar uma situação que pode ser inválida.

class InvalidCommand(Exception):
   pass
 
def minihelp(comando):
   if comando == "print":
      return u"""Imprime dados na tela.
                 Deixará de ser comando no Python 3.0"""
   elif comando == "assert":
      return u"""Certifica se uma condição é
               verdadeira e gera uma excessão em caso contrário"""
   elif comando == "...":
      pass # ...
   else:
      raise InvalidCommand(u"Comando %s inválido." % (comando,))

Eu gosto dessa prática mas isso não significa que você deva seguí-la sempre. Existem situações onde ter um “valor default” é necessário e nestes casos o uso do else sem levantar exceção se faz necessário.

if comando == "if":
   print "Vai usar elif?"
elif comando == "elif":
   print "Muito bem. Agora falta o else"
else:
   print "Pronto. Agora está bom."

“Pythonismos”

Use mais atributos públicos do que atributos protegidos (“_“).

Programadores acostumados com Java utilizam muito as cláusulas ‘private‘ e ‘protected‘ para encapsular os atributos de um objeto para logo depois implementarem os getters e setters para acessar esses atributos.

Essa prática é aconselhada em Java porque em algum momento do futuro você, talvez, precise validar esses dados ou retornar valores calculados. Nestes casos os programadores apenas implementam essa lógica nos métodos que acessam o atributo privado.

Mas em Python isso não é necessário. Em Python você pode transformar seu atributo público em uma “property” que não muda a forma de se acessar o atributo e permite o acrescimo de lógica ao acesso do mesmo.

Evite usar “__“.

Por convenção, em Python, todo método ou atributo iniciado com um “_” é considerado privado (equivalente ao protected em Java) e não deve ser acessado de fora da classe mesmo sendo possível fazê-lo.

Dito isso parece meio óbvio que não precisamos usar “__” para dificultar ainda mais o acesso à esse atributo/método. Além disso o uso do “__” traz alguns incoveninentes para quem quer derivar a sua classe e acessar este método/atributo já que o Python o renomeia acrescentando o nome da classe ao seu nome (__attr vira _Classe__attr).

Não sobrescreva builtins.

Python disponibiliza várias funções e classes builtins que facilitam muito o uso da linguagem e economizam digitação. Mas esses builtins tem nomes muito “comuns” e frequentemente a gente usa os nomes dos builtins como nomes de identificadores. Eu mesmo vivo (vivia) fazendo isso.

O problema é que em certos momentos alguns problemas podem acontecer quando você tenta chamar um buitin que já não é mais um builtin. Na maioria das vezes o problema “explode” logo e você rapidamente conserta mas em alguns casos você pode perder muitas horas tentando achá-lo.

Algumas pessoas usam um “_” no fim do nome do identificador (ex. “id” vira “id_”) mas eu acho isso um pouco feio então uso só quando não encontro uma alternativa melhor.

Vou colocar aqui uma tabela de equivalências que eu costumo usar para substituir o nome dos builtins mais comumente sobrescritos:

  • id – ident, key
  • type – kind, format
  • object – obj
  • list – plural (lista de element vira elements)
  • file – fd, file_handler
  • dict – dic, hashmap
  • str – text, msg

Análise estática economiza seu tempo.

Eu uso o pylint, mas conheço algumas pessoas que preferem o pyflakes ou o PyChecker.

A dica é essa: usar um programinha de análise estática como esses pode diminuir consideravelmente aqueles errinhos chatos de sintaxe, ou de digitação. Pode limpar os ‘import’ desnecessários do seu software, etc, etc.

É lógico que esse tipo de ferramenta não substitui uma boa política de testes mas é um bom complemento para ela.

Challenge yourself

Máximo de 3 níveis de indentação. (ou 4 se estiver dentro de uma classe)

Ao se esforçar para que seu código não fique muito aninhado você está trabalhando melhor a implementação dos seus métodos e funções. Nem sempre é possível (ou aconselhável) restringir tanto o nível de identação do seu código mas muitas vezes isso melhora a sua implementação.

Máximo de 2 indireções.

Recebeu um objeto como parâmetro? Chame apenas métodos dele e evite ao máximo chamar métodos do retorno desses objetos:

def f(obj):
    obj.metodo() # legal!
    obj.metodo().outro_metodo() # ruim!

Quando você chama um método pra um objeto retornado por outro método você está aumentando o acoplamento entre as classes envolvidas impedindo que uma delas seja substituída (ou reimplementada) ‘impunemente’.

Essa regrinha é uma das regrinhas da Lei de Demeter.

Máximo de 0 ‘if/elif/else‘s.

Polimorfismo é isso. No mundo OO ideal, perfeito e utópico praticamente não precisaríamos do comando “if” e usaríamos somente polimorfismo. Mas… como não conseguimos isso tão facilmente* devemos, ao menos, usar o “if” com moderação.

Conclusão

Esta é uma lista incompleta de dicas para programadores Python. Se futuramente eu lembrar ou aprender algo novo eu volto aqui para falar sobre o assunto.

Alguns desenvolvedores podem não concordar com as dicas. Neste caso eles podem enriquecer ainda mais esse texto argumentando sobre o as suas restrições no espaço para comentários.

Se você tiver alguma dica para compartilhar com a gente coloque nos comentários. Se ela for boa mesmo eu coloco ela no corpo principal do blog.

* eu mesmo só consegui fazer uma aplicação OO funcional sem usar um único if. Era uma implementação do joguinho de adivinhação de animais (aquele que pergunta “Vive na água? (s/n)”) em Smalltalk.

Update: pequena correção sugerida pelo Francisco nos comentários: s/Classe/_Classe/.

Quer mais?

Você pode encontrar várias dicas para desenvolvimento Python (e Django) nos artigos reunidos em minha newsletter quinzenal “O Melhor da Internet”. Você vai receber links com o melhor conteúdo relacionado a Python, Django, Carreira, Negócios e Empreendedorismo.

Para assinar basta preencher o formulário abaixo.

[mc4wp_form]

Publicado por

Osvaldo Santana

Desenvolvedor Python e Django, Empreendedor, dono de uma motocicleta esportiva, hobbysta de eletrônica, fã de automobilismo e corinthiano

  • É muito bom ter uma lista como essa, porque, principalmente quando eu comecei no Python, eu tive que aprender isso “na marra”.

    Valeu pelo texto!
    Abraço.

  • É muito bom ter uma lista como essa, porque, principalmente quando eu comecei no Python, eu tive que aprender isso “na marra”.

    Valeu pelo texto!
    Abraço.

  • Rafael SDM Sierra

    Osvaldo, compartilha o programa em SmallTalk conosco 🙂

  • Rafael SDM Sierra

    Osvaldo, compartilha o programa em SmallTalk conosco 🙂

  • Ah Rafael,

    Não tenho mais isso não. 🙂 Deixei lá onde eu trabalhava.

    Mas a implementação era feita usando um sistema de “nós” com as perguntas e dependendo da resposta do usuário eu chamava um método assim (em python): {‘sim’: self.metodo_sim, ‘nao’: self.metodo_nao}[resposta]()

  • Ah Rafael,

    Não tenho mais isso não. 🙂 Deixei lá onde eu trabalhava.

    Mas a implementação era feita usando um sistema de “nós” com as perguntas e dependendo da resposta do usuário eu chamava um método assim (em python): {‘sim’: self.metodo_sim, ‘nao’: self.metodo_nao}[resposta]()

  • Muito boa a lista, já está nos meus favoritos.

    No caso das exceções:

    def f():
    try:
    return conecta()
    except ExcecaoQueDeveriaSerErro:
    return None

    Nem sempre uma função será desse jeito, mas você pode levantar a exceção levantada pro pra próxima camada, por exemplo:

    try:
    # abre arquivo
    # comando1
    # comando2 # solta uma exceção
    # comando3
    except:
    # fecha arquivo
    raise

    Você não sabe o que fazer com a exceção mas sabe que no escopo da sua rotina você tem que finalizar as coisas caso uma exceção ocorra.

  • Muito boa a lista, já está nos meus favoritos.

    No caso das exceções:

    def f():
    try:
    return conecta()
    except ExcecaoQueDeveriaSerErro:
    return None

    Nem sempre uma função será desse jeito, mas você pode levantar a exceção levantada pro pra próxima camada, por exemplo:

    try:
    # abre arquivo
    # comando1
    # comando2 # solta uma exceção
    # comando3
    except:
    # fecha arquivo
    raise

    Você não sabe o que fazer com a exceção mas sabe que no escopo da sua rotina você tem que finalizar as coisas caso uma exceção ocorra.

  • Oi Danilo,

    O exemplo que você pegou é o exemplo do que *não* devemos fazer. O que você sugeriu é uma alternativa sim (jogar a exceção para cima).

    Mas teu exemplo com abre/fecha precisa ficar mais detalhado para funcionar como um bom exemplo. Imagine um IOError em “abre arquivo”. Teu except pega todas as exceçoes e depois tentará fechar um arquivo que não foi aberto 🙂

    Por isso que pra abrir arquivos o negócio é usar o “with:”

    try:
       with file("foo.txt") as fd:
          conteudo = fd.read()
    except IOError:
       logging.error("deu erro")
       raise
    
  • Oi Danilo,

    O exemplo que você pegou é o exemplo do que *não* devemos fazer. O que você sugeriu é uma alternativa sim (jogar a exceção para cima).

    Mas teu exemplo com abre/fecha precisa ficar mais detalhado para funcionar como um bom exemplo. Imagine um IOError em “abre arquivo”. Teu except pega todas as exceçoes e depois tentará fechar um arquivo que não foi aberto 🙂

    Por isso que pra abrir arquivos o negócio é usar o “with:”

    try:
       with file("foo.txt") as fd:
          conteudo = fd.read()
    except IOError:
       logging.error("deu erro")
       raise
    
  • Ricardo

    Valeu pelas dicas! E o melhor é que elas valem para outras linguagens também.

  • Ricardo

    Valeu pelas dicas! E o melhor é que elas valem para outras linguagens também.

  • Osvaldo,
    Belo Post, valeu pelas dicas

    Att,

  • Osvaldo,
    Belo Post, valeu pelas dicas

    Att,

  • Excelente, Osvaldo! Acho que cabia até um linkzinho para o Python Is Not Java lá no meio…

    Quando você colocou a regra de 0 if’s eu já senti o cheiro de Smalltalk, e até pensei que você não ia falar… Eu sei que você mesmo já falou isso antes, mas lá vai: aprendam Smalltalk. 🙂

  • Excelente, Osvaldo! Acho que cabia até um linkzinho para o Python Is Not Java lá no meio…

    Quando você colocou a regra de 0 if’s eu já senti o cheiro de Smalltalk, e até pensei que você não ia falar… Eu sei que você mesmo já falou isso antes, mas lá vai: aprendam Smalltalk. 🙂

  • Hoje eu me deparei com um código que levantava uma exceção no __init__().

    Não gosto muito dessa prática mas não pensei o suficiente sobre ela para fundamentar isso aqui no blog. Prometo que vou pensar mais no assunto e atualizar esse post com mais essa dica.

    Coloquei esse comentário aqui só pra não me esquecer deste tópico 🙂

  • Hoje eu me deparei com um código que levantava uma exceção no __init__().

    Não gosto muito dessa prática mas não pensei o suficiente sobre ela para fundamentar isso aqui no blog. Prometo que vou pensar mais no assunto e atualizar esse post com mais essa dica.

    Coloquei esse comentário aqui só pra não me esquecer deste tópico 🙂

  • “Essa prática é aconselhada em Java porque em algum momento do futuro você, talvez, precise validar esses dados ou retornar valores calculados. Nestes casos os programadores apenas implementam essa lógica nos métodos que acessam o atributo privado.””

    Fala cara, o real motivo para o uso de métodos private e protected em Java é o ENCAPSULAMENTO da classe.
    Não parece que foi isso que vc quis dizer com o trecho acima. Nunca tive contato com Python, mas acredito que deva valer o mesmo. Afinal de contas, atribuir as responsabilidades para cada classe e mantê-las conhecidas apenas para quem interessa é um dos fundamentos da Orientação à Objetos (ENCAPSULAMENTO).

    Abs!

  • “Essa prática é aconselhada em Java porque em algum momento do futuro você, talvez, precise validar esses dados ou retornar valores calculados. Nestes casos os programadores apenas implementam essa lógica nos métodos que acessam o atributo privado.””

    Fala cara, o real motivo para o uso de métodos private e protected em Java é o ENCAPSULAMENTO da classe.
    Não parece que foi isso que vc quis dizer com o trecho acima. Nunca tive contato com Python, mas acredito que deva valer o mesmo. Afinal de contas, atribuir as responsabilidades para cada classe e mantê-las conhecidas apenas para quem interessa é um dos fundamentos da Orientação à Objetos (ENCAPSULAMENTO).

    Abs!

  • Opa Osvaldo, bem legal o artigo.. é sempre bom ter dicas práticas para nosso uso.

    Abraços

  • Opa Osvaldo, bem legal o artigo.. é sempre bom ter dicas práticas para nosso uso.

    Abraços

  • Muito legal Osvaldo!!

    Bem que você poderia fazer uma seqüência a partir deste post escrevendo mais dicas… seria genial poder compartilhar técnicas de otimização e boas práticas utilizando Python!

    []s

  • Muito legal Osvaldo!!

    Bem que você poderia fazer uma seqüência a partir deste post escrevendo mais dicas… seria genial poder compartilhar técnicas de otimização e boas práticas utilizando Python!

    []s

  • Oi Diego,

    Isso mesmo. Programadores Java cuidam de encapsular seus atributos utilizando acessors para eles. Em Python isso não é necessário pois, sempre que necessário, você pode transformar o atributo em uma property que encapsula o seu atributo.

    Quando os professores ensinam encapsulamento pra gente na escola deveriam ensinar o conceito e não simplesmente dizer: “encapsulamento é colocar um getXXX() e um setXXX() pra manipular um atributo”.

    Uma vez eu li um artigo bem interessante explicando porque “getters and setters are harmful”. Mas infelizmente não encontrei mais ele no Google.

  • Oi Diego,

    Isso mesmo. Programadores Java cuidam de encapsular seus atributos utilizando acessors para eles. Em Python isso não é necessário pois, sempre que necessário, você pode transformar o atributo em uma property que encapsula o seu atributo.

    Quando os professores ensinam encapsulamento pra gente na escola deveriam ensinar o conceito e não simplesmente dizer: “encapsulamento é colocar um getXXX() e um setXXX() pra manipular um atributo”.

    Uma vez eu li um artigo bem interessante explicando porque “getters and setters are harmful”. Mas infelizmente não encontrei mais ele no Google.

  • Henrique

    Há muito mais sobre OO do que apenas encapsulamento… infelizmente parece que nem todo mundo “clicou” isso ainda. E olha que lá se vão mais de 30 anos de OO.

    Boas dicas.

  • Henrique

    Há muito mais sobre OO do que apenas encapsulamento… infelizmente parece que nem todo mundo “clicou” isso ainda. E olha que lá se vão mais de 30 anos de OO.

    Boas dicas.

  • Gustavo

    Bom texto…
    Gostaria de saber mais sobre a parte de polimorfismo, para evitar o uso dos “if’s”
    Se tiver como enviar um email seria mto grato.

  • Gustavo

    Bom texto…
    Gostaria de saber mais sobre a parte de polimorfismo, para evitar o uso dos “if’s”
    Se tiver como enviar um email seria mto grato.

  • Pingback: PT_BR: Mais um (muito bom) site sobre Python–gabrielstein.org()

  • Pingback: Pythonologia » É mais fácil pedir desculpas do que permissão - Python, open-source e desenvolvimento()

  • Gostei… valeu pelo texto =)

  • Gostei… valeu pelo texto =)

  • Pingback: Pythonismos – abruno.com [ Blog ]()

  • erick

    ei cara queria sabe s esse programa faz o mause clicar sozinho no local onde eu qeo

  • erick

    ei cara queria sabe s esse programa faz o mause clicar sozinho no local onde eu qeo

  • Esse programa não faz nada disso… mas sei de um programa, que deve ser o que você está procurando, chamado Sikuli (http://www.sikuli.org/) que faz isso que você está querendo.

  • Esse programa não faz nada disso… mas sei de um programa, que deve ser o que você está procurando, chamado Sikuli (http://www.sikuli.org/) que faz isso que você está querendo.

  • Francisco

    Cara, parabéns. O post tá excelente. Ainda estou começando meus estudos um pouco mais profundos de Python, mas certamente até pra mim foi uma excelente leitura.

    Queria só dar uma contribuição. Quando vc fala dos “underscores” (no texto: (__attr vira Classe__attr), se n me engano o python transforma em _Classe__attr. Ficou faltando o primeiro underscore aí.

    Continuarei lendo seus textos.

    Parabéns

  • Oi Francisco,

    Tem razão. Vou arrumar já.

  • The games in here are unlikely conventional Flash online game.This is a integrated entertainment platform for genera gamers to enjoy a leisure communication. Some commentators on the game’s rating is also very interesting, here I can also find philosophy about life when we play together. welcome to my blog!

  • Ph

    Eaw galera, sou novato em Python , estudo python apenas 2 meses por conta própria e está me ajudando muito no ramo científico ( parte estatística, gráficos,ler vários arquivos em txt etc.) tomara que todos comecem a usar python , pois o brasileiro precisa apreender a renovar e usar o que a de melhor !

  • Junior

    Estou a 1 ano utilizando python (unica linguagem que sei realmente programar) , e posso dizer que na parte científica está me ajudando muito… como ler arquivos , fazer simulações (Vpthon, onde fiz até publicações na área de física) , gráficos etc … realmente vale apena para qualquer um apreender Python.

    Mas queria um curso de python para me profissionalizar nesta linguagem …

  • Muito boa a explicação, apesar de usar python a algum tempo é semrpe bom saber que tem formas de otimizar simples de fazer.