Pythonic方式“如果Y失败,在Y之前运行X”?

问题描述:

我正在寻找一种更好的方式来实现这种逻辑:Pythonic方式“如果Y失败,在Y之前运行X”?

if not a(): 
    if not b(): 
     c() 
     b() 
    a() 

另一种形式:

try: 
    a() 
except: 
    try: 
     b() 
     a() 
    except: 
     c() 
     b() 
     a() 

在口头上,“尝试运行A.如果我们不能做到,我们需要先做B,如果我们不能做B,我们需要先做C,等等。“

+0

您正在运行'了'和'b '在引发异常之后运行的异常块中。这对我来说是个不好的主意。 – tacaswell 2013-04-04 00:57:25

+2

你的确切*问题/情况是什么? – Blender 2013-04-04 00:58:28

+0

所以如果'a()'失败,'b()'可能会以某种方式成功运行'a()'?这让我感到害怕......你有什么确切的用例? – 2013-04-04 00:58:28

不确定您是否对此感觉“更好”;这是另一种选择。我相信有些人喜欢它,有些人却不喜欢。

a() or (b(),a())[0] or (c(),b(),a())[0] 

下面是验证测试:

def a(ret): 
    print 'run a, a succeeded?', ret 
    return ret 

def b(ret): 
    print 'run b, b succeeded?', ret 
    return ret 

def c(ret): 
    print 'run c, c succeeded?', ret 
    return ret 

而且

a(False) or (b(False),a(False))[0] or (c(True),b(False),a(False))[0] 

run a, a succeeded? False 
run b, b succeeded? False 
run a, a succeeded? False 
run c, c succeeded? True 
run b, b succeeded? False 
run a, a succeeded? False 

而且

a(False) or (b(True),a(False))[0] or (c(True),b(True),a(False))[0] 

run a, a succeeded? False 
run b, b succeeded? True 
run a, a succeeded? False 

创建一个功能,如fallback_until_success(func_list),其中func_list = [a, b, c]。如果您有参数,可以将它们绑定,例如通过传递(func, *args, **kwargs)的元组。

然后,您可以在while循环中查看列表(包括每次迭代的后退 - 回溯),直到您获得成功或点击列表的末尾;如果你没有成功,返回最后的异常(或异常列表)。

但是,这似乎是一种情况,即通过初始测试来通知您的代码路径比尝试首先执行损坏和回溯更好。你在做的是滥用异常作为消息传递服务。

更新:也为时已晚,现在是这样,但是这里是一个具体的例子:

def fallback_until_success(func_list): 
    index = 0 
    results = [] 
    exceptions = [] 
    while (index < len(func_list)): 
     try: 
      print func_list[index::-1] # debug printing 
      for func_spec in func_list[index::-1]: 
       #func, args, kwargs = func_spec # args variant 
       #result = func(*args, **kwargs) 
       func = func_spec 
       result = func() 
       results.append(result) 
      break 
     except Exception, e: 
      exceptions.append(e) 
      index += 1 
      results = [] 
      continue 
     break 
    return results, exceptions 

# global "environment" vars 
D = { 
     "flag1": False, 
     "flag2": False, 
    } 

def a(): 
    if not D["flag1"]: 
     failstr = "a(): failure: flag1 not set" 
     print failstr 
     raise Exception(failstr) 
    print "a(): success" 
    return D["flag1"] 

def b(): 
    if not D["flag2"]: 
     failstr = "b(): failure: flag2 not set" 
     print failstr 
     raise Exception(failstr) 
    else: 
     D["flag1"] = True 
     print "b(): success" 
    return D["flag2"] 

def c(): 
    D["flag2"] = True 
    print "c(): success" 
    return True 

# args variant 
#results, exceptions = fallback_until_success([(a, [], {}), (b, [], {}), (c, [], {})]) 

results, exceptions = fallback_until_success([a, b, c]) 
print results 
print exceptions 

输出:

[<function a at 0x036C6F70>] 
a(): failure: flag1 not set 
[<function b at 0x03720430>, <function a at 0x036C6F70>] 
b(): failure: flag2 not set 
[<function c at 0x037A1A30>, <function b at 0x03720430>, <function a at 0x036C6F70>] 
c(): success 
b(): success 
a(): success 
[True, True, True] 
[Exception('a(): failure: flag1 not set',), Exception('b(): failure: flag2 not set',)] 

当然,这是基于例外,但你也可以修改这个以基于返回值的成功/失败。

+1

如果最初的测试需要“很长”的时间会怎么样? – 2013-04-04 01:08:00

+2

我的回答没有意义吗?我提出的解决方案基本上是您在答案中发布的try-except链的语法糖。不知道为什么你认为它会更慢? – 2013-04-04 01:17:59

+0

我回应了你的“然而...”段落。我认为你的答案的第一部分是正确的,但我不知道它会在哪里回溯到重试'func_list'中的早期项目。 – 2013-04-04 01:24:26

如何:

while not a(): 
    while not b(): 
     c() 

这仅适用,只要c()预计最终使b()(同样为b()a())成功,但是这对我来说是一种比较常见的模式。

+0

我喜欢这个,但如果由于某种原因'a'或'b'总是失败,我们会得到一个无限循环。 – 2013-04-04 01:34:46

这应该工作。请注意,如果失败,它将执行b,c,a。如果b然后失败,它将执行c,a,b - 也就是说,不是原来的顺序,但是如果顺序没有任何特定的偏好,它应该是好的。

ops = [a,b,c] 

while op = ops.pop(0): 
    if (op()): 
    continue 
    ops.append(op) 

基于邵川王的answer,我想我可能最终会做这样的事情:

any(all((a())), 
    all((b(), a())), 
    all((c(), b(), a()))) 

为什么暴露了这一切给调用者?来电者不应该知道/关心的一个小部件如何工作机制的细节。为什么不通过执行类似隔离从“胆”的客户端代码:

do_stuff() # This is the only call you make directly 

def do_stuff(): 
    ## commands for Step A, in this case 
    ## try to update the changeset 
    result = False 
    # Do stuff and store result 
    if (result == False): 
     result = step_B() 
    return result 

def step_B(): 
    ## Commands for Step B, in this case 
    ## try to pull the repository 
    result = False 
    # Do stuff and store the result 
    if (result == False): 
     result = step_C() 
    return result 

def step_C(): 
    ## Commands for Step C, in this case 
    ## try to clone the repository 
    ## and set `result' to True or False 
    result = False 
    # Do stuff and set `result' 
    return result