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

AutomatizarAtributosSlots

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