1860
Comentário:
|
4868
|
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:
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