在Python中是否有goto或任何等价的东西能够跳转到特定的代码行?


当前回答

我在找一些类似的东西

for a in xrange(1,10):
A_LOOP
    for b in xrange(1,5):
        for c in xrange(1,5):
            for d in xrange(1,5):
                # do some stuff
                if(condition(e)):
                    goto B_LOOP;

所以我的方法是使用一个布尔值来帮助打破嵌套的for循环:

for a in xrange(1,10):
    get_out = False
    for b in xrange(1,5):
        if(get_out): break
        for c in xrange(1,5):
            if(get_out): break
            for d in xrange(1,5):
                # do some stuff
                if(condition(e)):
                    get_out = True
                    break

其他回答

我用函数解决了这个问题。我唯一做的就是改变函数的标签。下面是一个非常基本的代码:

def goto_holiday(): #label: holiday
        print("I went to holiday :)")
    
def goto_work(): #label: work
    print("I went to work")
salary=5000
if salary>6000:
    goto_holiday()
else:
    goto_work()

我最近写了一个函数装饰器,在Python中启用goto,就像这样:

from goto import with_goto

@with_goto
def range(start, stop):
    i = start
    result = []

    label .begin
    if i == stop:
        goto .end

    result.append(i)
    i += 1
    goto .begin

    label .end
    return result

我不知道为什么有人想做这样的事情。也就是说,我并不是很认真。但我想指出的是,这种元编程在Python中实际上是可能的,至少在CPython和PyPy中是可能的,而不仅仅是像其他人那样误用调试器API。不过,您必须修改字节码。

通过一些工作,在python中添加类似“goto”的语句在技术上是可行的。我们将使用"dis"和"new"模块,这两个模块对于扫描和修改python字节代码都非常有用。

实现背后的主要思想是首先将代码块标记为使用“goto”和“label”语句。一个特殊的“@goto”装饰器将用于标记“goto”函数。然后,我们扫描这两个语句的代码,并对底层字节代码应用必要的修改。这一切都发生在源代码编译时。

import dis, new

def goto(fn):
    """
    A function decorator to add the goto command for a function.

        Specify labels like so:
        label .foo

        Goto labels like so:
        goto .foo

        Note: you can write a goto statement before the correspnding label statement
    """
    labels = {}
    gotos = {}
    globalName = None
    index = 0
    end = len(fn.func_code.co_code)
    i = 0

    # scan through the byte codes to find the labels and gotos
    while i < end:
        op = ord(fn.func_code.co_code[i])
        i += 1
        name = dis.opname[op]

        if op > dis.HAVE_ARGUMENT:
            b1 = ord(fn.func_code.co_code[i])
            b2 = ord(fn.func_code.co_code[i+1])
            num = b2 * 256 + b1

            if name == 'LOAD_GLOBAL':
                globalName = fn.func_code.co_names[num]
                index = i - 1
                i += 2
                continue

            if name == 'LOAD_ATTR':
                if globalName == 'label':
                    labels[fn.func_code.co_names[num]] = index
                elif globalName == 'goto':
                    gotos[fn.func_code.co_names[num]] = index

            name = None
            i += 2

    # no-op the labels
    ilist = list(fn.func_code.co_code)
    for label,index in labels.items():
        ilist[index:index+7] = [chr(dis.opmap['NOP'])]*7

    # change gotos to jumps
    for label,index in gotos.items():
        if label not in labels:
            raise Exception("Missing label: %s"%label)

        target = labels[label] + 7   # skip NOPs
        ilist[index] = chr(dis.opmap['JUMP_ABSOLUTE'])
        ilist[index + 1] = chr(target & 255)
        ilist[index + 2] = chr(target >> 8)

    # create new function from existing function
    c = fn.func_code
    newcode = new.code(c.co_argcount,
                       c.co_nlocals,
                       c.co_stacksize,
                       c.co_flags,
                       ''.join(ilist),
                       c.co_consts,
                       c.co_names,
                       c.co_varnames,
                       c.co_filename,
                       c.co_name,
                       c.co_firstlineno,
                       c.co_lnotab)
    newfn = new.function(newcode,fn.func_globals)
    return newfn


if __name__ == '__main__':

    @goto
    def test1():
        print 'Hello' 

        goto .the_end
        print 'world'

        label .the_end
        print 'the end'

    test1()

希望这回答了问题。

对于一个向前的后藤,你可以添加:

while True:
  if some condition:
    break
  #... extra code
  break # force code to exit. Needed at end of while loop
#... continues here

但这只适用于简单的场景(即嵌套这些会让你陷入混乱)

在执行“goto”时,首先必须问“goto”是什么。虽然看起来很明显,但大多数人都没有考虑goto与函数栈之间的关系。

如果在函数内部执行“goto”,实际上就放弃了函数调用堆栈。这被认为是一种糟糕的做法,因为函数栈的设计期望在委托一个中间任务之后,您将继续您离开的地方。这就是为什么goto用于异常,而异常可以用来模拟goto,我将对此进行解释。

Finite state machines are probably the best use case for goto, which most of the time are implemented in a kludgy way with loops and switch statements, but I believe that "top level" gotos, are the cleanest, most semantic way to implement finite state machines. In this case, you want to make sure, if you have more variables, they are globals, and don't require encapsulation. Make sure you first model your variable state space(which may be different from execution state, ie the finite state machine).

我相信有合理的设计理由使用goto,异常处理是特殊情况下,混合goto和函数是有意义的。然而,在大多数情况下,您希望将自己限制在“顶级”goto,因此永远不要在函数中调用goto,而只在全局作用域中调用。

在现代语言中模拟顶级goto最简单的方法是实现顶级goto只需要全局变量和空调用堆栈。因此,为了保持调用堆栈为空,每当调用新函数时都返回。下面是一个输出前n个斐波那契数的例子:

a = 0
b = 1
n = 100
def A():
    global a, b
    a = a + b
    n -= 1
    print(a)
    return B() if n > 0 else 0
def B():
    global a, b
    b = a + b
    n -= 1
    print(b)
    return A() if n > 0 else 0
A()

虽然这个示例可能比循环实现更详细,但它也更强大和灵活,并且不需要特殊情况。它让你有一个完整的有限状态机。您还可以使用goto运行器对此进行修改。

def goto(target):
    while(target) target = target()
def A():
    global a, b
    a = a + b
    print(a)
    return B
def B():
    global a, b
    b = a + b
    print(b)
    return A
goto(A)

为了强制执行“返回”部分,您可以编写一个goto函数,在完成时抛出一个异常。

def goto(target):
    target()
    throw ArgumentError("goto finished.")
def A():
    global a, b
    a = a + b
    print(a)
    goto(B)
def B()
    global a, b
    b = a + b
    print(b)
    goto(A)
goto(A)

因此,您可以看到,这在很大程度上是过度思考的,而调用一个函数然后抛出一个错误的helper函数就是您所需要的。您可以将它进一步包装在一个“start”函数中,这样错误就会被捕获,但我认为这并不是严格必要的。虽然其中一些实现可能会耗尽调用堆栈,但第一个运行器示例将其保持为空,如果编译器可以进行尾部调用优化,这也会有所帮助。