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

NoSelf

Essa é para você, que fica desesperado em ter que digitar aquele MALDITO 'self' no início de todos seus métodos. Seus problemas acabaram! Você jamais terá de se preocupar em digitar aqueles quatro caracteres na definição de método novamente. Claro que você terá de se preocupar com muitas outras coisas, mas isso é um mero detalhe.

Basta salvar o código abaixo em um módulo, importar a classe NoSelfType para seu módulo e definir a variável 'metaclass = NoSelfType' no início.

OBS: aos desavisados, esse código é uma brincadeira, feita para mostrar como retirar o self é algo muito mais complicado do que parece! Não vá utilizá-lo em código real!

Atendendo a pedidos, coloquei comentários explicativos...

   1 #!/usr/bin/env python
   2 # encoding: utf-8
   3 
   4 
   5 import opcode
   6 import types
   7 import dis
   8 
   9 locals().update(opcode.opmap)
  10 
  11 
  12 
  13 def noself(func, inst):
  14     # A maior parte da mágica está aqui nesta função..
  15 
  16     # Ela receberá a função que define o método a ser
  17     # modificado e a instância que deve aparecer lá dentro como 'self'
  18     
  19     # o que ela faz é simplesmente colocar no início do método o
  20     # equivalente à linha: self = inst
  21 
  22     # primeiro, precisamos do código da função
  23     code = func.func_code
  24 
  25     # agora precisamos de tudo do objeto código, retirar
  26     # todos os atributos para recriá-lo mais tarde... nem tudo aqui
  27     # será alterado, mas facilita depois
  28 
  29     argcount = code.co_argcount       # número de parâmetros
  30     nlocals = code.co_nlocals         # número de variáveis locais
  31     stacksize = code.co_stacksize     # tamanho máximo da pilha
  32     flags = code.co_flags             # algumas flags para o interpretador
  33     codestring = code.co_code         # o código
  34     constants = code.co_consts        # as constantes na função. 
  35     names = code.co_names             # nomes de globais usadas
  36     varnames = code.co_varnames       # nomes de variáveis usadas
  37     filename = code.co_filename       # nome do arquivo
  38     name = code.co_name               # nome da função
  39     firstlineno = code.co_firstlineno # primeira linha da função
  40     lnotab = code.co_lnotab            
  41     freevars = code.co_freevars       # variáveis livres
  42     cellvars = code.co_cellvars       # variáveis usadas em função aninhada
  43 
  44     # primeiro, colocamos o nome 'self' na lista de nomes de
  45     # variáveis... colocamos no final para evitar ter de mudar o
  46     # índice de todas as outras
  47     varnames = varnames + ('self',)
  48 
  49     # depois, fazemos a mesma coisa com a instância na lista de
  50     # constantes
  51     constants = constants + (inst,)
  52 
  53     # como acrescentamos uma variável, aumentamos o número de
  54     # variáveis locais
  55     nlocals += 1
  56 
  57     # convertemos a string de código para uma lista com o valor
  58     # numérico de cada byte
  59     bcode = map(ord, code.co_code)
  60 
  61     # então colocamos no início do código as instruções para fazer
  62     # a atribuição self=inst
  63     bcode = [LOAD_CONST,        
  64              len(constants)-1,  # carrega a instância na pilha
  65              0,
  66              STORE_FAST,
  67              len(varnames)-1,   # atribui a instância a 'self'
  68              0] + bcode
  69 
  70 
  71     # agora vem a parte complicada... infelizmente, quando definimos
  72     # as chamadas a 'self' no código sem estar definido localmente, o
  73     # interpretador vai buscá-lo como global, e não vai encontrar o
  74     # que definimos localmente logo agora... para consertar isso,
  75     # precisamos encontrar todas os acessos a globais e substituir
  76     # aqueles que tentam acessar o 'self' como global por um acesso
  77     # local
  78 
  79     # para facilitar, vamos usar um iterador...
  80     itercode = iter(enumerate(bcode))
  81     
  82     while 1:
  83         try:
  84             i, op = itercode.next()
  85             # se a instrução precisa de argumentos, pode nos
  86             # interessar ou não, mas de qualquer maneira precisamos
  87             # pular os bytes dos argumentos para não confundir um
  88             # valor com instrução...
  89             if op >= opcode.HAVE_ARGUMENT:
  90                 # os argumentos vem nos dois bytes seguintes... 
  91                 oparg = itercode.next()[1] + itercode.next()[1]*256
  92                 # procuramos por LOAD_GLOBAL
  93                 if op == LOAD_GLOBAL:
  94                     # o argumento para LOAD_GLOBAL é o índice do nome
  95                     # da variável em 'names'... se é 'self',
  96                     # então é o que procuramos!
  97                     if names[oparg] == 'self':
  98                         # substituímos a instrução LOAD_GLOBAL por LOAD_FAST
  99                         bcode[i] = LOAD_FAST
 100                         # substituímos o argumento para o índice do
 101                         # nome 'self' na lista 'varnames'
 102                         bcode[i+1] = len(varnames)-1
 103                         # a menos que a função tenha mais de 255
 104                         # variáveis, não teremos problemas aqui e
 105                         # podemos colocar 0
 106                         bcode[i+2] = 0
 107 
 108         except StopIteration:
 109             # saia do loop quando o iterador esgotar...
 110             break
 111 
 112     # transformamos a lista com o código numérico novamente em string
 113     codestring = ''.join(map(chr, bcode))
 114 
 115     # recriamos o objeto código com os valores e byte-code novo: muda
 116     # apenas 'varnames', 'constants', 'nlocals', e o código, claro...
 117     ncode = types.CodeType(argcount, nlocals, stacksize, flags, codestring,
 118                            constants, names, varnames, filename, name,
 119                            firstlineno, lnotab, freevars, cellvars)
 120     # note que como 'self' vem originalmente como global, o nome vem
 121     # em 'names', mas não podemos removê-lo para não atrapalhar com os
 122     # índices de outras variáveis...
 123 
 124     # finalmente, recriamos e retornamos a função, com tudo de antes,
 125     # mas com o código novo, alterado
 126     nfunc = types.FunctionType(ncode, func.func_globals, func.func_name,
 127                                func.func_defaults, func.func_closure)
 128 
 129     return nfunc
 130 
 131 
 132 # Depois disso tudo, o resto é moleza...
 133 
 134 class NoSelfMethod(object):
 135     # um descriptor que implementa (mais ou menos) o mesmo que os
 136     # métodos normais (já que a classe MethodType original não permite
 137     # herança)....
 138     def __init__(self, func, instance, cls):
 139         self.im_func = func
 140         self.im_self = instance
 141         self.im_class = cls
 142 
 143     def __get__(self, obj, cls):
 144         return NoSelfMethod(self.im_func, obj, cls)
 145 
 146     def __call__(self, *args, **kwds):
 147         # com a diferença de que quando é chamado com uma instância,
 148         # ele usa nossa função nofunc() para criar a nova versão
 149         # inserindo o 'self' (sim, ele recria a função fazendo aquilo
 150         # tudo cada vez que o método é chamado!)
 151         if self.im_self is None:
 152             return self.im_func(*args, **kwds)
 153         else:
 154             func = noself(self.im_func, self.im_self)
 155             return func(*args, **kwds)
 156 
 157 
 158 class NoSelfType(type):
 159     # E finalmente, uma metaclasse que faz com que suas classes usem o
 160     # nosso método especial 
 161     def __new__(mcls, name, bases, dic):
 162         for k, v in dic.items():
 163             if callable(v):
 164                 dic[k] = NoSelfMethod(v, None, None)
 165 
 166         return type.__new__(NoSelfType, name, bases, dic)
 167         
 168 
 169 def test():
 170 
 171     # Testando ...
 172 
 173     # Uma global só para garantir que elas não foram alteradas...
 174     bar = 'bar'
 175 
 176     class C(object):
 177         __metaclass__ = NoSelfType
 178 
 179         def __init__(a, b, c=None):
 180             print 'Look mom... no self!', self
 181             # confirmando que o 'self' apareceu aqui
 182             print 'I am', self, 'they called me with', a, b, c
 183 
 184         def m(obj):
 185             # confirmando que o 'self' que aparece aqui e o objeto
 186             # criado são de fato os mesmos
 187             assert obj is self
 188             # confirmando que a global está ok
 189             assert bar == 'bar'
 190 
 191             def f():
 192                 # FIXME: infelizmente, funções aninhadas usam a
 193                 # instrução LOAD_DEREF para encontrar variáveis do
 194                 # escopo anterior, que aparecem naquela lista
 195                 # 'cellvars' lá em cima, então esta função f() não vai
 196                 # encontrar o 'self' pois tenta procurá-lo como
 197                 # global, o que significa que teremos de fazer outra
 198                 # alteração aqui também, bem mais complicada... por
 199                 # via das dúvidas, vamos testar e conferir que dá
 200                 # erro em todas as implementações e versões...
 201                 try:
 202                     assert obj is self
 203                     raise AssertionError('self found from nested function?')
 204                 except NameError:
 205                     pass
 206             f()
 207 
 208             # É isso aí... 
 209 
 210     o = C(1, 2, c='foo')
 211     o.m(o)
 212         
 213         
 214 if __name__ == '__main__':
 215     test()

Outras implementações

* http://metapython.blogspot.com/2010/11/selfless-python.html