豆瓣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模块的代码如下:

python 的goto语句 python中goto_字符串

python 的goto语句 python中goto_#if_02

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)