Posts Tagged ‘ Programação ’

Criador de Funções com Metaclasses

Esse é um projeto que fiz meses atrás para gerar funções em Python com operadores matemáticos usando metaclasses.
Com isso, você pode fazer:

g = f ** 2 + 1   #função que eleva ao quadrado e soma 1
g(10) == 101

O código abaixo é uma versão (muito) simplificada do módulo que coloquei no GitHub, mas serve como exemplo de uso de metaclasses em Python (neste caso, Python 3):

import operator

class MetaFuncBuilder(type):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        attr = '__{0}{1}__'

        for op in (x for x in dir(operator) if not x.startswith('__')):
            oper = getattr(operator, op)
            op = op.rstrip('_') #special case for keywords: and_, or_

            def func(self, *n, oper=oper):
                return type(self)(lambda x: oper(self.func(x), *n),
                                  self.op + [(oper.__name__, n[0])
                                             if n else oper.__name__])
            def rfunc(self, n, *, oper=oper):
                return type(self)(lambda x: oper(n, self.func(x)),
                                  self.op + [(n, oper.__name__)])
           
            setattr(self, attr.format('', op), func)
            setattr(self, attr.format('r', op), rfunc)

class FuncBuilder(metaclass=MetaFuncBuilder):
    def __init__(self, func=None, op=None):
        self.op = op if op else []
        self.func = func if func else lambda x: x

    def __repr__(self):
        return '<var %s>' % self.op

    def __call__(self, *args):
        if not args:
            return self.func() #unary operators
        required, *args = args
        out = self.func(required)
        return out(*args) if args else out

f = FuncBuilder()

Em duas situações é comum o uso de metaclasses em Python: para que um certa classe tenha propriedades novas ou para automatizar algum mecanismo nas classes. Neste caso, usei para automatizar a criação de métodos para cada operador em Python, com ajuda do módulo operator.

Em Python, os operadores matemáticos binários existem nas formas __op__, __rop__ e __iop__, onde op é o nome, r significa que o objeto é o segundo operador e i significa in-place. Outros operadores existem em apenas uma forma.
Assim, o código pega os operadores e cria as três formas de cada um (os que existem em apenas uma forma ficam com 2 formas extras que não causam problema algum, já que não são nunca chamadas) e todos os operadores, ao invés de executar a operação designada, apenas criam uma novo objeto-função que pode ser chamado ou ter novas funções adicionadas.

O módulo no GitHub tem uma centena de outras funcionalidades para gerar essas funções, mas ele sofre de um problema que pode ser bem sério: Chamadas de função em Python são caras!

Basicamente, cada objeto do tipo FuncBuilder guarda a operação que tem que executar e o próximo da lista. Isso gera um monte de chamadas de função em cima dos operadores, o que pode deixar o código muito mais lento.

Minha próxima alteração pra esse código seria juntar todas as operações numa só chamada de função, o que não vai permitir extrair uma função de dentro de outra (como é possível agora por meio de closures), mas vai reduzir o custo para apenas uma chamada de função, como seria natural. Isso não vai ser tão simples por causa dessa estrutura recursiva da classe, mas quando tiver tempo, eu faço.

Otimização 0.1

Achei bem interessante que nesse tempo que fiquei sem escrever, o blog teve uma quantidade de acessos maior que em tempos de atividade. De qualquer forma, um post de vez em quando não faz mal a ninguém :).

Tem um site de tirinhas chamado Vida de Programador que posta situações corriqueiras (as vezes) de forma engraçada.

Na tirinha de hoje, ele mostrava o código de um programador iniciante:

bool isValid;
...
switch(isValid) {
case true: ...
case false: ...
}

Claro que não faz sentido tanta verborragia pra algo tão simples como uma variável booleana. No entanto, as pessoas se esquecem que os compiladores atuais são muito bons e não vão deixar seu código lento só porque você preferiu que ele fosse “mais legível”.

Uma excelente ferramenta online chamada GCC Explorer mostra a saída assembly do seu compilador preferido com diversos níveis de otimização.

Independente do nível de otimização, o código gerado para o trecho acima será extamente o mesmo que para “if...else” ou o operador ternário “? :“.

int testFunction(bool x) {
  switch (x) {
    case true:
        return 10;
        break;
    case false:
        return 12;
  }
}

int testFunction2(bool x) {
  return x? 10 : 12;
}

E o Assembly gerado (com -O2) será:

testFunction(bool):
	cmpb	$1, %dil
	sbbl	%eax, %eax
	andl	$2, %eax
	addl	$10, %eax
	ret
testFunction2(bool):
	cmpb	$1, %dil
	sbbl	%eax, %eax
	andl	$2, %eax
	addl	$10, %eax
	ret

Se você olhar, não há nenhum jump no assembly gerado. Ele compara a variável com o número 1, subtrai o registro de retorno dele mesmo com borrow, faz um AND com o 2 e depois soma 10. Você não ia pensar que retornar 12 ou 10 significa colocar 2 ou 0 numa variável e depois somar 10, mas o compilador sabe que lógica AND e OR para números inteiros é muito mais rápida que jumps e faz tudo sem que você se preocupe. Esse código seria semelhante a:

int testFunction3(bool x) {
  return 10 + (2 & -(1-x));
}

Mas o GCC consegue diminuir ainda mais o assembly!

testFunction3(bool):
	leal	3(%rdi), %eax
	andl	$2, %eax
	addl	$10, %eax
	ret

Só que ninguém vai querer ler um código tão feio assim que apenas poupa 1 nanossegundo.

Sem dar razão à pessoa que fez um switch para um bool, se o código não é o gargalo do seu programa, não há motivo para mudar. Como escreveu Donald Knuth: otimização prematura é a raiz de todo o mal.

Python 3 – exec e outras mudanças

Já tem um tempo que tento usar somente Python 3 para meus projetos em Python, mas, como muita gente ainda está presa em versões antigas (como o pessoal do CentOS que há um mês mudou do Python 2.4 para o ainda defasado 2.6), sou obrigado a trabalhar com os dois ambientes.

Portar pequenos programas de uma versão para outra pode ser bastante simples (como no Txt2Tags que precisei de umas 2 ou 3hs para fazer funcionar) quando já se conhece os possíveis problemas e suas soluções, mas nem tudo está bem documentado.

Claro que tudo isso seria muito mais difícil se não houvesse script chamado 2to3.py que vem junto com as versões mais novas do interpretador para automatizar o processo. O problema que existem coisas que ele é incapaz de traduzir ou que precisam de mudanças mais bruscas no código para que funcionem.

Por algum motivo, o site “Dive into Python 3” que tinha uma versão online do livro homônimo foi removido do ar e perdi a melhor referência para o tópico que existia, ** Encontrei um novo mirror do Dive into Python 3 **

Também existem outros bons textos por ai.

Algumas das mudanças mais importantes na nova versão são os iteradores usados em lugares que antes retornavam listas, o uso de unicode por padrão (que na minha opinião já vale por todo o trabalho de portar código) e a troca de algumas palavras-chaves (statements) por funções e vice-versa.

Sobre essa última mudança, as palavras-chave tem o poder de mudar o local em que se encontram enquanto outros nomes podem significar qualquer coisa que o programador queira e só podem ser verificados em tempo de execução, não podendo transformar o ambiente.

Por exemplo, o comando yield faz com que o a função que o contenha seja transformada num gerador mesmo que ele nunca seja executado ou tenha bytecode para isso!

def gerador():
    if 0:
        yield

Neste código acima, o interpretador vai ver que o if nunca será executado e não vai gerar bytecode para nada dentro dele (no caso o yield), mas a função não vai retornar o implícito None quando for chamada. Ela vai retornar um gerador vazio que pode ser usado num loop for.

Usando o módulo dis para desmontar bytecode vemos que a função só retorna None, mas ela vai ter um flag para mostrar sua utilidade:

>>> dis.dis(gerador)
  2           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE

Ainda sobre isso, True, False e None agora são palavras reservadas e que não podem ser modificadas, fazendo com que, se o exemplo acima fosse feito com False ao invés de 0, ele geraria bytecode no Python 2, mas não no Python 3!

No sentido contrário, as palavras-chave print e exec do Python 2 agora são funções e não podem mais alterar seu ambiente.

No caso do print, não tem problema algum e a sintaxe ficou bem melhor, mas o exec (que normalmente seu uso não é uma boa prática de programação) pode ter um comportamento bem diferente nas duas versões do interpretador.

Essa informação não vai ser encontrada na documentação do Python (apenas alguns avisos que não explicam bem a situação) e geralmente a pessoa vai procurar saber disso quando bater de cara com esse problema e nada funcionar.

Olhe o código abaixo (Não tome como exemplo de boa programação):

a = 1
def f():
    exec("a = 2")
    print(a)

f()
print(a)

No Python 2, a função vai imprimir “2” e o outro print vai imprimir “1”, como esperado. No Python 3, o número “1” vai ser impresso nos dois casos.

O que aconteceu?

O meu amigo dis mostra como a variável “a” é chamada no bytecode para impressão dentro da função:

Python 2:

3            8 LOAD_NAME                0 (a)

Python 3:

3           13 LOAD_GLOBAL              2 (a)

Como o Python 3 não consegue ver nenhum a dentro da função, ele assume que é uma palavra definida globalmente e vai embora. No Python 2, é percebida a existência da palavra exec e transforma a busca por um nome sendo primeiro no namespace local e, se não encontrar, no namespace global.

O que, por um lado traz o resultado “desejado”, também faz todos os acessos a funções e variáveis serem mais lento.

Soluções:
1 – Não usar exec.

Praticamente sempre é possível fazer uma solução sem exec que seja simples, mas se for necessário, é preciso tomar cuidado com essa mudança ou passar um segundo parâmetro para a função dizendo em que namespace você quer se o resultado seja avaliado e usar a informação nele.

Normalmente, ninguém usa exec para situações como a que eu mostrei, mas para gerar conteúdo (funções, classes, subclasses, etc) em tempo de execução e muitas vezes existem ferramentas melhores, como as metaclasses e os decoradores.

ZZ no Python

Pra quem não conhece, existe um conjunto de mini-programas para shell muito legal chamado Funções ZZ, criado pelo Aurélio Jargas (@oreio).

[Atualizado!] – Veja as novas funcionalidades no fim

Não sei se alguém já fez, mas criei um wrapper para essas funções em python, para os que já tem o ícone do Python no desktop e não querem abrir um terminal :).

Disponível em http://dl.dropbox.com/u/6016495/zz.py. Funciona tanto em Python 2 como em Python 3.

Exemplos:

>>> import zz
>>> int(zz.calcula('19+23'))
42
>>> zz.senha()
'k23Civ'
>>> zz.ramones()
'Come back baby, come back'
>>> zz.dolar
09/05/2011 compra   venda   hora
Comercial   1,618   1,620   17:01   +0,18
Paralelo    1,700   1,820   20/2/2011   0,00
Turismo     1,560   1,730   16:16   +0,58

>>> print(zz.loteria('megasena'))
megasena:
   08 - 11 - 14 - 30 - 36 - 38 
   Concurso 1281 (07/05/2011)
   Acumulado em R$ 2.000.000,00 para 11/05/2011

>>> zz.cpf('11111111111')
'CPF válido'
>>> zz.converte('cf', 32)
'32 C = 89.60 F'

[Novo!]
Agora também funciona com Pipes, podendo encadear funções ZZ e usar STDIN!
Mais exemplos:

>>> '1+2+3+4' | zz.calcula
'10'
>>> print('a\nb\nc\nd\na\nb\nc' | zz.uniq)
a
b
c
d
>>> 'oi, beleza?, oi, e ai, oi io, oi' | zz.contapalavra('oi', pipe=True)
'4'

Note que, se quiser ter uma chamada de função junto com o pipe, tem que adicionar o argumento “pipe=True”, senão ele calcula primeiro a resposta e tenta fazer a operação ‘or’ entre as strings. Podia ser esse segundo o comportamento padrão, mas teria que usar sempre um pipe pra pegar o valor final, que não é minha intenção.

A ideia de usar o operador ‘or’ pra fazer ligação entre chamadas de funções eu peguei desse site: http://dev-tricks.net/pipe-infix-syntax-for-python

Se quiser usar, pode colocar diretamente na pasta de módulos do Python, como por exemplo (no Linux):
/usr/local/lib/pythonX.Y/dist-packages

Bônus:
E, pra quem quiser, apresento o modo mais estranho de calcular 2+2+2+2:

>>> int((zz.calcula('2+2')[:-1]+'+2' | zz.calcula(pipe=True))[:-1] + '+2' | zz.calcula)
8

Python e Qt

Esse post não se chama Python + Arduino (Parte 3) porque agora estou usando outro microcontrolador, um Cortex M3.
Aliás, a ideia aqui é mostrar o uso de PyQt, um binding para usar Qt com Python.

Após baixar e instalar, você pode começar a programar interfaces no método convencional ou usar o QtDesigner (ferramenta do Qt) para fazer isso.

Por exemplo, você pode fazer assim:

Abra o QtDesigner e crie um novo projeto. Pode ser Widget ou MainWindow, faz pouca diferença no arquivo gerado.

Coloque seus botões, labels, caixas, etc:

Junte os sinais que não precisam de código. Aqui eu coloquei a caixa e o slider para um atualizar o valor do outro e o checkbox para desativar ou ativar o frame contendo a caixa e o slider.

Agora, você vai salvar um arquivo no formato .ui, que é um XML que vai ser usado para gerar o nosso programa.

Dentro da pasta de instalação do PyQt, que fica dentro do diretório do Python, existe uma ferramenta chamada pyuic4, que faz a conversão. No Linux, você pode rodar o programa diretamente, mas no Windows é uma boa criar um arquivo .bat contendo o comando que vai ser usado para converter o arquivo XML em código Python.

@”C:\Python32\python” “C:\Python32\Lib\site-packages\PyQt4\uic\pyuic.py” controle.ui > form.py

Verifique a pasta de instalação e escolha o nome do arquivo que você salvou e salve isso num arquivo com extensão .bat.
Agora é só dar dois cliques que deve surgir um arquivo chamado form.py, que contem uma classe para modificar uma janela que você tenha criado.

Agora você tem que criar um outro arquivo Python para ser seu programa a ser executado e criar alguns objetos. Existem bons tutoriais de PyQt4 pela internet, então não vou ficar detalhando muito.

Basicamente, você tem que criar um QApplication, uma janela (QWidget ou QMainWindow) e passar essa janela para a função do arquivo form.py que vai criar os botões, etc.

Após isso, você tem que conectar os sinais e os slots dos seus objetos para criar as funcionalidades desejadas. Neste caso, a cada evento no slider ou na caixa, um sinal serial está sendo enviado para o microcontrolador.

Cada slot é um novo método que você deve criar ou então usar de um artifício que são as funções lambda do Python.
Imagem do programa sendo executado:

Abaixo um exemplo de código:
Qualquer dúvida, faça um comentário!


from PyQt4 import QtGui, QtCore
import sys, serial 
import scan, form

class Main():
    def __init__(self):
        self.app = QtGui.QApplication(sys.argv)
        self.janela = QtGui.QMainWindow()
        self.ui = form.Ui_Form()  #Referências para elementos da janela
        self.ui.setupUi(self.janela)
        self.cria_slots()
        self.encontra_portas()
        self.janela.show()

    def cria_slots(self):
        self.ui.bt_conectar.clicked.connect(self.slot_conexao)
        self.ui.box_valor.valueChanged.connect(self.envia_info)
        self.ui.ck_ativar.clicked.connect(self.set_on_off)

    def encontra_portas(self):
        for porta in scan.scan():
            self.ui.box_porta.addItem(porta[0])

    #Criar outros métodos aqui

if __name__ == '__main__':
    prog = Main()
    prog.app.exec_()

Python + Arduino (Parte 2)

Além de plotar gráficos, com o Python é possível criar interfaces para modificar parâmetros do microcontrolador, enviando dados pela porta serial.

Como já visto na Parte 1 deste assunto, é preciso instalar o PySerial além do Python.

Desta vez eu fiz em uns 30 minutos um programa para enviar ao Arduino um número de 0 a 255 (8-bit) que serve para mudar o valor da resistência de um potenciômetro digital que deveria estar ligado à um amplificador de som.

Estou sem o amplificador no momento, então, para visualizar as mudanças coloquei um led para acender com PWM dentro do programa do arduino.

/*
 * Exemplo de leitura serial com o Arduino
 * Colocar isso dentro de um loop do programa
 */

char palavra[10];
unsigned char valor;

if (Serial.available()) {
    delay(100);
    unsigned char n = 0;
    while (Serial.available() > 0) {
        palavra[n++] = (Serial.read());
        //Cuidado com escrita fora do array
    }
    palavra[n] = '\0';

    valor = atoi(palavra);

    /*Usar o 'valor' para fazer alguma coisa, 
      como acender um Led com PWM*/
}

Outro dia coloco vídeo do sistema funcionando.

Python + Arduino

Não, ainda não dá para programar em Python para o ATmega, mas dá para acessar a porta serial pelo Python.

Recentemente descobri a existência do PySerial, um módulo para fazer acesso às portas seriais pelo Python, que tem me ajudado bastante a processar dados adquiridos pelo meu microcontrolador.

A melhor parte é que você pode coletar dados e, com ferramentas como Numpy e Matplotlib fazer diversas transformações e plotar gráficos bem interessantes.

Por exemplo, tenho um display de LCD cuja intensidade de luz é controlada por PWM provido pelo Arduino. A intensidade é escolhida de acordo com um valor lido num divisor resistivo com um resistor de 4.7K e um foto-resistor (LDR) que varia de 0 a 20K.

Eu escrevi um pouco sobre LDR aqui.

Para fazer o PWM, a tensão é lida numa porta analógica e passa por uma função escolhida empiricamente para a intensidade de luz ficar adequada ao ambiente. Sabe aquela coisa de que você tá querendo dormir e tem sempre um led de computador, televisão, aparelho de DVD ou outros aceso? Neste caso, o display fica com um mínimo de luminosidade quando está escuro.

Abaixo um gráfico gerado pelo sensor, usando Python.

Tensão lida(Azul), PWM (Verde) X Horário do dia:

Coloquei no pastebin um pequeno script em Python para coletar dados e salvar num arquivo.

Modo de uso:
1) Primeiro modifique a porta serial para a que você usa. No Linux é “/dev/ttyUSB“, no Windows é “COM“. Escolha também a taxa de transmissão adequada. O meu Arduino fica variando de /dev/ttyUSB0 para /dev/ttyUSB1, por isso tem uma função que busca a porta aberta.

2) Chame o script passando o número de amostras (o microcontrolador deve mandar quebras de linhas entre as amostras) e o nome do arquivo a ser gerado. Exemplo:

$ python meu_script.py 1234 nome_do_arquivo.txt

Para interromper a amostragem, aperte Ctrl+C e os dados serão salvos no arquivo antes de terminar o programa.

Para número indeterminado de amostras, coloque 0. Vai ficar pegando dados até você apertar Ctrl+C.

Programa foi escrito para Python 3, mas a única modificação necessária para Python 2 é retirar a chamada de str() na função de amostragem, pois a aquisição de dados é feito em strings de bytes e não unicode.

Os dados são salvos no arquivo assim:

['123', '546', '789', ... ]

Para obtê-los novamente no Python, faça:

>>> minha_lista = eval(open('nome_do_arquivo.txt').read())

Se estiver usando valores inteiros, converta a lista:

>>> minha_lista = [int(i) for i in minha_lista]