About me

About

2014-06-29

Pythonices, throttling.

Problema: precisamos de executar uma acção sujeita a limites temporais, não mais que n execuções por segundo são permitidas. Chamemos a isto throttle.

Como boa prática a função de throttle deve estar separada da acção f a executar. Uma abordagem funcional é perfeita: f = throttle(count, timeframe, f), tal que a função retornada por throttle() tenha o mesmo inteface que f.

Isto é precisamente o que fazem os decoradores em python.

Eis a minha implementação desta funcionalidade.

import datetime

def throttle(count, timeframe):  # timeframe in seconds
    def work(fn):
        # state is lasttimemarker, remaining count
        state = [datetime.datetime.now(), count]

        def __work(*args, **kwargs):
            elapsed = datetime.datetime.now() - state[0]
            ntimeframes = int(elapsed.total_seconds() // timeframe)
            if ntimeframes > 0: ## just an optimization
                state[1] += count * ntimeframes
                state[0] += datetime.timedelta(seconds=timeframe) * ntimeframes
            if state[1] <= 0:
                wait_time = timeframe - elapsed.total_seconds() % timeframe
                time.sleep(wait_time)
                return __work(*args, **kwargs)
            state[1] -= 1
            rv = fn(*args, **kwargs)
            return rv
        return __work
    return work

####

import time

# exec 3 times on each 5 seconds timeframe
@throttle(3, 5)
def printtime():
    print time.ctime(time.time())
    
for i in range(10): printtime()
Sat Jun 28 17:16:08 2014
Sat Jun 28 17:16:08 2014
Sat Jun 28 17:16:08 2014
Sat Jun 28 17:16:13 2014
Sat Jun 28 17:16:13 2014
Sat Jun 28 17:16:13 2014
Sat Jun 28 17:16:18 2014
Sat Jun 28 17:16:18 2014
Sat Jun 28 17:16:18 2014
Sat Jun 28 17:16:23 2014

2014-06-28

Pythonices, enumerate.

Uma idiossincrasia comum em python é iterar com ciclos for.

xs = "a b c d".split()
for x in xs:
    print x
a
b
c
d

Mas por vezes é necessário ter um contador associado. A solução mais imediata é manter um contador à parte e incrementá-lo dentro do loop. Por vezes é necessário trocar o for por um while.

Felizmente existe uma construção nativa que permite associar uma contagem a qualquer iterador: enumerate.

xs = "a b c d".split()
for i,x in enumerate(xs):
    print i,x
0 a
1 b
2 c
3 d

O enumerate é a aplicação de uma técnica comum em linguagens funcionais: transformar uma lista de valores (escalares) numa lista de tuples, juntando a informação adicional no outro membro do tuple.

Repare-se que o enumerate corresponde à aplicação de duas funções do módulo itertools.

import itertools
xs = "a b c d".split()
for i,x in itertools.izip(itertools.count(), xs):
    print i,x
0 a
1 b
2 c
3 d

2013-10-15

Pythonices, simplificar objectos de dados.

Um objecto de dados só tem estado, não tem comportamento. Ou traduzindo para python: são instâncias de classes sem métodos, apenas com variáveis.

Formas de definir classes de dados.

Auto iniciar locais a partir dos argumentos

É a solução clássica e mais explícita:

class P:
    def __init__(self, sln, dll=None, aspnet=None): 
        self.sln = sln
        self.dll = dll
        self.aspnet = aspnet

Solução dinâmica

Compacta, python voodoo.

class P:
    def __init__(self, sln, dll=None, aspnet=None): self.__dict__.update(locals())

Ou para ser-se pedante

Falta remover o self do próprio dicionário.

class P:
    def __init__(self, sln, dll=None, aspnet=None): self.__dict__.update(locals()); del self.self

E agora uma coisa completamente diferente: namedtuple

Continuando no exemplo acima, mas desta vez sem defaults.

from collections import namedtuple
P = namedtuple("P", "sln dll aspnet".split())

p = P("xpto.sln", "xpto.dll", None)
print p

# recuperando a solucao classica
class Pc:
    def __init__(self, sln, dll=None, aspnet=None): 
        self.sln = sln
        self.dll = dll
        self.aspnet = aspnet

    def __str__(self): return str([self.sln, self.dll, self.aspnet])

p = Pc("xpto.sln", "xpto.dll", None)
print p

Tem como resultado:

P(sln='xpto.sln', dll='xpto.dll', aspnet=None)
['xpto.sln', 'xpto.dll', None]

Resumindo:

  • o namedtuple fornece uma forma simples e legível de definir objectos de dados simples;
  • as formas compactas são úteis para prototipagem.

2013-10-08

Ubíquo Word.

O Word não só matou a documentação técnica como tomou o mundo da documentação de assalto. Desde a composição de documentos de notas com 3 ou 4 linhas, a livros com centenas de páginas, o seu uso é universal. Como as pessoas se habituaram ao Word, exigem receber documentos de Word. A situação chega a ser tão caricata como os seguintes casos reais:
  • Cerca do ano 2000 enviei o meu CV em PDF a uma conhecida empresa. Responderam-me a dizer que não o conseguiam abrir – na altura o acrobat reader não estava muito disseminado – e que eu enviasse logo a versão “Word”. Algo impossível tendo em conta que eu não tinha usado sequer o Word para o produzir1.
  • Há uns anos enviei um artigo a um colega meu para revisão. Ele respondeu, algo frustrado, que tinha tido muito trabalho a editar o meu artigo porque eu não estava a usar um formato standard, e recomendou-me que da próxima vez o fizesse, que enviasse um doc de Word. O formato que lhe tinha enviado era HTML.
  • Num sítio onde trabalhei havia um processo manual para fazer backups. Eu estava lá contratado para fazer administração de sistemas, mas aproveitei algum tempo livre para desenhar e desenvolver um software que automatizava o sistema de backups. O cliente ficou satisfeito com esse sistema novo mas alertou que não havia documentação. Compilei num README.txt toda a informação técnica sobre esse sistema. Como estava construído, como se instalava no master e nos slaves, como se corria… no dia seguinte mostrei o ficheiro de texto ao cliente. Instantaneamente vira-se para mim e diz: “Isto não é nada” (mesmo assim). Perguntei porquê, o que esperava ter encontrado que não estivesse no documento. Olhou-me de forma estranha, hesitou um pouco e disse que aquilo não era um documento. Apenas. Na realidade ele esperava a mesma informação só que num ficheiro Word. Um ficheiro de texto, apesar de conter o mesmo conteúdo, não era considerado “um documento”.
  • Por fim, ainda me recordo de algumas coisas me terem caído ao chão – e não vou detalhar se em sentido alegórico ou não – quando vi este requisito por parte de uma empresa pública: “O adjudicatário entregará à entidade adjudicante (…) a seguinte documentação em suporte digital (.pdf e .doc/.ppt/.rtf) e em papel: (…)”.

  1. tinha usado groff.

2013-10-07

15 anos de desenvolvimento de software.

Ao longo de 15 anos na área do desenvolvimento de software, sobretudo no mundo das telcos, eis o cenário que pinto sobre o estado de arte nacional.

  1. Não se faz 100% desenvolvimento: ao longo do tempo acabam por se fazer coisas tão díspares como administração de sistemas, coaching, entrevistas, gestão de projectos, burocracia e, sobretudo, pré-venda. Varia com cada um, mas há sempre desvios. Razões para isto?
    • não existe mercado em Portugal para manter uma função exclusiva de desenvolvimento;
    • o desenvolvimento feito em Portugal tipicamente é básico, logo pode ser feito por juniores, logo…
    • não há carreira técnica;
    • não há investimento em inovação, quanto mais em pesquisa;
    • a inovação existente resulta da utilização ou colocação em prática de novos softwares ou práticas vindas de fora, e.g. inovação por compra de novos produtos ou upgrade de versões.
  2. A estratégia de TI empresarial está sujeita aos fornecedores tecnológicos.

  3. O desenvolvimento nacional é pouco ambicioso e simples: a maior parte dos projectos desenvolvidos de raiz são uma implementação básica – ainda que por vezes massiva e/ou complicada – de um modelo source-process-dump. E.g. boa parte das aplicações web.

  4. As necessidades de software mais exigente, do ponto de vista de complexidade, são normalmente supridas por soluções 3rd party.

  5. Os projectos atrasam-se ou correm mal raramente por questões técnicas – já que são simples q.b. – mas por:
    • requisitos mal especificados inicialmente e alterados de forma inconsistente – continuando mal especificados – ao longo do projecto;
    • problemas de integração com outros sistemas;
    • indisponibilidade de ambientes e dados.
  6. A tecnologia escolhida tem pouco impacto no projecto e é irrelevante para a organização: mesmo que outras linguagens e plataformas melhorassem 10x a produtividade no desenvolvimento e deploy, esse tempo seria diluído nas ineficiências gerais do projecto.

  7. A falta de qualidade do código é suprida via testes, ou seja, prática de code-and-fix.

  8. Os programadores juniores apresentam lacunas, que são impeditivas de realizar software bem construído embora suficientes para realizar os nossos projectos básicos (ainda que sem uma boa construção), em:
    • estruturação;
    • abstracção;
    • automatização;
    • scripting e prototipagem.

    As práticas de código mais comuns baseiam-se em excesso de programação imperativa mal estruturada com abuso de copy paste. Há evidência baseada em estudos que estes factores não melhoram com a experiência.

2013-10-05

Reuniões como promoção de visibilidade.

«Additionally, meetings give visibility, an essential factor to anyone who hopes to rise in big-company hierarchy. You don’t get noticed by listening thoughtfully, so anyone who’s there for visibility is likely to be a talker. The worst meetings feel like congregations of windbags with nobody listening and everybody speaking or waiting to speak. Because there are so many who need to speak, meeting duration increases seemingly without bound.»

– “Meetings, Monologues, and Conversations” in Peopleware: Productive Projects and Teams (3ª edição), Tom DeMarco, Timothy Lister

Custa assim tanto perceber?!

Esta passagem deste magnífico livro fez-me lembrar um episódio real. Há cerca de 10 anos participei num grande projecto. Inicialmente tinha sido contratado um consultor da Microsoft, temporariamente, para arrancar com a solução técnica, mas como ninguém da equipa inicial percebia o que ele estava a fazer, integraram-me no projecto para acompanhar e dar continuidade ao seu trabalho. Rapidamente me tornei o “arquitecto” de facto do projecto e um dos seus grandes motores, sobretudo a nível de inovação.

Esse projecto – bem como o meu papel no mesmo – veio recentemente a tema com uma pessoa que também tinha estado envolvida no mesmo, embora como parte interessada, externa à equipa. Ambos desconhecíamos o envolvimento do outro. A reacção dele: “Estiveste no projecto? Não me lembro de ti nas reuniões!Q.E.D.

2013-10-04

Pythonices, obter vários valores de uma lista.

Objectivo: processar grupos de 3 linhas seguidas de um ficheiro.

Soluções…

Juntar as 3 no loop usando informação de estado

with file("pythonices") as f:
    lines = []
    for line in f:
        lines.append(line)
        if len(lines) == 3:
            process(lines)
            del lines[:]

A lista lines é usada para manter a contagem das linhas lidas. Chegando a 3, processa-as.

  • a lógica de juntar as linhas torna o ciclo pouco claro;
  • obriga a manter estado explícito.

Obter as 3 dentro do ciclo

with file("pythonices") as f:
    while 1: 
        try: 
            l1 = next(f)
            l2 = next(f)
            l3 = next(f)
            process([l1, l2, l3])
        except StopIteration: 
            break
  • a obtenção de linhas é agora linear;
  • a construção é mais simples;
  • mas usa-se o bloco try-except para controlar a paragem do ciclo.

Alterando a source para fornecer logo as 3 linhas

with file("pythonices") as f:
    for l1,l2,l3 in zip(f,f,f): 
        process([l1,l2,l3])

Mais simples e legível.

E se o ficheiro não for múltiplo de 3? Os casos anteriores descartam as linhas extras. Com a última solução podemos controlar esse comportamento facilmente mexendo apenas na “fonte”. Neste caso usando itertools.izip_longest que funciona como o zip, só que convertendo os valores extra – caso os haja – num valor pré-definido.

import itertools

with file("pythonices") as f:
    for l1,l2,l3 in itertools.izip_longest(f,f,f, fillvalue=None): 
        process([l1,l2,l3])

A vantagem desta solução face às anteriores foi ter separado e abstraído a tarefa em duas instâncias: a obtenção das fontes e o seu processamento.

Criando funções que representam explicitamente as abstracções fica:

import itertools

def process(x): print x

def consume(f): 
    for l1,l2,l3 in itertools.izip_longest(f,f,f, fillvalue=None): 
        yield l1,l2,l3

with file("pythonices") as f:
    for l1,l2,l3 in consume(f): 
        process([l1,l2,l3])

De notar que não foram as funções que criaram as abstracções. As funções são estrutura. A primeira solução desta secção tem o mesmo nível de abstracção que esta última. Posso voltar no futuro ao tema abstracção.