3600
Comentário:
|
9180
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()