GMGall’s blog

Programação, Linux e o que mais der na telha

Arquivos da Categoria: Python

Daemon em Python

Já usei mais de uma vez o código abaixo para criar pequenos daemons para Linux. Achei aqui e como ele foi liberado em domínio público, use como melhor convir.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys, os, time, atexit
from signal import SIGTERM 

class Daemon:
    """
    A generic daemon class.
    
    Usage: subclass the Daemon class and override the run() method
    """
    def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = pidfile
    
    def daemonize(self):
        """
        do the UNIX double-fork magic, see Stevens' "Advanced 
        Programming in the UNIX Environment" for details (ISBN 0201563177)
        http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
        """
        try: 
            pid = os.fork() 
            if pid > 0:
                # exit first parent
                sys.exit(0) 
        except OSError, e: 
            sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)
    
        # decouple from parent environment
        os.chdir("/") 
        os.setsid() 
        os.umask(0) 
    
        # do second fork
        try: 
            pid = os.fork() 
            if pid > 0:
                # exit from second parent
                sys.exit(0) 
        except OSError, e: 
            sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1) 
    
        # redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        si = file(self.stdin, 'r')
        so = file(self.stdout, 'a+')
        se = file(self.stderr, 'a+', 0)
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())
    
        # write pidfile
        atexit.register(self.delpid)
        pid = str(os.getpid())
        file(self.pidfile,'w+').write("%s\n" % pid)
    
    def delpid(self):
        os.remove(self.pidfile)

    def start(self):
        """
        Start the daemon
        """
        # Check for a pidfile to see if the daemon already runs
        try:
            pf = file(self.pidfile,'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None
    
        if pid:
            message = "pidfile %s already exist. Daemon already running?\n"
            sys.stderr.write(message % self.pidfile)
            sys.exit(1)
        
        # Start the daemon
        self.daemonize()
        self.run()

    def stop(self):
        """
        Stop the daemon
        """
        # Get the pid from the pidfile
        try:
            pf = file(self.pidfile,'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None
    
        if not pid:
            message = "pidfile %s does not exist. Daemon not running?\n"
            sys.stderr.write(message % self.pidfile)
            return # not an error in a restart

        # Try killing the daemon process    
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        except OSError, err:
            err = str(err)
            if err.find("No such process") > 0:
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
            else:
                print str(err)
                sys.exit(1)

    def restart(self):
        """
        Restart the daemon
        """
        self.stop()
        self.start()

    def run(self):
        """
        You should override this method when you subclass Daemon. It will be called after the process has been
        daemonized by start() or restart().
        """

Resolvi adicionar a funcionalidade de verificar o status do processo também, daí adicionei o seguinte método à classe acima:

def status(self):
    try:
        pf = file(self.pidfile, 'r')
        pid = int(pf.read().strip())
        pf.close()
    except IOError:
        pid = None

    try:
        procfile = file("/proc/%d/status" % pid, 'r')
        procfile.close()
    except IOError:
        sys.stdout.write("there is not a process with the PID specified in %s\n" % self.pidfile)
        sys.exit(0)
    except TypeError:
        sys.stdout.write("pidfile %s does not exist\n" % self.pidfile)
        sys.exit(0)

    sys.stdout.write("the process with the PID %d is running\n" % pid)

Não ficou muito bonito, mas funciona. Lembrando o que já está comentado no código original: para usar a classe acima para “daemonizar” seu programa, crie uma classe filha de Daemon e sobrescreva o método run(). Ele será chamado após o processo de “daemonização” por start() e restart().

Anúncios

Sequência look and say em Python

Tenho brincado ultimamente com os desafios do Python Challenge. São bem interessantes para quem quer aprender Python na prática. Estou resolvendo o nível 11 e já precisei processar imagens, descompactar dados comprimidos com zip e bz2, serializar objetos, acessar recursos via URL, usar expressões regulares e algumas tarefas que não exigiam necessariamente um módulo.

O último nível que resolvi tinha como resposta o comprimento de um elemento específico de uma sequência de inteiros conhecida como look and say (olhe e descreva). Achei diversas implementações da geração da sequência pela web, nenhuma delas me pareceu pythonica ou legível o suficiente. Resolvi juntar algumas das idéias que vi nessas implementações com os conhecimentos que obtive recentemente com a leitura de um material sobre programação funcional em Python e criei uma função que retorna uma lista com os elementos da sequência. A minha solução não é a mais eficiente possível (essa página contém uma implementação alegadamente otimizada para velocidade), mas acho que é elegante e mostra alguns aspectos interessantes da linguagem. Segue o código:

def look_and_say(first, elements):
    from itertools import groupby
    seq = [str(first)]

    def say(number):
        ret = []
        for k,g in groupby(number):
            ret.append( str(len(list(g))) + k )
        return ''.join(ret)

    for i in xrange(elements):
        seq.append(say(seq[-1]))
    return seq

O argumento first de look_and_say recebe o primeiro elemento da lista e elements quantos elementos depois do primeiro devem ser gerados. Exemplo de uso:

>>> look_and_say(1,10)
['1', '11', '21', '1211', '111221', '312211', '13112221', '1113213211', '31131211131221', '13211311123113112211', '11131221133112132113212221']
>>> look_and_say(55,4)
['55', '25', '1215', '11121115', '31123115']

Dentre os aspectos que gostaria de destacar estão a possibilidade de importar partes de módulos e definir funções dentro de funções, o uso de listas para concatenar strings com eficiência e o uso de itertools.groupby que faz um agrupamento semelhante ao do comando uniq do Unix, juntando elementos iguais consecutivos em um iterator.

Para quem quiser uma abordagem matemática da sequência, o Wolfram Mathworld tem uma página sobre ela.

Metaclasses em Python

Introdução

Li dois textos interessantes no Kodumaro recentemente: um sobre propriedades e acessores e outro sobre o design pattern singleton. Ambos citavam as metaclasses, um conceito novo para mim e, pelo que andei conversando, novo para muitos de meus colegas de faculdade e trabalho. O seguinte texto é resultado de minha tentativa de explicar o que são metaclasses de uma forma simples de ser assimilada por pessoas que começaram a estudar Python há pouco tempo como eu e portanto não pode ser considerado como um guia definitivo e sem erros sobre o assunto. Qualquer sugestão ou comentário é bem vindo.

Revisando Orientação a Objetos e definindo metaclasses

Em uma linguagem de programação orientada a objetos, você pode definir classes que combinam dados (atributos) e métodos (comportamentos) que atuam sobre esses dados. Pode-se afirmar que ao definir uma classe se está definindo um domínio.

As classes geralmente atuam como modelos para a criação de instâncias de classe, que apesar de compartilharem uma mesma “forma”, possuem dados diferentes. Uma instância real e outra peso de uma mesma classe Moeda podem possuir um atributo cotacao, mas cada instância terá seu próprio valor para esse atributo.

Em Python, tudo é objeto e tudo tem um tipo. Não existe diferença real entre uma classe e um tipo, especialmente depois da unificação de classes e tipos iniciada no Python 2.2. Ao definir uma classe, portanto, o programador está criando um novo tipo de dados. Como tudo é objeto, as classes também o são e possuem uma classe, um tipo. A classe de uma classe é chamada de metaclasse. O tipo default de uma classe é type.

  >>> class Moeda(object):
  ...    pass
  ...
  >>> type(Moeda)
  <type 'type'>
  >>> Moeda
  <class '__main__.Moeda'>

Ao chamar type() com apenas um argumento, obtém-se o tipo do objeto. Observe que o tipo da classe Moeda é type. Afirmar que o tipo de uma classe é type é o mesmo que afirmar que a sua metaclasse é type. O exemplo acima comprovou que as classes realmente são um objeto com um tipo.

Os conceitos apresentados até aqui são um tanto abstratos para quem aprendeu orientação a objetos em linguagens como Java e C# e para os novatos em Python. No tópico seguinte, apresento a implementação de uma classe e de uma metaclasse com dicionários, como se Python não tivesse suporte sintático para OO. Apesar de ninguém querer programar assim na prática o exemplo é extremamente útil para entender como os objetos funcionam em Python.

Implementando classes com dicionários

O código abaixo foi originalmente postado no blog de Pedro Werneck em um post que explicava o porquê do self explícito. Apesar de explicar metaclasses não ser o objetivo principal do texto, achei bastante útil para entender como o suporte a orientação a objetos foi implementado em Python. Isso acaba levando ao entendimento de vários conceitos interessantes (entre eles metaclasses) da linguagem e a uma apreciação ainda maior de sua simplicidade e consistência.

No post original, o código evoluiu aos poucos até chegar ao ponto em que está aqui. Para um entendimento pleno, leia o post original.

A primeira parte define uma função newClass que recebe um nome de classe e um dicionário com seus atributos e retorna um dicionário que faz o papel das “classes” desse programa.

  # precisaremos usar isso logo adiante...
  from functools import partial
  
  ### Agora já podemos pensar nela como a classe 'Class'
  def newClass(nome, atributos):
      cls = {'nome':nome} # cria o dicionário para a classe somente com seu nome
      for k, v in atributos.items():
          # aqui se um método for o newNome, ele tem de receber a mesma
          # alteração e passar a receber 'cls' como primeiro argumento
          if k == 'new'+nome:
              v = partial(v, cls)
          # e atribuímos tudo normalmente
          cls[k] = v
      return cls

Em seguida, é definido uma “classe” Pessoa com os atributos nome, nascimento e um método idade.

  ### Classe Pessoa
  
  # construtor, corresponde ao __new__ de Python
  def newPessoa(cls, nome, nascimento): # agora a classe mesmo vem aqui
      inst = {} # a nova instância
      inst['classe'] = cls # a instância tem de saber de que classe é, e
                           # agora pode saber dinamicamente
  
      # Agora a instância vai criar os métodos embrulhando as chamadas
      # para as funções originais em uma nova, já se incluindo nela
      for k, v in cls.items():
          # Se for função...
          if callable(v):
              # é, então vamos embrulhar...
              metodo = partial(v, inst)
              inst[k] = metodo
      inst['init'+cls['nome']](nome, nascimento)
      return inst
  
  # inicializador, corresponde ao __init__ de Python
  def initPessoa(inst, nome, nascimento):
      inst['nome'] = nome
      inst['nascimento'] = nascimento
      # assim como fazemos normalmente no __init__, aqui não precisamos
      # nos preocupar
  
  def idade(inst, hoje):
      hd, hm, ha = hoje
      nd, nm, na = inst['nascimento']
      x = ha - na
      return x

Com as funções que se tornarão os métodos da classe Pessoa definidos, falta a última parte da definição da classe que é criar o objeto que representa a classe. Esse objeto é retornado pela função newClass definida no início do código.

  # e agora initPessoa() tem de entrar aqui também
  Pessoa = newClass('Pessoa', {'newPessoa':newPessoa,
                               'initPessoa':initPessoa,
                               'idade':idade})
  ### Fim da definição de classe

A seguir, cria-se duas instâncias de Pessoa para testar a implementação.

  hank = Pessoa['newPessoa']('Hank Moody', (8, 11, 1967))
  print hank['idade']((6, 11, 2008))
  
  fante = Pessoa['newPessoa']('John Fante', (8, 4, 1909))
  print fante['idade']((6, 11, 2008))

Nesse exemplo, a função newClass faz o papel de uma metaclasse, pois é ela que é chamada para criar a classe. É interessante notar que não existe diferença real entre o funcionamento de newClass e newPessoa. Ambas são funções que retornam instâncias, as instâncias retornadas por newClass são “classes” e as instâncias retornadas por newPessoa são objetos do tipo Pessoa.

O autor termina o texto usando as funções de inicialização e cálculo de idade com a metaclasse padrão de Python, type. Ela recebe uma string que será o nome da nova classe, uma tupla de classes base e um dicionário representando o namespace da classe. Esse dicionário contém o que você normalmente define como o corpo de uma instrução class. A única coisa que tem que mudar é o acesso aos atributos, que antes estavam sendo feitos como chaves de dicionários e agora devem usar a sintaxe de atributos normais.

  #-*- coding: utf-8 -*-
  def initPessoa(inst, nome, nascimento):
          inst.nome = nome
          inst.nascimento = nascimento
  
  def idade(inst, hoje):
          hd, hm, ha = hoje
          nd, nm, na = inst.nascimento
          x = ha - na
          return x
  
  Pessoa = type('Pessoa', (), {'__init__':initPessoa,
                                'idade':idade})
  print "Representação de Pessoa como string: ",Pessoa
  print "Tipo de Pessoa (sua metaclasse): ",type(Pessoa)
  print
  print "Imprimindo a idade de uma instância de Pessoa para teste: "
  hank = Pessoa('Hank Moody', (8, 11, 1967))
  print hank.idade((6, 11, 2008))

Resumindo:


  • Em Python tudo é objeto, inclusive classes e instâncias. Não existe diferença real entre elas.
  • Criar uma classe é criar um novo tipo.
  • A classe de uma classe é chamada de metaclasse. As instâncias de metaclasses são classes.
  • A metaclasse default é type.
  • Não existe diferença real entre métodos e funções. Os métodos são apenas funções com namespaces específicos (suas classes).

Criando e definindo metaclasses

Para definir sua própria metaclasse, basta criar uma classe que herda da metaclasse type. O método inicializador recebe os mesmos argumentos que o método inicializador de type: uma string que será o nome da classe, uma tupla de classes bases e um dicionário representando o namespace da classe.

Segue um exemplo ligeiramente modificado de um artigo do IBM developerWorks. Primeiramente, vamos definir uma nova metaclasse:

  >>> class ChattyType(type):
  ...    def __new__(cls, name, bases, dct):
  ...       print "Alocando memória para classe", name
  ...       dct['usa_metaclasse']=True
  ...       return type.__new__(cls, name, bases, dct)
  ...    def __init__(cls, name, bases, dct):
  ...       print "Inicializando (configurando) classe", name
  ...       super(ChattyType, cls).__init__(name, bases, dct)
  ...

Essa metaclasse sobrescreve o método __new__ que efetivamente é o construtor e retorna uma nova instância de classe (nesse exemplo específico, a instância retornada será uma classe por estarmos escrevendo uma metaclasse) e o método __init__ que é o inicializador da instância e não retorna nada. É tentador chamar o __init__ de construtor, mas o trabalho de criação de instâncias é do __new__.

O código acima criou um novo tipo que imprime uma mensagem quando está sendo criado e outra quando está sendo inicializado. Definiu-se também que todas as instâncias do novo tipo terão um atributo usa_metaclasse com o valor inicial True. Essa mudança foi feita adicionando uma chave ao dicionário que representa o namespace da classe (dct).

Com a metaclasse definida, já é possível criar classes a partir dela chamando-a como chama-se type:

  >>> X=ChattyType('X', (object,), {})
  Alocando memória para classe X
  Inicializando (configurando) classe X

Instanciar classes manualmente como feito acima, não é um procedimento muito comum. Segue como Python determina a metaclasse de uma determinada classe:


  1. Se o dicionário que representa o namespace da classe contiver uma chave __metaclass__, o seu valor é usado.
  2. Senão, se existe pelo menos uma classe base, sua metaclasse é usada.
  3. Senão, se existe uma variável global __metaclass__, seu valor é usado.
  4. Senão, types.ClassType é usado.

A criação da classe X feita no exemplo acima comumente seria feita assim:

  >>> class X(object):
  ...    __metaclass__=ChattyType
  ...
  Alocando memória para classe X
  Inicializando (configurando) classe X

A instanciação de ChattyType, feita explicitamente no primeiro exemplo, agora é feita pelo interpretador Python no final da instrução class. Note que a instrução class é simplesmente açúcar sintático para a instanciação de classes através de suas metaclasses. Os programadores que não fazem uso de metaclasses acabam não percebendo isso, já que simplesmente criam suas classes usando class e Python simplesmente as instancia chamando type.

Continuando o exemplo, podemos confirmar que o tipo da classe é a metaclasse:

  >>> type(X)
  <class '__main__.ChattyType'>
  >>> y=X()
  >>> type(y)
  <class '__main__.X'>
  >>> type(X) == type(type(y))
  True

Para concluir, segue algumas dicas na hora de usar metaclasses:


  • A modificação do dicionário que representa o namespace da classe deve ser feita em __new__, como no exemplo. Se na definição de ChattyType, dct[‘usa_metaclasse’]=True fosse executado em __init__, a alteração não teria efeito. Considere o uso de setattr nesses casos.
  • Sempre herde de type.
  • __new__ é o construtor e deve retornar uma nova instância.
  • Erros em metaclasses costumam aparecer durante imports e definições de classes que usem metaclasses diferentes de type.

Alguns exemplos

Metaclasses são usadas na maior parte do tempo para alterar o comportamento de criação e inicialização de classes. Na maioria dos casos, os usuários finais são outros programadores. Frameworks e ferramentas de mapeamento objeto-relacional são exemplos de softwares que usam metaclasses.

Alguns usos para as metaclasses:

O importante é que a classe só é realmente criada na chamada final a type na metaclasse. Você é livre para modificar o dicionário de atributos de acordo com as suas necessidades antes disso, portanto existem muito mais possibilidades de uso para as metaclasses do que as apresentadas aqui.