Frequentemente em Python nos deparamos com código semelhante a esse, definindo como atributos do objeto os argumentos recebidos no método init:
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