Mostrando postagens com marcador Python. Mostrar todas as postagens
Mostrando postagens com marcador Python. Mostrar todas as postagens

sábado, 28 de novembro de 2015

A outra porta

Image O Paradoxo de Monty Hall consiste no fato de que, em se descartando algumas escolhas erradas, as que sobram desconhecidas combinam as probabilidades de estarem certas das descartadas.

Por exemplo, em três portas, atrás de uma há um prêmio. Você escolhe uma das três e o mestre de cerimônias abre uma das outras duas, mostrando que o carro não está lá. A porta que você escolheu continua com ⅓ de chance de ser a correta, mas a outra porta não escolhida ganha a possibilidade de conter o prêmio da que foi aberta, passando a ser de ⅔.

Isso acontece porque a porta com o prêmio e a porta escolhida (independente de ser a do prêmio ou não) influenciaram juntas na escolha da porta a ser aberta, mesmo que você não saiba qual a porta do prêmio.

Muita gente discorda disso, diz que se sobraram duas portas, as chances passa a ser ½+½ e não ⅓+⅔. Pode-se ficar horas (ou dias) discutindo isso. A melhor forma de se verificar isso é testando!

Vamos escrever uma classe que MasterOfCeremonies que oferece três portas, sendo uma premiada:
from random import choice


class MasterOfCeremonies:

    __slots__ = '__doors __prize __choice'.split()

    def __init__(self) -> None:
        self.__doors = 'ABC'
        self.__prize = choice(self__doors)
        self.__choice = None

    @property
    def doors(self) -> str:
        return self.__doors

    @property
    def wins(self) -> bool:
        if not self.__choice:
            raise TypeError
        return self.__choice == self.__prize

    def choose(self, door: str) -> None:
        if door not in self.__doors:
            raise ValueError
        self.__choice = door

Nossa classe guarda três portas (A, B e C), apenas uma premiada. Para testar, podemos jogar mil vezes e vermos em quantas vezes ganhamos.

Para facilitar, vamos escolher sempre a porta do meio:
if __name__ == '__main__':
    wins = 0
    for _ in range(1000):
        wc = MasterOfCeremonies()
        door = wc.doors[1]  # sempre a porta do meio
        wc.choose(door)
        wins += 1 if wc.wins else 0
    print('Wins:', wins)

Não importa quantas vezes você execute esse programa, vai sempre retornar um valor próximo de 333 vitórias – o que faz todo sentido! São mil lances, ⅓ dá 333.

Agora vamos adicionar o paradoxo: um método que abre aleatoriamente uma porta sem prêmio e outro que permite ao jogador trocar para a porta fechada restante.

Dentro da classe MasterOfCeremonies adicione os seguintes métodos:
    def remove(self) -> None:
        if not self.__choice or len(self.__doors) == 2:
            raise TypeError
        doors = set(self.__doors)
        others = doors - {self.__choices, self.__prize}
        other = choice(list(others))
        doors -= {other}
        self.__doors = ''.join(list(doors))

    def change(self) -> str:
        if not (self.__choice and len(self.__doors) == 2):
            raise TypeError
        doors = set(self.__doors)
        doors -= {self.__choice}
        self.__choice = list(doors)[0]
        return self.__choice

Agora vamos mudar nosso procedimento principal para trocar de porta após a abertura (remove) da porta sem prêmio:
if __name__ == '__main__':
    wins = 0
    for _ in range(1000):
        wc = MasterOfCeremonies()
        door = wc.doors[1]  # sempre a porta do meio
        mc.choose(door)
        mc.remove()         # descarta uma das portas sem prêmio
        door = mc.change()  # troca a porta escolhida
        wins += 1 if wc.wins else 0
    print('Wins:', wins)

Agora vem o mindfuck: não importa quantas vezes você execute esse programa, o número de vitórias em mil será sempre por volta de 667, ⅔ das tentativas!

Ou seja, o paradoxo realmente funciona. Volte lá na Wikipédia, porque a explicação é realmente muito boa. ;-)

[]’s

PS: Eu ia escrever os códigos em Prolog, pois sua abordagem de lidar com domínio de verdades é muito conveniente para este problema, no entanto Python é muito mais didática: já basta a dificuldade em entender o Paradoxo de Monty Hall, não precisamos de mais complicação com uma implementação complexa.

sábado, 18 de abril de 2015

Tipos em Cython

Image Ontem fiz uma pequena introdução ao Cython.

Cython é uma plataforma que traduz código Python para C e o compila em biblioteca compartilhada importável no próprio Python. A linguagem em si é um superset de Python, com tipagem estática ou dinâmica (duck-typing) e suporte a código especial que pode ser traduzido diretamente para C.

Uma parte importante de Cython é sua tipagem estática, mas os tipos pode ser um pouco diferentes de Python.

Há tipos específicos, como struct e enum, que são traduzidos diretamente para o equivalente C, e tipo de extensão (cdef class). Entre eles:

Tipo CythonTipo CCoerção para Python 3
bool PyLongObject * bool
bint int bool
size_t size_t int
char char int
unsigned char unsigned char int
int int int
long long int
long long long long int
float float float
double double float
const char * const char * bytes
bytes PyBytesObject * bytes
const Py_UNICODE * const Py_UNICODE * str
unicode struct PyUnicodeObject str
object PyObject * object
list PyListObject * list
dict PyDictObject * dict
set PySetObject * set
tuple PyTupleObject * tuple
void * void * sem equivalência
struct S struct S dict
enum E enum E int

Todos os modificadores C (unsigned, const, long, *…) são aceitos. Na coerção de tipos, também é aceito &. Se um valor numérico for recebido por uma variável do tipo object, será usado PyLongObject * (inteiro de tamanho arbitrário) ou PyFloatObject * (ponto flutuante, equivalente a double de C).

[]’s
ℭacilhας, La Batalema

sexta-feira, 17 de abril de 2015

Cython

Image Nas últimas semanas tenho desenvolvido uma aplicação usando Cython e me surpreendi com o resultado.

Comecei usando o Cython apenas para compilar código Python – o que de fato rendeu o aumento de desempenho prometido –, mas então resolvi ir mais longe pra ver o quanto poderia extrair dessa plataforma: comecei a usar tipagem estática, funções C (cdef) e depois acabei migrando minhas classes para tipos de extensão (cdef classes).

A cada novo passo era perceptível o crescimento do desempenho e da coerência geral do código. Minha única crítica é quanto aos testes: o código fica muito difícil de ser testado, já que não é possível fazer monkey-patch para colocar os mocks.

Segundo a documentação, ao compilar código Python com Cython, você tem um aumento de 35% no desempenho do código. Usando a tipagem estática, o aumento é de 300% e usando funções C e tipos de extensão o aumento é de aproximadamente 15.000%.

Não posso confirmar esses números, pois não fiz medições exatas, mas uma fila do RabbitMQ que enchia com 6, 7 mil mensagens, encheu com 64 mil no mesmo período de tempo (eu estava fazendo apenas carga, sem consumir a fila).

Uma coisa que gostei muito no Cython é o feeling: tem uma pegada bastante parecida com a do Objective C, o que faz sentido.

Vou dar um exemplo da própria documentação do Cython:

Dado o seguinte código Python puro:
def f(x):
    return x**2-x

def integrate_f(a, b, N):
    s = 0
    dx = (b-a)/N
    for i in range(N):
        s += f(a+i*dx)
    return s * dx
Você pode salvar esse código em integrate.pyx e compilá-lo assim:
bash$ cythonize -ib integrate.pyx
Isso irá gerar o código equivalente em C (integrate.c) e compilá-lo na biblioteca integrate.so, que pode ser diretamente importada em Python:
bash$ python
>>> from integrate import integrate_f
>>>
Se você tiver o IPython, pode usar o Cython no prompt:
bash$ ipython
Python 2.7.9 (default, Jan  7 2015, 11:49:12) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> %load_ext Cython
>>> %%cython
from integrate cimport integrate_f
De qualquer forma, só usando a biblioteca compilada em vez do código Python importado, Cython já promete um aumento de 35% de performance – o que não me frustrou.

Além disso, podemos adicionar tipagem ao código: Cython é um superset em Python, ou seja, é uma linguagem em si e um código Python é também código válido Cython.
def f(double x):
    return x**2-x

def integrate_f(double a, double b, int N):
    cdef:
        int i
        double s, dx
    s = 0
    dx = (b-a)/N
    for i in range(N):
        s += f(a+i*dx)
    return s * dx
Segundo a documentação, isso garante um desempenho 4 vezes superior ao de Python.

No entanto ainda é possível otimizar mais ainda o código! Cython tem uma sintaxe específica que gera código C diretamente, cdef:
cdef double f(double x) except? -2:
    return x**2-x

def integrate_f(double a, double b, int N):
    cdef:
        int i
        double s, dx
    s = 0
    dx = (b-a)/N
    for i in range(N):
        s += f(a+i*dx)
    return s * dx
O desempenho dessa função f em C promete ser 150 vezes melhor do que a mesma função em Python puro.

O except? -2 na função é usado para passar exceções para o código C. Em um outro momento posso entrar em mais detalhes.

Tipo de extensão

Tipos de extensão são equivalentes às classes de Python, porém mais restritos e muito mais eficientes.

Por exemplo, da a seguinte classe:
class Consumer(object):

    def __init__(self, backend):
        self.backend = backend

    def run(self, handler):
        resp = self.backend.get()
        return handler.handle(resp)
Considerando que as instâncias só serão executadas em Cython, o código equivalente estaria em dois arquivos, o primeiro consumer.pxd:
from backends.base cimport Backend
from handlers.base cimport Handler

cdef class Consumer:

    cdef:
        Backend backend
        int run(self, Handler handler) except? -1
Esse código .pxd equivale ao cabeçalho .h de C. Agora, o arquivo de implementação deve ser chamado consumer.pyx:
from backends.base cimport Backend
from handlers.base cimport Handler

cdef class Consumer:

    def __cinit__(self, Backend backend):
        self.backend = backend

    cdef int run(self, Handler handler) except? -1:
        cdef dict resp = self.backend.get()
        return handler.handle(resp)
Esse código promete ser muito mais eficiente que sua versão em Python, infelizmente métodos declarados como cdef são acessíveis apenas em C (e em Cython). Para que o método seja acessível em Python, ele deve ser criado como um método Python comum (def) ou pode ser usado cpdef.

O comando cpdef (C/Python def) cria a função C (como cdef) e um wrapper em Python para torná-la acessível. Se o módulo for importando com cimport, será usada a função C original; se for importado com o clássico import, será usado o wrapper.

Conclusão

Até agora não tive motivos para me arrepender de usar Cython, recomendo.

[]’s
ℭacilhας, La Batalema

quarta-feira, 15 de abril de 2015

Uma brincadeira rápida

Image

Um jeito divertido de calcular Fibonacci usando NumPy:

from collections.abc import Callable
from numpy import matrix, long

def fib() -> Callable:
    m = matrix('1, 1; 1, 0', dtype=long)
    
    def fib(n: int) -> long:
        return (m ** n)[0, 0]
    return fib
fib = fib()

Cython

from libc.stdint cimport int64_t
from numpy cimport ndarray
from numpy import matrix, long


cdef:
    ndarray m = matrix('1, 1; 1, 0', dtype=long)


cpdef int64_t fib(int n):
    return (m ** n)[0, 0]

[]’s

[update 2015-11-28]
Código atualizado para Python 3 e versão Cython.
[/update]

domingo, 24 de agosto de 2014

Combinador de ponto fixo

Glider A beleza da matemática e do cálculo é que algo não precisa ter uma aplicação prática imediata para ser estudado em profundidade. A aplicabilidade pode surgir 150, 200 anos após sua concepção.

Um dos tópicos mais magníficos do cálculo-λ é o combinador de ponto fixo, apesar de ser de interesse mais acadêmico do que diretamente prático.

Ainda assim, seu entendimento engrandece em tamanha proporção o conhecimento do programador, que vale a pela dedicar-lhe algum tempo.

Conceitos básicos

Há alguns pré-requisitos que você precisa conhecer para começar nosso estudo:

Cálculo-λ

Cálculo-λ é uma parte da lógica matemática criada da década de 1930 como parte da investigação dos fundamentos da matemática. No cálculo-λ, todos os elementos são funções de primeira classe, inclusive os números.

Por exemplo, o número 2 é uma função que aplica uma outra função duas vezes, e é representado como λsz.s(sz).

O sinal + (ou S em alguns textos) significa incremento, e é descrito como λwyx.y(wyx). Ou seja, +3 é 3 incrementado, que é igual a 4. 2+3 é a função 2 recebendo como parâmetros + e 3, ou seja, incrementa duas vezes o número 3, +(+3), o que resulta em 5.

A multiplicação é prefixal, não mesoclítica, ou seja, 2x escreve-se como *2x (ou M2x) e sua definição é λab.a(b+)0 ou λxyz.x(yz).

O cálculo-λ é o princípio de onde deriva a programação funcional.

[update 2014-08-25]
A definição de decremento é (caso eu não tenha copiado errado):
DEC = λnfx.n (λgh.h(gf)) (λu.x) I
[/update]

Ponto fixo

Impossível entender combinador de ponto fixo sem saber o que significa ponto fixo. Ponto fixo é o ponto de uma função onde o valor retornado é igual ao valor fornecido.

Por exemplo, dada a função f(x) = 2x-1, o que em notação cálculo-λ fica f = λx.DEC(*2x), o ponto fixo é aquele onde fx = x, ou seja, o resultado é igual ao parâmetro.

Usando aritmética básica:
f(x) = 2x - 1
x = 2x - 1
2x - 1 = x
2x - 1 - x = 0
x - 1 = 0
x = 1

Portanto o ponto fixo de λx.DEC(*2x) é 1.

Repare que a recursão sobre o ponto fixo resulta sempre no mesmo resultado. Se:
x = fx

Podemos substituir x por fx:
x = fx = f(fx) = f(f(fx))) = ...


Variáveis livres e vinculadas

Em cálculo-λ, no corpo de uma função, todas as variáveis que vêm de sua assinatura são vinculadas e as que vêm de fora são livres.

Por exemplo, na função:
λfx.Δxf


As variáveis x e f são vinculadas e a variável Δ é livre.

Combinadores

Combinadores são funções que não possuem variáveis livres. Por exemplo:
λx.xx
λx.y

A primeira linha é um combinador, já a segunda não.

Veja que, a rigor, números e operadores aritméticos são variáveis livres, porém, como são valores constantes e bem definidos, tratam-se de exceções aceitáveis em um combinador.

Construção let

A construção let é uma construção clássica em cálculo-λ e em linguagens funcionais de programação.

Darei um exemplo: seja fx igual a *2x em f4, o resultado é 8 – fx é o dobro e x e o caso é f4. Isso é descrito assim:
let fx = *2x in f4

Podemos construir uma função assim, por exemplo, dado o parâmetro x, sendo fa igual a *2a em fx-1:
λx.let fa = *2a in DEC(fx)

É o mesmo que:
λx.DEC(*2x)

Em Haskell isso pode ser escrito assim:
func x = let f a = 2 * a in f x - 1

Em Racket fica assim:
(describe func
  (λ (x)
    (let ((f (λ (a) (* 2 a)))
      (- (f x) 1))))

A forma genérica da construção let é:
let fx = y in z

E sua abertura é:
(λf.z)(λx.y)


Finalmente: o combinador de ponto fixo

Combinador de ponto fixo é uma função que retorna o ponto fixo da função fornecida como parâmetro. Em cálculo e mesmo em programação isso é muito útil na implementação de funções recursivas.

Função recursiva é aquela que faz chamada a ela mesma, por exemplo, fatorial:
FACT = λn.(ISZERO n) 1 (*n (FACT (DEC n)))

Olhando para o corpo da função, mesmo desconsiderando números e operadores (ISZERO, 1, * e DEC), ainda há a variável livre FACT, que não é definida na assinatura da função.

[update 2014-08-25]
A definição de ISZERO é:
TRUE = λxy.x
FALSE = λxy.y
ISZERO = λn.n(λx.FALSE)TRUE
[/update]

A forma de resolver isso é criar uma função que recebe a si mesma na assinatura:
ALMOST-FACT = λfn.(ISZERO n) 1 (*n (f (DEC n)))

Quando esse combinador recebe como parâmetro o fatorial, ele retorna o próprio fatorial, assim a função fatorial é o ponto fixo dele!

O combinador de ponto fixo é aquele que, ao receber a função como parâmetro, retorna seu ponto fixo – e quando a função recebe o ponto fixo, retorna o próprio ponto fixo, segundo o seguinte comportamento:
yf = f(yf)

Só aqui já seria possível implementar uma função que calcule o ponto fixo. Em Haskell:
fix f = f (fix f)

E em Lazy Racket:
(define fix
  (λ (f)
    (f (fix f))))

Porém seria sem sentido, pois essa mesma função que resolve o problema, é parte do problema, já que ela própria possui variável livre.

Resolver este problema não é difícil: basta pegarmos a definição: dada uma função f, queremos o ponto x onde fx seja igual a x… é uma construção let!

Data uma função que recebe f (λf.), seja x igual a fx (let x = fx) em x (in x):
λf.let x = fx in x

De fato isso já funciona em Haskell:
fix :: (a -> a) -> a
fix f = let x = f x in x


Combinador Y

Para Scheme (Lazy Racket), precisamos destrinchar um pouco mais, abrindo o let.

Precisamos de dois parâmetros na primeira posição, portanto dobraremos o x:
λf.let x = fx in x
λf.let xx = f(xx) in xx
λf.(λx.xx)(λx.f(xx))

Mais um passo e chegamos ao famoso combinador Y de Curry!

Aplicando o parâmetro λx.f(xx) à função λx.xx, temos (λx.f(xx))(λx.f(xx)), exatamente o combinador Y de Curry:
Y = λf.(λx.f(xx))(λx.f(xx))

Em Lazy Racket:
(define Y
  (λ (f)
    ((λ (x) (f (x x))) (λ (x) (f (x x))))))


Linguagens estritas

A maioria das linguagens não suporta lazy evaluation, então é preciso fazer um truque para que a definição do combinador não entre em loop infinito.

O truque é aplicar a função λsv.sv (que é o número 1, equivalente à identidade: I = λx.x). Por exemplo:
g = 1g
g = (λsv.sv) g
g = (λv.gv)

Por exemplo, o combinador Y em Python:
Y = lambda f: (lambda x: x(x))(lambda x: f(x(x)))

Esse código entra em loop infinito! Mas se substituirmos xx por λv.xxv, tudo se resolve:
Y = lambda f: (lambda x: x(x))(lambda x: f(lambda *args: x(x)(*args)))


Outros combinadores de ponto fixo

Curry foi o que obteve a solução mais simples para o combinador de ponto fixo, mas não foi o único. Há outras soluções possíveis.

Combinador de Turing:
Θ = (λx.xx)(λab.b(aab))


Combinador de ponto fixo estrito (vimos acima):
Z = λf.(λx.xx)(λx.f(λv.xxv))

Combinador de ponto fixo não padrão:
B = λwyx.w(yx)
∆ = λx.xx
N = B∆(B(B ∆)B)

[]’s

sábado, 12 de abril de 2014

Modelo de dados em Python

Image Ao escrever uma classe em Python, é preciso ficar atento a algumas convenções a serem respeitadas.

Métodos especiais

Em Python, métodos começando e terminando com sublinha dobrado (__*__) são reservados e chamados especiais. Você não deve implementá-los se não souber o que está fazendo.

Métodos especiais podem fazer coisas maravilhosas ou criar comportamentos inesperados que zoarão com sua aplicação. São divididos em 12 tipos:
  1. Personalização básica;
  2. Acesso personalizado a atributos;
  3. Criação personalizada de classes;
  4. Verificações personalizadas de instâncias e subclasses;
  5. Emulação de chamada;
  6. Emulação de recipiente;
  7. Emulação de tipos sequenciais;
  8. Emulação de tipos numéricos;
  9. Regras de coerção;
  10. Gerenciamento de contexto;
  11. Pesquisa de classes old-style;
  12. Pesquisa de classes new-style.

Recomendo fortemente a leitura do documento Data model, é essencial ao bom programador.

Métodos privados

Métodos privados são iniciados com sublinha dobrado, mas não terminados da mesma forma (__*).
Um método privado não pode ser acessado de outro contexto a não ser da classe onde ele foi definido.

Isso não é de todo verdade… o que acontece de fato é que métodos privados são prefixados com um sublinha seguido do nome da classe, para evitar que sejam sobrescritos e prevenir acesso a partir de subclasses ou de fora do contexto da classe.

Por exemplo, no contexto da classe Person, todos os métodos iniciados com sublinha dobrado serão prefixados com _Person. Assim, __fix_data vira _Person__fix_data.

Isso permite que você faça herança múltipla de classes que possuem o mesmo nome de método privado sem conflitos.

Métodos protegidos

Há uma convenção em Python de que métodos com nome iniciado com um sublinha (_*) são protegidos, ou seja, só devem ser acessados no contexto da classe e de suas subclasses.

Nenhum tratamento é feito para evitar que sejam acessados de outros contextos, mas se espera que os programadores sigam a convenção.

A orientação em Python é que o programador não é nenhum bebezinho que precisa ser guiado e sabe o que está fazendo, portanto não aja como um bebê e respeite as convenções.

Atributos e propriedades

Tudo o que foi dito sobre métodos especiais, privados e protegidos também vale para atributos e propriedades.

[]’s
Cacilhας, La Batalema

Ordem de chamada dos métodos de inicialização

Image Quando você cria e instancia uma classe em Python, muita coisa acontece e acho que nem todos estão familiarizados com os passos.

Vamos dar uma olhada na sequência.

Criação da classe

Quando você cria uma classe com o statement class, a primeira coisa que acontece é o construtor (__new__) da metaclasse (por padrão type) ser chamado.

O construtor recebe como parâmetros a própria metaclasse, o nome da classe (str), uma tupla contendo as classes base da classe criada e um dicionário com métodos e atributos declarados no corpo da classe.

Caso você sobrescreva o construtor da metaclasse, ele precisa retornar o objeto instanciado, que você obtém do construtor da classe pai da metaclasse.

Em seguida o método de inicialização (__init__) da metaclasse é chamado, recebendo como parâmetros o retorno do construtor, o nome da classe, a tupla de classes base e o dicionário de métodos e atributos.

É recomendável não sobrescrever o construtor da metaclasse. Qualquer procedimento pode ser tranquilamente executado nos métodos e inicialização e de chamada.

Instanciação da classe

Quando você instancia a classe, o primeiro método a ser chamado é o método de chamada (__call__) da metaclasse. Ele recebe como parâmetros a classe e quaisquer parâmetros passados no comando de instanciação.

Em seguida é evocado o construtor da classe, recebendo a classe e quaisquer parâmetros passados no comando de instanciação.

É obrigatório que a instância criada pelo construtor de super seja retornada, caso o construtor seja sobrescrito.

Após o construtor, o método de inicialização da classe é chamado, recebendo como parâmetros o retorno do construtor e quaisquer parâmetros passados no comando de instanciação.

Chamando a instância

Caso o método de chamada seja implementado na classe, a instância é “chamável” (callable). Na chamada o método de chamada é executado recebendo a instância e quaisquer parâmetros passados na chamada da instância.

Quem é quem

Um pequeno código apenas com boilerplates que não executam nada, com o único objetivo de demonstrar onde fica cada método citado:
class Metaclasse(type):

    def __new__(meta, name, base, dict_):
        """ Este é o construtor da metaclasse """
        return type.__new__(meta, name, base, dict_)

    def __init__(cls, name, base, dict_):
        """ Este é o método de inicialização da metaclasse """
        type.__init__(cls, name, base, dict_)

    def __call__(cls, *args, **kwargs):
        """ Este é o método de chamada da metaclasse """
        return type.__call__(cls, *args, **kwargs)


class Classe(object):
    __metaclass__ = Metaclasse

    def __new__(cls, *args, **kwargs):
        """ Este é o construtor da classe """
        return super(Classe, cls).__new__(cls)

    def __init__(self, *args, **kwargs):
        """
        Este é o método de inicialização da classe.
        Como ela herda de object, não há necessidade de
        chamar super, mas quando houver, a forma é:
        super(Classe, self).__init__(*args, **kwargs)
        """

    def __call__(self, *args, **kwargs):
        """ Este é o método de chamada da classe """
        return None

[update 2015-04-18] Esta é a versão do código acima em Python 3:
class Metaclasse(type):

    def __new__(meta, name: str, base: tuple, dict_: dict) -> type:
        """ Este é o construtor da metaclasse """
        return type.__new__(meta, name, base, dict_)

    def __init__(cls, name: str, base: tuple, dict_: dict):
        """ Este é o método de inicialização da metaclasse """
        type.__init__(cls, name, base, dict_)

    def __call__(cls, *args, **kwargs) -> object:
        """ Este é o método de chamada da metaclasse """
        return type.__call__(cls, *args, **kwargs)


class Classe(object, metaclass=Metaclasse):

    def __new__(cls, *args, **kwargs) -> object:
        """ Este é o construtor da classe """
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        """
        Este é o método de inicialização da classe.
        Como ela herda de object, não há necessidade de
        chamar super, mas quando houver, a forma é:
        super().__init__(*args, **kwargs)
        """

    def __call__(self, *args, **kwargs) -> None:
        """ Este é o método de chamada da classe """
        return None
[/update]

Dois dedos de prosa sobre método destruidor

O método destruidor (__del__) não é chamado quando o objeto perde todas as referências ativas, mas sim quando é coletado pelo garbage collector (gc).

Como o gc não tem hora certa para rodar e seu comportamento varia muito de uma implementação de Python para outra, não é recomendável confiar nele para executar procedimentos críticos.

O que você pode fazer é usá-lo para garantir determinados estados que podem ter sido esquecidos ou perdidos depois que a instância ficou sem referências.

Observações finais

As assinaturas do construtor e do método de inicialização da classe devem ser rigorosamente iguais.

[update]
Um detalhe importante é que, se o método de inicialização for sobrescrito alterando sua assinatura, não há necessidade de sobrescrever o construtor, porém se o construtor for sobrescrito alterando sua assinatura, é obrigatório que o método de inicialização também seja sobrescrito.

Detalhes da linguagem…
[/update]

A chamada do método de chamada da classe pai da meta classe deve passar os parâmetros esperados pelos argumentos nas assinaturas do construtor e da inicialização da classe. No exemplo acima, esta chamada (precedida por return) é:
type.__call__(cls, *args, **kwargs)

[update]
Um detalhe que esqueci de mencionar é que é justamente nessa chamada que o construtor e o método de inicialização são evocados.
[/update]

Os parâmetros passados são os esperados nas assinaturas citadas.

[]’s
Cacilhας, La Batalema

quarta-feira, 19 de março de 2014

RLock

Image Dando continuidade o artigo sobre thread, um recurso muito útil é lock reentrante.

Lock reentrante é uma variação de lock que pode ser realocado múltiplas vezes pelo mesmo thread e não pode ser liberado por outro thread. É muito útil em funções recursivas, mas funciona também para garantir que o lock seja alocado e liberado pelo mesmo thread.

A fábrica (factory) para criar locks reentrantes é threading.RLock.

É preciso tomar cuidado para que todos os threads compartilhem o mesmo lock, senão ele se torna inútil:
lock = RLock()

thr1 = Thread(target=func1, args=(lock, ))
thr2 = Thread(target=func2, args=(lock, ))
thr3 = Thread(target=func3, args=(lock, ))

Dentro da função, é preciso alocá-lo (acquire) no inicío e liberá-lo (release) ao final. Por exemplo:
def func1(lock):
    lock.acquire()
    try:
        # executa o procedimento
        ...
    finally:
        lock.release()

Protegendo um objeto mutável

Uma utilidade para o lock reentrante é proteger métodos que alterem o conteúdo de um objeto.

Imagine que temos uma classe Person com dados, como identity_code (CPF) que podem sofrer alterações em threads diferentes (sei que não é uma boa abordagem, mas apenas como exemplo).

Podemos criar um decorador que torna um método thread-safe usando lock reentrante:
def lock(wrapped):
    lock_ = RLock()

    @wraps(wrapped)
    def wrapper(*args, **kwargs):
        with lock_:
            return wrapped(*args, **kwargs)

    return wrapper

Esse decorador pode ser usado nos setters de cada propriedade:
class Person(object):

    ...

    @property
    def identity_code(self):
        return self.__identity_code

    @identity_code.setter
    @lock
    def identity_code(self, value):
        self.__identity_code = value

    ...

Na verdade essa abordagem não resolve 100% o problema, mas já reduz muito a ocorrência de bugs.

Protegendo qualquer objeto

Porém a abordagem acima apenas protege parcialmente o objeto e não funciona para classes de terceiros.

Outra abordagem é usar um lock para todo o objeto, tanto leitura quanto gravação, agnóstico a qual objeto está sendo protegido. Assim, não há necessidade de usarmos propriedades.

Vamos criar uma classe para trancar qualquer objeto:
class ObjectLocker(object):

    def __init__(self, obj):
        self.__obj = obj
        self.__lock = RLock()

    def __enter__(self):
        self.__lock.acquire()
        return self.__obj

    def __exit__(self, etype, exc, traceback):
        self.__lock.release()

No código a instância dessa classe será passada para os threads, que terá de usar with para acessar o objeto original.

Ao usar with, o objeto original será trancado para o thread atual e liberado ao final.

A passagem será:
locker = ObjectLocker(Person(...))
thr = Thread(target=func, args=(locker, ))

Dentro da(s) função(ões) func a instância de Person deve ser acessa da seguinta forma:
with locker as person:
    name = person.name
    person.identity_code = data['identity_code']

Espero que os exemplos tenham sido úteis.

[]’s
Cacilhας, La Batalema

sábado, 15 de março de 2014

Thread-safe

Image Tenho tido a necessidade de lidar com muitas bibliotecas de terceiros e teno percebi um erro (ou seria uma abordagem?) comum nas mais novas: quase nenhuma delas é thread-safe.

Acredito que, com o modismo do uso de corrotinas (chamadas lightweight threads), os programadores mais novos passaram a considerar os threads de sistema obsoletos, deixando de tomar cuidados essenciais para boas bibliotecas.

Bem, tenho uma novidade para vocês: threads não são obsoletos e corrotinas não resolvem todos os problemas do mundo. Há situações em que usar corrotinas pode ser a melhor opção sim, mas em alguns casos os bons e velhos threads ainda são o salva vidas.

Para que isso seja possível, na criação de bibliotecas é necessário tomar alguns cuidados:
  • Prefira sempre que possível usar objetos imutáveis. Prefira tuplas, strings, tipos numéricos básicos, etc.
  • Evite permitir que objetos agregados façam alteração em seu objeto contentor sempre que possível.
  • Em todos métodos e propriedades de um objeto que pode ser compartilhado (como o contentor citado) que alterem o estado do objeto, inicie com um RLock, chamando seu método acquire, e encerre chamando seu método release. Tome cuidado para que seja usada a mesma instância de RLock!
  • Não tenha medo de usar objetos do módulo threading: Condition, Event, Lock, RLock e BoundedSemaphore. Eles são seus amigos. ;-)
  • Se estiver difícil escrever testes, substitua threading por dummy_threading para os testes unitários, mas use threading para testes de aceitação.

[]’s
Cacilhας, La Batalema

terça-feira, 18 de fevereiro de 2014

Aspectos – parte II: mixins

Image Na parte I demos uma passada geral no conceito de aspectos. Aqui veremos os mixins.

Mixins são classes incompletas que apenas atribuem determinado comportamento às classes herdeiras.

Vamos a um exemplo bem esdrúxulo, mas suficiente: um objeto que armazena notas de alunos em um arquivo.
from cPickle import dumps, loads
import gdbm

__all__ = ['Banco', 'Notas']


class Banco(object):

    def __init__(self, file=None):
        if file is None:
            file = 'notas.db'
        if isinstance(file, basestring):
            file = gdbm.open(file, 'c')
        self.file = file

    def close(self):
        if self.file:
            self.file.close()
            self.file = None


class Notas(object):

    def __init__(self, matricula, db):
        self.matricula = matricula

        if not isinstance(db, Banco):
            raise TypeError(
                'expected Banco instance, got {}'
                .format(type(db).__name__)
            )

        self.db = db

        try:
            self.notas = loads(db[str(matricula)])
        except KeyError:
            self.notas = {}


    def save(self):
        db[str(self.matricula)] = dumps(self.notas)


    @property
    def primeiro_bimestre(self):
        return self.notas.get('1bim')

    @primeiro_bimestre.setter
    def primeiro_bimestre(self, value):
        self.notas['1bim'] = float(value)

    @property
    def segundo_bimestre(self):
        return self.notas.get('2bim')

    @segundo_bimestre.setter
    def segundo_bimestre(self, value):
        self.notas['2bim'] = float(value)

    @property
    def terceiro_bimestre(self):
        return self.notas.get('3bim')

    @terceiro_bimestre.setter
    def terceiro_bimestre(self, value):
        self.notas['3bim'] = float(value)

    @property
    def quarto_bimestre(self):
        return self.notas.get('4bim')

    @quarto_bimestre.setter
    def quarto_bimestre(self, value):
        self.notas['4bim'] = float(value)

    @property
    def recuperacao(self):
        return self.notas.get('rec')

    @recuperacao.setter
    def recuperacao(self, value):
        return self.notas['rec'] = float(value)

    @property
    def media(self):
        soma = sum(
            self.primeiro_bimestre or 0,
            self.segundo_bimestre or 0,
            self.terceiro_bimestre or 0,
            self.quarto_bimestre or 0,
        )
        m = soma / 4.

        rec = self.recuperacao
        if rec is not None:
            m = (m + rec) / 2.

        return m

Repare que temos o mesmo problema apresentando na parte I: está tudo misturado em uma única classe!

Podemos separar as partes de gerência de banco e serialização em classes diferentes, dedicadas a seu próprio aspecto, chamadas mixins.

A classe de faz serialização pode ser apenas isso:
class NotaSerializavelMixin(object):
    def load(self):
        s = self.retrieve()
        self.notas = loads(s) if s else {}

    def __str__(self):
        return dumps(self.notas)

A gerência de banco vai para outro mixin:
class PersistenciaMixin(object):
    def retrieve(self):
        try:
            return self.db[str(self.matricula)]
        except KeyError:
            return None

    def save(self):
        db[str(self.matricula)] = str(self)

Preferindo, é possível separar a gerência de notas em um mixin também:
class NotasMixin(object):

    @property
    def primeiro_bimestre(self):
        return self.notas.get('1bim')

    @primeiro_bimestre.setter
    def primeiro_bimestre(self, value):
        self.notas['1bim'] = float(value)

    ...

    @property
    def media(self):
        soma = sum(
            self.primeiro_bimestre or 0,
            self.segundo_bimestre or 0,
            self.terceiro_bimestre or 0,
            self.quarto_bimestre or 0,
        )
        m = soma / 4.

        rec = self.recuperacao
        if rec is not None:
            m = (m + rec) / 2.

        return m

Ao final, a classe principal será apenas uma cola dos mixins:
class Notas(NotaSerializavelMixin, PersistenciaMixin, MotasMixin):

    def __init__(self, matricula, db):
        self.matricula = matricula

        if not isinstance(db, Banco):
            raise TypeError(
                'expected Banco instance, got {}'
                .format(type(db).__name__)
            )

        self.db = db
        self.load()

A API da classe continua idêntica: recebe o número da matrícula e o banco na instanciação, propriedades para acessar as notas e método save() para salvá-las em arquivo, porém agora cada aspecto está isolado e encapsulado em seu próprio mixin.

[]’s
Cacilhας, La Batalema

Aspectos – parte I

Atualizado no blog novo.

Image Um paradigma muito útil é a Programação orientada a Aspectos.

Consiste em separar e encapsular as funcionalidades de um código conforme sua importância.

Nesta primeira parte, abordaremos de forma simples tal separação e deixaremos o conceito de mixins para a parte II.

Vamos começar com um exemplo: imagine uma view que modifica o estado de um objeto, retornando um hash do novo estado:
@app.route('/people/<uuid>/', methods=['PATCH'])
def update_person(uuid):
    person = db.person.find({ '_id': uuid }).first()
    if not person:
        raise Http404

    try:
        data = json.loads(request.data)
    except ValueError:
        return json.dumps({ 'error': 'invalid request' }), \
               400, \
               { 'Content-Type': 'application/json' }

    person.update(data)
    db.person.save(person)

    r = [(str(k), repr(v)) for k, v in person.iteritems()]
    r.sort()
    s = ';'.join('{}:{}'.format(k, v) for k, v in r)

    return json.dumps({ 'etag': md5(s).hexdigest() }), \
           200, \
           { 'Content-Type': 'application/json' }

A solução atende, mas é de difícil manutenção. Perceba que a função chamada update_person (atualiza pessoa) faz muito mais do que simplesmente atualizar os dados:
  • Recupera o documento do banco, retornando 404 se não existir;
  • Faz os parsing dos dados recebidos, retornando 400 em caso de erro;
  • Efetivamente atualiza o documento;
  • Serializa o objeto para a resposta;
  • Gera um hash da serialização;
  • Responde a requisição com formato conveniente.

Cada um desses passos é um aspecto do processo e pode ser isolado do restante.

Vamos então separar o primeiro aspecto: recuperação do documento.
def retrieve_person_aspect(view):
    @wraps(view)
    def wrapper(uuid):
        person = db.person.find({ '_id': uuid }).first()
        if not person:
            raise Http404

        return view(person)
    return wrapper

@app.route('/people/<uuid>/', methods=['PATCH'])
@retrieve_person_aspect
def update_person(person):
    try:
        data = json.loads(request.data)
    except ValueError:
        return json.dumps({ 'error': 'invalid request' }), \
               400, \
               { 'Content-Type': 'application/json' }

    person.update(data)
    db.person.save(person)

    r = [(str(k), repr(v)) for k, v in person.iteritems()]
    r.sort()
    s = ';'.join('{}:{}'.format(k, v) for k, v in r)

    return json.dumps({ 'etag': md5(s).hexdigest() }), \
           200, \
           { 'Content-Type': 'application/json' }

Agora a recuperação do documento está isolada, podendo inclusive ser usada em outras views. Nossa view já recebe o documento recuperado e não precisa lidar com o fato dele existir ou não.

Porém ainda temos muita coisa misturada. Por exemplo, a obtenção e parsing dos dados recebidos: isso caracteriza outro aspecto do código, que não a atualização do documento.

Podemos portanto, separá-los:
def parse_data_aspect(view):
    @wraps(view)
    def wrapper(person):
        try:
            data = json.loads(request.data)
        except ValueError:
            return json.dumps({ 'error': 'invalid request' }), \
                   400, \
                   { 'Content-Type': 'application/json' }

        return view(person, data)
    return wrapper

def retrieve_person_aspect(view):
    ...

@app.route('/people/<uuid>/', methods=['PATCH'])
@retrieve_person_aspect
@parse_data_aspect
def update_person(person, data):
    person.update(data)
    db.person.save(person)

    r = [(str(k), repr(v)) for k, v in person.iteritems()]
    r.sort()
    s = ';'.join('{}:{}'.format(k, v) for k, v in r)

    return json.dumps({ 'etag': md5(s).hexdigest() }), \
           200, \
           { 'Content-Type': 'application/json' }

A função update_person já está muito mais limpa: atualiza o documento, serializa e retorna o hash, mas ainda faz coisas demais. Vamos separar o tratamento do retorno:
def respond_etag_aspect(view):
    @wraps(view)
    def wrapper(person, data):
        response = view(person, data)
        return json.dumps({ 'etag': md5(response).hexdigest() }), \
               200, \
               { 'Content-Type': 'application/json' }
    return wrapper

def parse_data_aspect(view):
    ...

def retrieve_person_aspect(view):
    ...

@app.route('/people/<uuid>/', methods=['PATCH'])
@retrieve_person_aspect
@parse_data_aspect
@respond_etag_aspect
def update_person(person, data):
    person.update(data)
    db.person.save(person)

    r = [(str(k), repr(v)) for k, v in person.iteritems()]
    r.sort()
    return ';'.join('{}:{}'.format(k, v) for k, v in r)

As coisas estão ficando cada vez mais separadas. A única coisa que a função update_person faz agora além de atualizar o documento é serializá-lo. Isso também pode ser isolado:
def serialize_person_aspect(view):
    @wraps(view)
    def wrapper(person, data):
        response = view(person, data)
        r = [(str(k), repr(v)) for k, v in response.iteritems()]
        r.sort()
        return ';'.join('{}:{}'.format(k,v) for k, v in r)

def respond_etag_aspect(view):
    ...

def parse_data_aspect(view):
    ...

def retrieve_person_aspect(view):
    ...

@app.route('/people/<uuid>/', methods=['PATCH'])
@retrieve_person_aspect
@parse_data_aspect
@respond_etag_aspect
@serialize_person_aspect
def update_person(person, data):
    person.update(data)
    db.person.save(person)
    return person

Perceba que, com a separação dos aspectos em funções distintas, o código ficou muito mais semântico:
  • retrive_person_aspect apenas recupera o documento do banco;
  • parse_data_aspect apenas faz o parsing dos dados recebidos;
  • respond_etag_aspect apenas gera o formato correto da resposta;
  • serialize_person_aspect apenas serializa o documento;
  • finalmente, update_person apenas atualiza o documento.

Observações

  • db é um objeto de banco de dados MongoDB, apenas para fim de exemplo.
  • app é uma aplicação Flask, apenas para fim de exemplo.
  • A ordem dos decoradores é importante.
  • Os imports foram omitidos:
import json
from functools import wraps
from hashlib import md5

Na parte II abordaremos mixins.

[]’s
Cacilhας, La Batalema

sábado, 12 de outubro de 2013

Encadeamento de funções

Poliedro Encadeamento de função é o pattern onde um grupo de funções é encadeado.

Em uma cadeia, sempre a saída de uma função é a entrada da seguinte, o parâmetro inicial é fornecido à primeira função da cadeia, que segue executando as funções, e o resultado da última função é retornado como resultado da cadeia.

Vejamos como criar cadeia.

Python

A forma mais simples simples de encadear funções é simplesmente efetuar a chamada, como o código Python a seguir:
f1 = lambda x: x * 2
f2 = lambda x: x - 1
f3 = lambda x: x // 3

print(f3(f2(f1(8))))

Porém esse código é visualmente confuso e difícil de ser depurado. É conveniente criar uma fábrica (factory) de cadeias:
chain = lambda *funcs: \
    reduce(lambda acc, func: (lambda x: func(acc(x))),
    funcs or [lambda x: x])

the_chain = chain(
    lambda x: x * 2,
    lambda x: x - 1,
    lambda x: x // 3,
)
print(the_chain(8))

Repare que as funções agora estão na ordem em que são executadas:
  1. O número é dobrado;
  2. O resultado da primeira função é decrementado;
  3. O resultado da terceira função é triplicado e retornado.

A função reduce() de Python executa a redução de um map/reduce: o primeiro parâmetro é uma função que recebe o acumulador e o próximo valor, retornando o resultado parcial.

O segundo parâmetro de reduce() é a lista a ser reduzida, no caso, a lista de funções ou, caso nenhuma função tenha sido informada, um função vazia que retorna o próprio parâmetro fornecido.

Com este procedimento, é possível reduzir a lista de funções a uma função representando o encadeamento das funções fornecidas.

Erlang

Em Erlang as coisas também podem ser interessantes.

Criaremos duas funções, uma fábrica a ser exportado (chain/1) e uma função de redução (chain/2) que não deve ser exportada:
-module(chain).
-export([chain/1]).

chain(Funs) when is_list(Funs) ->
    chain(Funs, fun(X) -> X).

chain([], Acc) -> Acc;

chain([Fun|Rest], Acc) when is_function(Fun) ->
    chain(Rest, fun(X) -> Fun(Acc(X)) end).

Você pode então executar no shell:
1> c(chain).
{ok,chain}
2> Fun = chain:chain([
2> fun(X) -> X * 2 end,
2> fun(X) -> X - 1 end,
2> fun(X) -> X div 3 end
2> ]).
#Fun<chain.1.134034448>
3> Fun(8).
5
4> 

Haskell

Haskell por outro lado já possui uma fábrica de cadeias, usando o carácter $, infelizmente na ordem inversa: primeiro as últimas funções a serem executadas, e por último as primeiras.

Então, por exemplo: f1 $ f2 $ f3 x é executado como f1 (f2 (f3 x)).

Crie um módulo:
module Chain where

the_chain :: Int -> Int
the_chain n = (`div` 3) $ (\x -> x - 1) $ (* 2) n

No prompt do ghci execute:
Prelude> :load chain.hs
[1 of 1] Compiling Chain            ( chain.hs, interpreted )
Ok, modules loaded: Chain.
*Chain> the_chain 8
5
*Chain> 

[]’s
Cacilhας, La Batalema

quarta-feira, 18 de setembro de 2013

Um editor de textos rapidinho

Image Veremos como é simples criar uma aplicação gráfica rapidamente usando Tkinter.

Vamos começar a a partir de um boilerplate:

#!/usr/bin/env python
# coding: UTF-8
from __future__ import absolute_import, division, print_function, unicode_literals

#-------------------------------------------------------------
# Cabeçalhos
#

# Aqui vão entrar os imports

__all__ = ['Editor']


#-------------------------------------------------------------

class Editor(object):

    def mainloop(self):
        pass


#-------------------------------------------------------------
# Rodapé

if __name__ == '__main__':
    app = Editor()
    app.mainloop()

A primeira coisa que precisamos fazer é criar nossa janela raiz. Para tanto, adicione aos cabeçalhos o import:

from Tkinter import *

Método construtor

Em seguida, no começo da classe Editor, vamos começar a escrever nosso construtor:

class Editor(object):

    def __init__(self):
        root = self.root = Tk()
        root.title('EditPy')

Precisamos então de um frame para colocar os botões de abrir, salvar e salvar-como:

        fbuttons = Frame(root)
        Button(fbuttons, text='Open', command=self.open) \
              .pack(side=LEFT)
        Button(fbuttons, text='Save', command=self.save) \
              .pack(side=LEFT)
        Button(fbuttons, text='Save As', command=self.save_as) \
              .pack(side=LEFT)

O pai (master) do frame fbuttons é nossa janela raiz e o pai dos botões é o frame.

Agora precisamos de uma caixa de texto para editar e exibir o conteúdo dos arquivos. Primeiro vá aos cabeçalhos novamente e acrescente o import:

from ScrolledText import ScrolledText

Então, continuando o construtor, crie a caixa de texto com rolagem:

        steditor = self.steditor = ScrolledText(root)

Para fechar o construtor só falta exibir o frame e a caixa de texto, e colocar o foco na caixa:

        fbuttons.pack(expand=True, fill=X)
        steditor.pack(expand=True, fill=BOTH)
        steditor.focus()

Loop principal

Se você tentar rodar o código agora, nada acontece. Isso porque o método mainloop() faz isso: absolutamente nada.

Precisamos então, neste método, chamar o loop principal do Tkinter. Para isso, altere o método:

    def mainloop(self):
        self.root.mainloop()

Chamamos então o loop principal através da janela raiz (root).

Abrir um arquivo

Porém coisas estranhas e erros esdrúxulos podem acontecer, pois ainda não implementamos nenhum dos métodos chamados pelos botões – self.open, self.save e self.save_as.

Parece-me justo que comecemos pelo método de abertura de arquivo. Criemos a assinatura do método:

    def open(self):

Para abrir um arquivo, precisamos de uma ferramenta chamada askopenfilename que precisamos importar, então nos cabeçalhos acrescente:

from tkFileDialog import askopenfilename

Então, de volta ao método open(), podemos pegar o nome do arquivo e carregá-lo:

        filename = askopenfilename(title='EditPy | Open')
        if filename:
            self.filename = filename
            self.load_file()

O método load_file() é quem vai de fato carregar o arquivo, mas precisamos antes apagar o conteúdo da caixa de texto:

    def load_file(self):
        filename = self.filename
        steditor = self.steditor

        steditor.delete('@0,0', 'end')
        with open(filename) as fd:
            steditor.insert('@0,0', fd.read())

O método delete() da caixa de texto apaga parte do conteúdo da caixa e os parâmetros são os índices de começo e fim:
  • @0,0 significa linha 0, coluna 0 (começo do texto);
  • end significa o final do texto.

O método insert() inserte a string (lida do descritor de arquivo) na posição indicada (@0,0).

Salvando o texto

Para salvar o texto, precisamos implementar o método save(). Ele precisa:
  1. Verificar se temos um nome de arquivo – se não tivermos, é um save_as.
  2. Abrir o arquivo para escrita.
  3. Obter o texto da caixa de texto.
  4. Gravar o texto no arquivo.

Simples, indolor e o código é intuitivo:

    def save(self):
        filename = self.filename
        if not filename:
            return self.save_as()

        with open(filename, 'w') as fd:
            fd.write(
                self.steditor.get('@0,0', 'end')
            )

Repare nos índices usados no método get().

Em seguida é preciso saber o que fazer quando não temos o nome do arquivo ou quando escolhemos Save As.

Primeiro a assinatura do método:

    def save_as(self):

Caso haja um nome de arquivo, é honesto usá-lo como ponto de partida para caixa de diálogo de salvamento. Para isso precisamos separar o nome de diretório do nome base do arquivo e uma forma de fazer isso é com o método de string rfind.

Para sabermos qual o separador de diretórios usado, adicione aos imports:

from os import path

Então continue no método:

        filename, directory = self.filename, None
        if filename and path.sep in filename:
            index = filename.rfind(path.sep) + 1
            directory = filename[:index]
            filename = filename[index:]

[update 2014-04-12]
Optei por usar rfind, mas poderia ter usado path.basename e path.dirname. É uma questão de escolha e estilo.
[/update]
Feito o slice, já podemos exibir a caixa de diálogo de salvamento. Para isso precisamos da ferramenta asksaveasfilename.

Modifique o import do askopenfilename para:

from tkFileDialog import askopenfilename, asksaveasfilename

Continuando agora no método save_as(), podemos exibir a caixa de diálogo e, se algum arquivo for informado, chamar o método save():

        filename = asksaveasfilename(
            title = 'EditPy | Save As',
            initialfile = filename,
            initialdir = directory,
        )

        if filename:
            self.filename = filename
            self.save()

E pronto! Já pode testar seu editor de texto!

Lapidando

Uma forma de melhorar um pouco o visual da aplicação é usando a extensão ajulejada do tk, chamada ttk.

Para tanto, basta adicionar ao cabeçalho, no final dos imports:

from ttk import *

Você também pode usar os métodos wm_protocol() e bind_all() da janela raiz para adicionar funcionalidades interessantes, mas isso é história pra outro dia. ;-)

[]’s
Cacilhας, La Batalema

quarta-feira, 14 de setembro de 2011

Falsa indução ou a falácia da metodologia infalível

Glider Reza a lenda que um código bem testado é um código sem bugs.

Além de não acreditar em código sem bugs, ainda há um problema com a definição de «bem testado».

Hoje em dia os evangelistas das novas velhas metodologias de programação defendem que a experiência profissional seja secundária – quiçá irrelevante – caso as metodologias agile sejam corretamente aplicadas, e uma das regras de outro é entupir o código de testes.

Porém quantidade não é qualidade. Não adianta encher o código de testes se você não tem feeling sobre o que testar – e feeling só se adquire com experiência profissional.

Há uma série de armadilhas prontas para pegar os incautos que se creem blindados pelas metodologias.

Uma armadilha comum é a falsa indução.

Por exemplo, tenho um código que calcula a raiz quadrada e trinta testes para ele, todos passando:
bash$ nosetests
..............................
----------------------------------------------------------------------
Ran 30 tests in 0.013s

OK


No entanto, quando eu tento alguns valores:
bash$ python
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from sqr import sqr
>>> sqr(16) # 4
7
>>> sqr(25) # 5
7
>>> sqr(144) # 12
45
>>> sqr(225) # 15
27
>>> sqr(256) # 16
58


Valores completamente loucos! Isso porque o código caiu em uma falsa indução:
# coding: UTF-8

from math import ceil

__all__ = ['sqr']

def sqr(num):
d = 10 ** int(ceil(len(str(num)) / 2.))
return (num % d) + (num // d)


É claro que uma boa metodologia, como TDD, é um ferramenta poderosíssima nas mãos de um programador experiente, mas pode se tornar uma arma de autodestruição nas mãos de um novato com excesso de confiança.

Então repito o conselho de um autor que escreveu, mais ou menos isto: não aprenda a programar em dez horas ou em dez dias; aprenda a programar em dez anos.

[]’jn
Cacilhας, La Batalema


PS: Você pode baixar os testes de http://cacilhas.info/falsa-inducao/tests.py. Repare que se substituir:
from sqr import sqr

por:
from math import sqrt as sqr

os testes continuam passando.

domingo, 13 de março de 2011

Decoradores em Lua

@



Um recurso extremamente útil em Python é o uso de decoradores.

Por exemplo, um decorador que memoíze o resultado de uma função:

def memo(func, buffer={}):
def wrapper(*args):
if args in buffer:
response = buffer[args]
else:
response = buffer[args] = func(*args)
return response

return wrapper


Assim funções como o cálculo recursivo da sequência de Fibonacci se tornam menos custosas:
@memo
def fibonacci(n):
if n < 2:
return 1
else:
return fibonacci(n - 2) + fibonacci(n - 1)


Observação: memoização consome muita memória!

É possível ver que, em Python, decoradores são extremamente fáceis de se implementar e usar, o que permite usos bastantes complexos, como a orientação a aspecto.

Lua também suporta decoradores, mas de uma forma um pouco menos intuitiva, mais complexa e totalmente macetada.

Para a criação de decoradores em Lua, é usada a sobrecarga do operador de concatenação, como sugerido na literatura de referência.

Para criarmos então o decorador de memoização, precisamos de uma metatabela que sobrecarregue o operador:
local mt = {
__concat = function(self, func)
return function(...)
local response
local args = serialize(...)
if self[args] then
response = self[args]
else
response = func(...)
self[args] = response
end
return response
end
end,
}


A função serialize() não existe, então temos de criá-la:
local function serialize(...)
local response = ""
local i, v
for i, v in ipairs {...} do
response =
response .. "[" .. ("%g"):format(i) .. "] = " ..
("%g"):format(v) .. ", "
end
return response
end


Agora criamos o decorador:
function memo()
return setmetatable({}, mt)
end


Isso tudo pode ser colocado em módulo, chamado memo.lua por exemplo. Na última linha do módulo coloque:
return memo


Agora, para usar o decorador:
require "memo"

fibonacci =
memo() ..
function(n)
if n < 2 then
return 1
else
return fibonacci(n - 2) + fibonacci(n - 1)
end
end


O uso do decorador está na linha: memo() .. – a sintaxe não é tão elegante quanto a de Python – @memo –, mas funciona da mesma forma, inclusive a criação de cadeias de decoradores.

A criação de decoradores em Lua também não é tão intuitiva e simples quanto em Python, em função da diferença de filosofias entre as duas linguagens, mas com o hábito se torna um recurso poderoso.

[]’s
Cacilhας, La Batalema

sábado, 27 de novembro de 2010

Flask

Flask Há diversos frameworks em Python para Web, Django, Zope, web2py, Pyramid e TurboGears e Pylons.

Venho aqui para falar de um microframework chamado Flask.

Simples, flexível e poderoso, é capaz de manter desde o projeto mais simples até CMS no estilo Django ou grandes aplicações como em Pylons.

A instalação é também extremamente simples, já que se encontra no PyPI:
bash$ easy_install Flask


Como exemplo vou mostrar uma aplicação ao estilo Flask, registrando as URLs com decorador route, mas para aplicações mais complexas sugiro criar um arquivo url_rules.py que importe o objeto aplicação e use o método add_url_rule em vez do decorador.

Nossa aplicação simples exibirá o conteúdo dos arquivos em /proc/ dos sistemas Unix (por falta de ideia melhor).

Podemos começar importanto os recursos necessários:
from flask import Flask, make_response


  • Flask: classe da aplicação
  • make_response: função capaz de criar um objeto de resposta HTTP


Para a leitura dos arquivos e diretórios vamos precisar do módulo os:
import os


Vamos agora criar duas funções, uma que retorne a listagem de arquivos para um diretório e outra que retorne o conteúdo de um arquivo:
def list_dir(path):
filelist = os.listdir(path)
filelist.sort()
return '\n'.join(filelist)


def read_file(path):
data = 'NO DATA'
with open(path, 'r') as fd:
data = fd.read()
return data


Agora é possível criar a aplicação:
app = Flask(__name__)
app.debug = True


Podemos então criar uma função que receba o caminho do arquivo/diretório a ser lido e retorne para o navegador.

Caso seja necessário editar os cabeçalhos ou lidar com qualquer peculiaridade do objeto de resposta HTTP, é possível instanciá-lo manualmente. Caso contrário, retornar uma simples string já faz com que ela seja entendida como o conteúdo do objeto.

Se uma tupla for retornada, o primeiro valor será o conteúdo e o segundo o status de retorno.

Sabendo disso, podemos definir a função:
@app.route('/<path:filename>/')
def read_proc(filename):
real_path = os.path.join('/proc', filename)
data = 'NO DATA'

try:
if os.path.isfile(real_path):
data = read_file(real_path)

elif os.path.isdir(real_path):
data = list_dir(real_path)

else:
return 'NOT FOUND', 404

# Criando HTTP response apenas para editar headers
response = make_response(data)
response.headers['Content-Type'] = 'text/plain'
return response

except IOError:
# Você não pode ler este arquivo/diretório!
return 'BAD REQUEST', 400


A rota indica que o argumento filename receberá como parâmetro toda a URL passada (path: indica uma string com /).

Para a raiz da aplicação, poderíamos usar as funções redirect e url_for, por exemplo:
@app.route('/')
def index():
return redirect(url_for('read_proc', filename='cpuinfo'))


O que redirecionaria para a URL referente à função read_proc, passando 'cpuinfo' como parâmetro, mas faz mais sentido retornar o resultado da função:
@app.route('/')
def index():
return read_proc('')


Agora só falta colocar a aplicação para rodar:
if __name__ == '__main__':
app.run(host='0.0.0.0')


Execute o script e acesse no navegador: http://localhost:5000/ – você verá a listagem do diretório /proc/.

Acesse no navegador: http://localhost:5000/cpuinfo/ e você verá o conteúdo do arquivo /proc/cpuinfo.

Pretendo em uma próxima oportunidade demonstrar com construir uma estrutura mais complexa usando um arquivo com roteamente em vez de decorador.

Para mais diversão, leia a documentação e fascine-se também com a quantidade de vezes que a expressão «you don’t have to deal with that» se repete.

[]’s
Cacilhας, La Batalema

sexta-feira, 5 de março de 2010

Python é capa da TIdigital de Março

Gente,

Segunda-feira todo mundo correndo pras bancas!
TIdigital Março

Em março, Python em foco na revista TIdigital.

Fonte: PythOn Rio.

[]’s
Cacilhας, La Batalema

sábado, 27 de fevereiro de 2010

MongoDB

Logo Há atualmente na Computação uma onda de adoção de bancos de dados não-relacionais, comumente chamados NoSQL.

O foco principal dos bancos NoSQL atuais é a orientação a documentos, uma variação hash da orientação a objetos e alternativa aos RDBMSs, que dominaram a orientação dos gerenciadores de banco de dados por décadas.

O principal banco NoSQL é o CouchDB, DBMS do Projeto Apache baseado em Erlang, porém há uma alternativa bem mais simples e aparentemente tão poderosa quanto: MongoDB.

MongoDB combina os melhores funcionalidades de orientação a documentos, hashes e RDBMSs. É um banco de dados orientado a documentos, escalável, livre de esquema, de alto desempenho e código aberto escrito em C++.

Este artigo dá continuidade ao artigo nas Reflexões de Monte Gasppa e Giulia C. sobre a instalação do MongoDB. Aqui você poderá ver um pouquinho do uso de um banco de dados orientado a documentos.

Este artigo é baseado na documentação oficial e entende que você instalou o MongoDB segundo descrito nas Reflexões de Monte Gasppa e Giulia C..

Conexão na base de dados


Para conectar na base basta usar o comando mongo:
bash$ mongo
MongoDB shell version: 1.2.2
url: test
connecting to: test
type "help" for help
>


Caso você tenha configurado uma porta alternativa em /srv/mongodb/etc/mongodb.conf, você pode especificar isso na linha de comando do cliente:
bash$ mongo --port 27018


Você também pode se conectar a um servidor remoto, caso ele esteja habilitado para conexão externa, usando a flag --host.

Para escolher qual base de dados usar, o comando é use:
> use mydb


Autorização


Você pode se autorizar na linha de comando com as opções -u (usuário) e -p (senha), ou já do prompt do interpretador:
> use admin
> db.auth('admin', 'ra35EG/dz');


Comandos básicos


A linguagem de interação com MongoDB é Javascript, mas há alguns comandos úteis no interpretador.

Para listar comandos úteis:
> help


Listar recursos uteis da base atual:
> db.help();


Listar recursos uteis de uma coleção (levemente equivalente a tabela de um banco relacional), no exemplo foo:
> db.foo.help();


Para listar as bases instaladas:
> show dbs


Para listar as coleções da base atual:
> show collections


Listar usuários da base atual:
> show users


Criar usuário na base atual:
> db.addUser('admin', 'ra35EG/dz');


Autenticar-se:
> db.auth('admin', 'ra35EG/dz');


Listar o conteúdo de uma coleção:
> db.foo.find();


Apagar uma coleção:
> db.foo.drop();


Apagar a base de dados atual:
> db.dropDatabase();


Armazenamento de dados


Repare que o MongoDB é livre de esquema, portanto uma coleção pode conter estruturas de dados radicalmente diferentes, sempre no formato JSON.

Por exemplo, vamos inserir dois dados distintos na coleção foo da base mydb:
> use mydb
> var j = { name: 'mongo' };
> var t = { x: 3 };
> db.foo.save(j);
> db.foo.save(t);
> db.foo.find();
{ "_id" : ObjectId("4b8949d9cd50237b8573833b"), "name" : "mongo" }
{ "_id" : ObjectId("4b8949dbcd50237b8573833c"), "x" : 3 }


Você não precisa predefinir a coleção, que é criada automaticamente no salvamento da primeira estrutura. O campo _id é criado automaticamente e é a chave primária.

Para apagar o objeto j:
> j = db.foo.findOne({ name: 'mongo' });
> db.foo.remove(j);


Para apagar todos os objetos basta remover a coleção:
> db.foo.drop();


Vamos usar um pouco mais de lógica:
> for (var i=0; i<100; ++i) db.foo.save({ x: i, y: i*i });


Esse loop cria cem registros, com x variando de zero a noventa e nove e y igual a seu quadrado:
> db.foo.find();
{ "_id" : ObjectId("4b894c05cd50237b8573833e"), "x" : 0, "y" : 0 }
{ "_id" : ObjectId("4b894c05cd50237b8573833f"), "x" : 1, "y" : 1 }
{ "_id" : ObjectId("4b894c05cd50237b85738340"), "x" : 2, "y" : 4 }
{ "_id" : ObjectId("4b894c05cd50237b85738341"), "x" : 3, "y" : 9 }
{ "_id" : ObjectId("4b894c05cd50237b85738342"), "x" : 4, "y" : 16 }
{ "_id" : ObjectId("4b894c05cd50237b85738343"), "x" : 5, "y" : 25 }
{ "_id" : ObjectId("4b894c05cd50237b85738344"), "x" : 6, "y" : 36 }
{ "_id" : ObjectId("4b894c05cd50237b85738345"), "x" : 7, "y" : 49 }
{ "_id" : ObjectId("4b894c05cd50237b85738346"), "x" : 8, "y" : 64 }
{ "_id" : ObjectId("4b894c05cd50237b85738347"), "x" : 9, "y" : 81 }
{ "_id" : ObjectId("4b894c05cd50237b85738348"), "x" : 10, "y" : 100 }
{ "_id" : ObjectId("4b894c05cd50237b85738349"), "x" : 11, "y" : 121 }
{ "_id" : ObjectId("4b894c05cd50237b8573834a"), "x" : 12, "y" : 144 }
{ "_id" : ObjectId("4b894c05cd50237b8573834b"), "x" : 13, "y" : 169 }
{ "_id" : ObjectId("4b894c05cd50237b8573834c"), "x" : 14, "y" : 196 }
{ "_id" : ObjectId("4b894c05cd50237b8573834d"), "x" : 15, "y" : 225 }
{ "_id" : ObjectId("4b894c05cd50237b8573834e"), "x" : 16, "y" : 256 }
{ "_id" : ObjectId("4b894c05cd50237b8573834f"), "x" : 17, "y" : 289 }
{ "_id" : ObjectId("4b894c05cd50237b85738350"), "x" : 18, "y" : 324 }
{ "_id" : ObjectId("4b894c05cd50237b85738351"), "x" : 19, "y" : 361 }
has more


Consultas (queries)


É possível fazer consultas simples ou bem complexas assim como em modelos relacionais usando MongoDB.

Vamos obter uma contagem de objetos:
> db.foo.find().count();
100


Só os cinco primeiros objetos:
> db.foo.find().limit(5);
{ "_id" : ObjectId("4b894c05cd50237b8573833e"), "x" : 0, "y" : 0 }
{ "_id" : ObjectId("4b894c05cd50237b8573833f"), "x" : 1, "y" : 1 }
{ "_id" : ObjectId("4b894c05cd50237b85738340"), "x" : 2, "y" : 4 }
{ "_id" : ObjectId("4b894c05cd50237b85738341"), "x" : 3, "y" : 9 }


Agora os cinco seguintes:
> db.foo.find().skip(5).limit(5);
{ "_id" : ObjectId("4b894c05cd50237b85738343"), "x" : 5, "y" : 25 }
{ "_id" : ObjectId("4b894c05cd50237b85738344"), "x" : 6, "y" : 36 }
{ "_id" : ObjectId("4b894c05cd50237b85738345"), "x" : 7, "y" : 49 }
{ "_id" : ObjectId("4b894c05cd50237b85738346"), "x" : 8, "y" : 64 }
{ "_id" : ObjectId("4b894c05cd50237b85738347"), "x" : 9, "y" : 81 }


Para obter o elemento com valor de x vinte e quatro:
> var e = db.foo.findOne({ x: 24 });
> print(e.y);
576
> print(tojson(e));
{ "_id" : ObjectId("4b894c05cd50237b85738356"), "x" : 24, "y" : 576 }


Consultas avançadas


Vamos complicar um pouco…

Para obter todos os registros com x maior que cinquenta:
> db.foo.find('this.x > 50');
{ "_id" : ObjectId("4b894c05cd50237b85738371"), "x" : 51, "y" : 2601 }
{ "_id" : ObjectId("4b894c05cd50237b85738372"), "x" : 52, "y" : 2704 }
{ "_id" : ObjectId("4b894c05cd50237b85738373"), "x" : 53, "y" : 2809 }
{ "_id" : ObjectId("4b894c05cd50237b85738374"), "x" : 54, "y" : 2916 }
{ "_id" : ObjectId("4b894c05cd50237b85738375"), "x" : 55, "y" : 3025 }
{ "_id" : ObjectId("4b894c05cd50237b85738376"), "x" : 56, "y" : 3136 }
{ "_id" : ObjectId("4b894c05cd50237b85738377"), "x" : 57, "y" : 3249 }
{ "_id" : ObjectId("4b894c05cd50237b85738378"), "x" : 58, "y" : 3364 }
{ "_id" : ObjectId("4b894c05cd50237b85738379"), "x" : 59, "y" : 3481 }
{ "_id" : ObjectId("4b894c05cd50237b8573837a"), "x" : 60, "y" : 3600 }
{ "_id" : ObjectId("4b894c05cd50237b8573837b"), "x" : 61, "y" : 3721 }
{ "_id" : ObjectId("4b894c05cd50237b8573837c"), "x" : 62, "y" : 3844 }
{ "_id" : ObjectId("4b894c05cd50237b8573837d"), "x" : 63, "y" : 3969 }
{ "_id" : ObjectId("4b894c05cd50237b8573837e"), "x" : 64, "y" : 4096 }
{ "_id" : ObjectId("4b894c05cd50237b8573837f"), "x" : 65, "y" : 4225 }
{ "_id" : ObjectId("4b894c05cd50237b85738380"), "x" : 66, "y" : 4356 }
{ "_id" : ObjectId("4b894c05cd50237b85738381"), "x" : 67, "y" : 4489 }
{ "_id" : ObjectId("4b894c05cd50237b85738382"), "x" : 68, "y" : 4624 }
{ "_id" : ObjectId("4b894c05cd50237b85738383"), "x" : 69, "y" : 4761 }
{ "_id" : ObjectId("4b894c05cd50237b85738384"), "x" : 70, "y" : 4900 }
has more


Outra opção é usar o operador condicional $gt:
> db.foo.find({ x: { $gt: 50 } });


Da mesma forma outros operadores. Por exemplo, todos os elementos com x entre vinte e três e vinte e seis, inclusive:
> db.foo.find('23 <= this.x && this.x <= 26');


Ou:
> db.foo.find({ x: { $gte: 23, $lte: 26 } });


Os principais operadores são:
  • $lt – menor que
  • $gt – maior que
  • $lte – menor ou igual a
  • $gte – maior ou igual a
  • $ne – diferente de
  • $in – está em (recebe uma lista)
  • $nin – não está em
  • $mod – resto igual a (recebe uma lista onde o primeiro valor é o divisor e o segundo o resto)
  • $exists – contém ou não o atributo
  • $not – negação de uma condição


Ordenação


A ordenação é feita usando o método sort. Por exemplo, para ordenar de modo descrescente pelo atributo y:
> db.foo.find().sort({ y: -1 });


Expressões regulares

Quando há campos com valor string, é possível usar expressões regulares:
> db.foo.find({ name: /^mon.o$/i });


Índices

Quando você faz muitas consultas baseadas em um determinado atributo, é interessante criar um índice para ele. Por exemplo, se quisermos criar um índice para nosso atributo x:
> db.foo.ensureIndex({ x: 1 });


Obs.¹: o atributo _id possui índice por padrão.

Obs.²: é possível criar índices compostos.

Agregação


Não vou entrar nesse mérito, mas é possível criar agregações, como GROUP_BY de SQL. Para tanto veja a documentação oficial.

Módulos de conexão com linguagens de programação


Há módulos para várias linguagens, que podem ser encontrados na lista de APIs.

O módulo que pude e me fascinou foi para Python. Sua instalação usando easy_install foi muito simples:
bash$ sudo easy_install -U pymongo


E seu uso extremamente fácil devido à similiaridade entre dicionários de Python e JSON. A única reclamação que faço é o fato do método db.auth() do MongoDB ter sido traduzido como db.authenticate() em Python, o que me fez perder alguns segundos tentando entender por que não funcionava.

**


Espero que este artigo seja mais do que útil e incite o uso dessa intrigante ferramenta.

[]’s
Cacilhas, La Batalema