associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

Diferenças para "AutomatizarAtributosSlots"

Diferenças entre as versões de 2 e 3
Revisão 2e 2005-07-14 18:58:12
Tamanho: 1860
Comentário:
Revisão 3e 2005-07-14 23:17:28
Tamanho: 4868
Editor: PedroWerneck
Comentário:
Deleções são marcadas assim. Adições são marcadas assim.
Linha 1: Linha 1:
Metaclasse para definir automaticamente {{{__slots__}}} e todos os argumentos passados que iniciem com o caracter '_'
Frequentemente em Python nos deparamos com código semelhante a esse, definindo como atributos do objeto os argumentos recebidos no método __init__:



{{{
#!python

class A:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

}}}

Esse é um exemplo simples. Na prática podemos chegar a casos bem mais complicados. Esse logo abaixo foi retirado de um código real, e é a classe base de uma hierarquia com uma dezena de variações:

{{{
#!python

class Registro(object):
    def __init__(self,
                 registro=None,
                 gaveta=None,
                 montagem=None,
                 numero=None,
                 coletor=None,
                 data=None,
                 genero=None,
                 especie=None,
                 autor=None,
                 ano=None,
                 local=None,
                 estado=None,
                 latitude=None,
                 longitude=None,
                 altitude=None,
                 tipo=None,
                 familia=None,
                 obs=None):
        
        self.registro = registro
        self.gaveta = gaveta
        self.montagem = montagem
        self.numero = numero
        self.coletor = coletor
        self.data = data
        self.genero = genero
        self.especie = especie
        self.autor = autor
        self.ano = ano
        self.local = local
        self.estado = estado
        self.latitude = latitude
        self.longitude = longitude
        self.altitude = altitude
        self.tipo = tipo
        self.familia = familia
        self.obs = obs

}}}

Houve uma proposta de uma solução para esse problema ( http://cci.lbl.gov/~rwgk/python/adopt_init_args_2005_07_02.html ), mas não foi muito bem aceita, principalmente por envolver uma mudança de sintaxe, e também por não exigir alterações, podendo ser implementada de outras formas. Como é algo bem útil pra mim, e já estava pensando em fazer algo a respeito, resolvi com o código abaixo, a metaclasse AutoAttrs, que combinada com a metaclasse AutoSlots, definem automaticamente os atributos que iniciem com '_' e o atributo __slots__ para a classe.
Linha 32: Linha 95:
        
Linha 34: Linha 98:
            attrs = zip(names[1:], args)
            arg_names = names[1:len(args)+1]
            kwd_names = names[len(args)+1:]

            arg_attrs = zip(names[1:], args)
            kwd_attrs = [(n, kwds.get(n)) for n in kwd_names]
            
            attrs = arg_attrs + kwd_attrs
            
Linha 48: Linha 120:
#teste: }}}
Linha 50: Linha 122:
import unittest

class Sample(object):
    __metaclass__ = AutoSlotsAttrs
    def __init__(self, _a, _b, c, d=None):
        assert self.a + self.b == c
    

class Test(unittest.TestCase):
    def testAttributes(self):
        o = Sample(1, 2, 3, d=4)

        self.assertEqual(o.a, 1)
        self.assertEqual(o.b, 2)

        self.assert_(not hasattr(o, 'c'))

        self.assert_(hasattr(o, '__slots__'))
        self.assertRaises(AttributeError, setattr, o, 'e', 10)
No caso do exemplo acima, podemos usar a metaclasse AutoAttrs e todos os atributos serão definidos automaticamente:
Linha 71: Linha 125:
if __name__ == '__main__':
    unittest.main()
{{{
#!python
    
class Registro(object):
    __metaclass__ = AutoAttrs

    def __init__(self,
                 _registro=None,
                 _gaveta=None,
                 _montagem=None,
                 _numero=None,
                 _coletor=None,
                 _data=None,
                 _genero=None,
                 _especie=None,
                 _autor=None,
                 _ano=None,
                 _local=None,
                 _estado=None,
                 _latitude=None,
                 _longitude=None,
                 _altitude=None,
                 _tipo=None,
                 _familia=None,
                 _obs=None):

        pass


r = Registro(_registro="TIPO000001", _gaveta=0197, _numero=3, _montagem=0)

print r.registro
print r.gaveta
print r.numero
print r.montagem
Linha 74: Linha 162:


No caso de objetos que não terão novos atributos criados posteriormente, pode-se usar a metaclasse AutoSlotsAttrs, que combina as duas, criando os atributos e definindo automaticamente o atributo __slots__

Frequentemente em Python nos deparamos com código semelhante a esse, definindo como atributos do objeto os argumentos recebidos no método init:

   1 class A:
   2     def __init__(self, x, y, z):
   3         self.x = x
   4         self.y = y
   5         self.z = z

Esse é um exemplo simples. Na prática podemos chegar a casos bem mais complicados. Esse logo abaixo foi retirado de um código real, e é a classe base de uma hierarquia com uma dezena de variações:

   1 class Registro(object):
   2     def __init__(self,
   3                  registro=None,
   4                  gaveta=None,
   5                  montagem=None,
   6                  numero=None,
   7                  coletor=None,
   8                  data=None,
   9                  genero=None,
  10                  especie=None,
  11                  autor=None,
  12                  ano=None,
  13                  local=None,
  14                  estado=None,
  15                  latitude=None,
  16                  longitude=None,
  17                  altitude=None,
  18                  tipo=None,
  19                  familia=None,
  20                  obs=None):
  21         
  22         self.registro = registro
  23         self.gaveta = gaveta
  24         self.montagem = montagem
  25         self.numero = numero
  26         self.coletor = coletor
  27         self.data = data
  28         self.genero = genero
  29         self.especie = especie
  30         self.autor = autor
  31         self.ano = ano
  32         self.local = local
  33         self.estado = estado
  34         self.latitude = latitude
  35         self.longitude = longitude
  36         self.altitude = altitude
  37         self.tipo = tipo
  38         self.familia = familia
  39         self.obs = obs

Houve uma proposta de uma solução para esse problema ( http://cci.lbl.gov/~rwgk/python/adopt_init_args_2005_07_02.html ), mas não foi muito bem aceita, principalmente por envolver uma mudança de sintaxe, e também por não exigir alterações, podendo ser implementada de outras formas. Como é algo bem útil pra mim, e já estava pensando em fazer algo a respeito, resolvi com o código abaixo, a metaclasse AutoAttrs, que combinada com a metaclasse AutoSlots, definem automaticamente os atributos que iniciem com '_' e o atributo slots para a classe.

   1 class AutoSlots(type):
   2     def __new__(meta, name, bases, data):
   3         if '__init__' in data:
   4             slots = data.get('__slots__', [])
   5             init = data['__init__']
   6             varnames = init.func_code.co_varnames
   7             
   8             for var in varnames:
   9                 if var.startswith('_'):
  10                     var = var[1:]
  11                     if var not in slots:
  12                         slots.append(var)
  13 
  14             if slots:
  15                 data['__slots__'] = slots
  16 
  17         cls = super(AutoSlots, meta).__new__(meta, name, bases, data)
  18         super(AutoSlots, cls).__init__(cls, name, bases, data)
  19 
  20         return cls
  21 
  22 
  23 class AutoAttrs(type):
  24     def __call__(cls, *args, **kwds):
  25         o = object.__new__(cls, *args, **kwds)
  26         
  27         if hasattr(cls, '__init__'):
  28             names = cls.__init__.func_code.co_varnames
  29 
  30             arg_names = names[1:len(args)+1]
  31             kwd_names = names[len(args)+1:]
  32 
  33             arg_attrs = zip(names[1:], args)
  34             kwd_attrs = [(n, kwds.get(n)) for n in kwd_names]
  35             
  36             attrs = arg_attrs + kwd_attrs
  37             
  38             for k, v in attrs:
  39                 if k.startswith('_'):
  40                     setattr(o, k[1:], v)
  41 
  42         cls.__init__(o, *args, **kwds)
  43 
  44         return o
  45 
  46 
  47 class AutoSlotsAttrs(AutoAttrs, AutoSlots):
  48     pass

No caso do exemplo acima, podemos usar a metaclasse AutoAttrs e todos os atributos serão definidos automaticamente:

   1     
   2 class Registro(object):
   3     __metaclass__ = AutoAttrs
   4 
   5     def __init__(self,
   6                  _registro=None,
   7                  _gaveta=None,
   8                  _montagem=None,
   9                  _numero=None,
  10                  _coletor=None,
  11                  _data=None,
  12                  _genero=None,
  13                  _especie=None,
  14                  _autor=None,
  15                  _ano=None,
  16                  _local=None,
  17                  _estado=None,
  18                  _latitude=None,
  19                  _longitude=None,
  20                  _altitude=None,
  21                  _tipo=None,
  22                  _familia=None,
  23                  _obs=None):
  24 
  25         pass
  26 
  27 
  28 r = Registro(_registro="TIPO000001", _gaveta=0197, _numero=3, _montagem=0)
  29 
  30 print r.registro
  31 print r.gaveta
  32 print r.numero
  33 print r.montagem

No caso de objetos que não terão novos atributos criados posteriormente, pode-se usar a metaclasse AutoSlotsAttrs, que combina as duas, criando os atributos e definindo automaticamente o atributo slots