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

ClassAttrTracking

Por incrível que pareça, essa receita é um código de uso real, feito para uma aplicação que usaria diversas classes como estruturas, mas precisava manter um registro da ordem que os atributos foram definidos. Esse registro precisava ser simples de manter, portanto a idéia de manter uma lista com todos eles estava fora de questão.

Num primeiro instante a idéia óbvia era definir o método setattr na metaclasse, para registrar a ordem que os atributos foram definidos, no entanto isso só funcionaria para os atributos definidos depois que a classe for criada, não para aqueles na definição da classe, que são passados para a chamada à metaclasse em um dicionário (portanto, sem ordem). Simplesmente definir os atributos no método init e usar um setattr customizado na classe também não funcionaria, porque haveriam outras subestruturas que teriam de ser criadas na compilação, não durante a execução.

A solução é essa logo abaixo: complicada, mas interessante o bastante para aparecer aqui, para os interessados no assunto. A idéia é atribuir a cada objeto criado um número identificador, usado para ordená-los. MetaTrackable é uma metaclasse que registra esse número para suas classes, que por sua vez registram o número para suas instâncias. MetaTracker é outra metaclasse, cujas classes mantém o registro da ordem e geram um iterador para ser usado quando necessário.

Como as metaclasses são criadas cooperativamente, e usando super() para chamar as superclasses, criar uma metaclasse juntando o comportamento das duas, MetaTrackerTrackable, é algo bem trivial.

Código

   1 import itertools
   2 
   3 class MetaTrackable(type):
   4     _counter = itertools.count()
   5     
   6     def __init__(cls, name, bases, attrs):
   7         super(MetaTrackable, cls).__init__(name, bases, attrs)
   8         cls._counter = cls.__class__._counter
   9         cls._creation_number = cls._counter.next()
  10 
  11     def __call__(cls, *args, **kwds):
  12         obj = cls.__new__(cls, *args, **kwds)
  13         cls.__init__(obj, *args, **kwds)
  14         obj._creation_number = cls._counter.next()
  15         return obj      
  16 
  17 
  18 class MetaTracker(type):
  19     def __init__(cls, name, bases, attrs):
  20         super(MetaTracker, cls).__init__(name, bases, attrs)
  21 
  22         attrs_order = [(a, v._creation_number) for (a, v) in attrs.items()
  23                        if hasattr(v, '_creation_number')]
  24 
  25         cls._order = [a[0] for a in sorted(attrs_order, key=lambda x:x[1])]
  26 
  27     def __iter__(cls):
  28         for a in cls._order:
  29             yield getattr(cls, a)
  30 
  31 
  32 class MetaTrackerTrackable(MetaTracker, MetaTrackable):
  33     pass

Um exemplo de uso (na verdade, um teste do funcionamento)

   1 class Value:
   2     __metaclass__ = MetaTrackable
   3 
   4 class ValueX(Value):
   5     pass
   6 
   7 class ValueY(Value):
   8     pass
   9 
  10 class ValueZ(Value):
  11     pass
  12 
  13 
  14 class Node:
  15     __metaclass__ = MetaTrackerTrackable
  16 
  17 
  18 class Tree(Node):
  19     a = ValueZ()
  20     c = ValueX()
  21     b = ValueY()
  22 
  23     class SubA(Node):
  24         e = ValueX()
  25         d = ValueX()
  26 
  27         class SubA(Node):
  28             b = ValueX()
  29             a = ValueY()
  30 
  31         class SubB(Node):
  32             b = ValueY()
  33             c = ValueX()
  34             e = ValueZ()
  35             d = ValueX()
  36 
  37         h = ValueZ()     
  38 
  39 
  40 assert Tree._order == ['a', 'c', 'b', 'SubA']
  41 for v, c in zip(Tree, (ValueZ, ValueX, ValueY, type)):
  42     assert isinstance(v, c)
  43 
  44 assert Tree.SubA._order == ['e', 'd', 'SubA', 'SubB', 'h']
  45 for v, c in zip(Tree.SubA, (ValueX, ValueX, type, type, ValueZ)):
  46     assert isinstance(v, c)
  47 
  48 assert Tree.SubA.SubA._order == ['b', 'a']
  49 for v, c in zip(Tree.SubA.SubA, (ValueX, ValueY)):
  50     assert isinstance(v, c)
  51 
  52 assert Tree.SubA.SubB._order == ['b', 'c', 'e', 'd']
  53 for v, c in zip(Tree.SubA.SubB, (ValueY, ValueX, ValueZ, ValueX)):
  54     assert isinstance(v, c)

Volta para CookBook.


PedroWerneck