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

Diferenças para "NoSelf"

Diferenças entre as versões de 1 e 3 (2 versões de distância)
Revisão 1e 2008-01-14 12:20:52
Tamanho: 3600
Editor: PedroWerneck
Comentário:
Revisão 3e 2008-09-26 14:07:37
Tamanho: 9180
Editor: localhost
Comentário: converted to 1.6 markup
Deleções são marcadas assim. Adições são marcadas assim.
Linha 7: Linha 7:
Atendendo a pedidos, coloquei comentários explicativos...
Linha 10: Linha 11:
#!/usr/bin/env python
# encoding: utf-8
Linha 15: Linha 17:
import dis
Linha 20: Linha 22:
Linha 21: Linha 24:
    # A maior parte da mágica está aqui nesta função..

    # Ela receberá a função que define o método a ser
    # modificado e a instância que deve aparecer lá dentro como 'self'
    
    # o que ela faz é simplesmente colocar no início do método o
    # equivalente à linha: self = inst

    # primeiro, precisamos do código da função
Linha 22: Linha 34:
    argdefs = func.func_defaults

    argcount = code.co_argcount
    nlocals = code.co_nlocals
    stacksize = code.co_stacksize
    flags = code.co_flags
    codestring = code.co_code
    constants = code.co_consts
    names = code.co_names
    varnames = code.co_varnames
    filename = code.co_filename
    name = code.co_name
    firstlineno = code.co_firstlineno
    lnotab = code.co_lnotab
    freevars = code.co_freevars
    cellvars = code.co_cellvars

    # agora precisamos de tudo do objeto código, retirar
    # todos os atributos para recriá-lo mais tarde... nem tudo aqui
    # será alterado, mas facilita depois

    argcount = code.co_argcount # número de parâmetros
    nlocals = code.co_nlocals # número de variáveis locais
    stacksize = code.co_stacksize # tamanho máximo da pilha
    flags = code.co_flags # algumas flags para o interpretador
    codestring = code.co_code # o código
    constants = code.co_consts # as constantes na função.
    names = code.co_names # nomes de globais usadas
    varnames = code.co_varnames # nomes de variáveis usadas
    filename = code.co_filename # nome do arquivo
    name = code.co_name # nome da função
    firstlineno = code.co_firstlineno # primeira linha da função
    lnotab = code.co_lnotab
    freevars = code.co_freevars # variáveis livres
    cellvars = code.co_cellvars # variáveis usadas em função aninhada

    # primeiro, colocamos o nome 'self' na lista de nomes de
    # variáveis... colocamos no final para evitar ter de mudar o
    # índice de todas as outras
Linha 40: Linha 58:

    # depois, fazemos a mesma coisa com a instância na lista de
    # constantes
Linha 41: Linha 62:

    # como acrescentamos uma variável, aumentamos o número de
    # variáveis locais
Linha 43: Linha 67:
    # convertemos a string de código para uma lista com o valor
    # numérico de cada byte
Linha 45: Linha 70:
    bcode = [LOAD_CONST,
             len(constants)-1,

    # então colocamos no início do código as instruções para fazer
    # a atribuição self=inst
    bcode = [LOAD_CONST,
             len(constants)-1, # carrega a instância na pilha
Linha 49: Linha 77:
             len(varnames)-1,              len(varnames)-1,   # atribui a instância a 'self'
Linha 53: Linha 81:
    # agora vem a parte complicada... infelizmente, quando definimos
    # as chamadas a 'self' no código sem estar definido localmente, o
    # interpretador vai buscá-lo como global, e não vai encontrar o
    # que definimos localmente logo agora... para consertar isso,
    # precisamos encontrar todas os acessos a globais e substituir
    # aqueles que tentam acessar o 'self' como global por um acesso
    # local

    # para facilitar, vamos usar um iterador...
Linha 54: Linha 91:
    earg = 0     
Linha 58: Linha 95:
            # se a instrução precisa de argumentos, pode nos
            # interessar ou não, mas de qualquer maneira precisamos
            # pular os bytes dos argumentos para não confundir um
            # valor com instrução...
Linha 59: Linha 100:
                oparg = itercode.next()[1] + itercode.next()[1]*256 + earg                 # os argumentos vem nos dois bytes seguintes...
oparg = itercode.next()[1] + itercode.next()[1]*256
              # procuramos por LOAD_GLOBAL
Linha 61: Linha 104:
                    # o argumento para LOAD_GLOBAL é o índice do nome
                    # da variável em 'names'... se é 'self',
                    # então é o que procuramos!
Linha 62: Linha 108:
                        # substituímos a instrução LOAD_GLOBAL por LOAD_FAST
Linha 63: Linha 110:
                        # substituímos o argumento para o índice do
                        # nome 'self' na lista 'varnames'
Linha 64: Linha 113:
                        # a menos que a função tenha mais de 255
                        # variáveis, não teremos problemas aqui e
                        # podemos colocar 0
Linha 65: Linha 117:
Linha 66: Linha 119:
            # saia do loop quando o iterador esgotar...
Linha 68: Linha 122:
    # transformamos a lista com o código numérico novamente em string
Linha 70: Linha 125:


    ncode = types.CodeType(argcount, nlocals, stacksize, flags, codestring, constants, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars)
    nfunc = types.FunctionType(ncode, func.func_globals, func.func_name, argdefs, func.func_closure)
    # recriamos o objeto código com os valores e byte-code novo: muda
    # apenas 'varnames', 'constants', 'nlocals', e o código, claro...
    ncode = types.CodeType(argcount, nlocals, stacksize, flags, codestring,
                           constants, names, varnames, filename, name,
                           firstlineno, lnotab, freevars, cellvars)
    # note que como 'self' vem originalmente como global, o nome vem
    # em 'names', mas não podemos removê-lo para não atrapalhar com os
    # índices de outras variáveis...

    # finalmente, recriamos e retornamos a função, com tudo de antes,
    # mas com o código novo, alterado
    nfunc = types.FunctionType(ncode, func.func_globals, func.func_name,
                               func.func_defaults, func.func_closure)
Linha 78: Linha 142:
# Depois disso tudo, o resto é moleza...
Linha 79: Linha 145:
    def __init__(self, func, instance=None, cls=None):     # um descriptor que implementa (mais ou menos) o mesmo que os
    # métodos normais (já que a classe MethodType original não permite
    # herança)....
    def __init__(self, func, instance, cls):
Linha 88: Linha 157:
        # com a diferença de que quando é chamado com uma instância,
        # ele usa nossa função nofunc() para criar a nova versão
        # inserindo o 'self' (sim, ele recria a função fazendo aquilo
        # tudo cada vez que o método é chamado!)
Linha 96: Linha 169:
    # E finalmente, uma metaclasse que faz com que suas classes usem o
    # nosso método especial
Linha 99: Linha 174:
                dic[k] = NoSelfMethod(v)                 dic[k] = NoSelfMethod(v, None, None)
Linha 106: Linha 181:
    # Testando ...

    # Uma global só para garantir que elas não foram alteradas...
    bar = 'bar'
Linha 110: Linha 190:
            print 'Look mom... no self!'             print 'Look mom... no self!', self
            # confirmando que o 'self' apareceu aqui
Linha 114: Linha 195:
                     # confirmando que o 'self' que aparece aqui e o objeto
            # criado são de fato os mesmos
Linha 116: Linha 198:
            # confirmando que a global está ok
            assert bar == 'bar'

            def f():
                # FIXME: infelizmente, funções aninhadas usam a
                # instrução LOAD_DEREF para encontrar variáveis do
                # escopo anterior, que aparecem naquela lista
                # 'cellvars' lá em cima, então esta função f() não vai
                # encontrar o 'self' pois tenta procurá-lo como
                # global, o que significa que teremos de fazer outra
                # alteração aqui também, bem mais complicada... por
                # via das dúvidas, vamos testar e conferir que dá
                # erro em todas as implementações e versões...
                try:
                    assert obj is self
                    raise AssertionError('self found from nested function?')
                except NameError:
                    pass
            f()

            # É isso aí...
Linha 125: Linha 227:


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()