Modest opinions  
by a humble autodidact

豆瓣Python版有人想要goto。虽说看不出来goto有什么特别吸引力,但是为了实现Python社区对广大人民群众做出的“Python是世界上除Lisp外最牛b的编程语言”的庄严承诺,同时也为了复习一下一段时间没用Python知识,写了一个goto模块。使用如下例:

 1 from goto import *
 2 
 3 @patch
 4 def f2():
 5     goto(10)
 6     print 'should not see this'
 7     label(10)
 8     for i in range(1,99999):
 9         print i
10         if i == 5:
11             goto('out')
12     label('out')
13 
14 f2()

用法是:

1. from goto import *。注意暂时不支持import goto,不是不能实现,是暂时没时间写。

2.对需要使用goto的函数,前面加个@patch

3.用label(x)和goto(x)的形式写label和goto。x可以是数字或字符串。

 goto模块的代码如下:

goto.py
import dis,pdb

#dummy functions serving as target of bytecode patching
def goto(label):
    
pass

def label(label):
    
pass

#
def decode_bytecode(fun):
    
"""Input: a function
       Ouput: a list of pairs (opcode, arguments)
"""
    c 
= fun.func_code.co_code
    n 
= len(c)
    i 
= 0
    
while i < n:
        op 
= c[i]
        i 
+= 1
        arguments 
= ""
        
if ord(op) >= dis.HAVE_ARGUMENT:
            arguments 
= c[i : i+2]
            i 
+= 2
        
yield (op, arguments)

def sample():
    goto(
200)
    
if 1 == 2:
        sample()
    
else:
        
print 'sample'
        
def test_decode(fun):
    
for op,arg in decode_bytecode(fun):
        
if arg=='':
            
print dis.opname[ord(op)]
        
else:
            
print dis.opname[ord(op)] +' '+str(ord(arg[0]))+' '+str(ord(arg[1]))
        
def match_pattern(seq, i, p):
    
"""
    try to match pattern p to seq[i:], return None if match failed
    seq: output of decode_bytecode
    p -> [instr, instr, ...]
    instr -> (opcode, arg, arg)      opcode is a opcode string
    arg -> ''                        I don't give a damn about this arg
    arg -> integer                   match arg with number
    arg -> string                    the arg is key of the returned match dict from which the arg value can be extracted
    arg -> lambda                    lambda is evaluated with the argument, false return means failed match
    
"""
    
#pdb.set_trace()
    m = {}

    
for op, arg1, arg2 in p:
        
if i==len(seq):
            
return None
        
        
if dis.opmap[op] != ord(seq[i][0]):
            
return None

        
if arg1 == '':
            
pass
        
else:
            
if seq[i][1== ''return None
            
            a1 
= ord(seq[i][1][0])
            
if type(arg1) is str:
                m[arg1]
=a1
            
elif type(arg1) is int:
                
if arg1 != a1: return None
            
elif not arg1(a1):
                
return None

        
#don't need arg2 in this program

        i
+=1

        
    
return m
        
def int_to_bytecode_arg(i):
    
return chr(i  % 256+\
           chr(i 
// 256)

def patch(fun):
    NOP 
= chr(dis.opmap['NOP'])
    co 
= fun.func_code
    old 
= list(decode_bytecode(fun))
    new 
= [] #a list of characters
    
    
#mapping from label to bytecode offset
    label_table={}
    
#if a goto(label) is seen but label is not seen
    #record for the number the bytecode offset of the
    #argument for JUMP_ABSOLUTE for later patching
    goto_table={}

    i
=0
    
#pdb.set_trace()
    while i<len(old):
        m
= match_pattern(old, i,
                         [(
'LOAD_GLOBAL','fun_name',''),
                          (
'LOAD_CONST','label',''),
                          (
'CALL_FUNCTION',1,''),
                          (
'POP_TOP','','')])
        
if m:
            stmt 
= co.co_names[m['fun_name']]
            label 
= co.co_consts[m['label']]
            
        
if   m and stmt == 'goto':
            
# we have a goto statement
            if label_table.has_key(label):
                arg 
= int_to_bytecode_arg(label_table[label])
            
else:
                arg 
= '\xff\xff'
                goto_table[label] 
=\
                 goto_table.get(label, [])
+[len(new)+1]
            new 
+= chr(dis.opmap['JUMP_ABSOLUTE'])
            new 
+= arg
            
#todo
            #this is to maintain proper bytecode offset to
            #source code line number mapping. A better way
            #would be fixing the mapping instead of using
            #placeholders
            new += NOP*7
            i 
+= 4
        
elif m and stmt == 'label':
            
# we have a label statement
            label_table[label]=len(new)
            
if goto_table.has_key(label):
                
for offset in goto_table[label]:
                    new[offset: offset
+2]=int_to_bytecode_arg(len(new))
                
del goto_table[label]
            new 
+= NOP*10
            i 
+= 4
        
else:
            
# emit as-is 
            new += old[i][0] #the opcode
            new += old[i][1#its args if it has 
            i += 1

    
if len(goto_table):
        
#todo: output line number
        raise Exception('missing label')

    
import types
    newcode 
= types.CodeType(co.co_argcount,
                       co.co_nlocals,
                       co.co_stacksize,
                       co.co_flags,
                       
''.join(new),
                       co.co_consts,
                       co.co_names,
                       co.co_varnames,
                       co.co_filename,
                       co.co_name,
                       co.co_firstlineno,
                       co.co_lnotab)
    
return types.FunctionType(newcode,fun.func_globals)
    


 

posted on 2010-05-05 15:03  yushih  阅读(4806)  评论(2编辑  收藏  举报