短路评估,如Python"和"同时存储支票结果

时间:2016-09-20 20:42:30

标签: python short-circuiting

我有多个昂贵的函数可以返回结果。如果所有检查都成功,我想返回所有检查结果的元组。但是,如果一次检查失败,我不想调用后面的检查,例如and的短路行为。我可以嵌套if语句,但是如果有很多检查,这将失控。如何在保存结果供以后使用的同时获得and的短路行为?

def check_a():
    # do something and return the result,
    # for simplicity, just make it "A"
    return "A"

def check_b():
    # do something and return the result,
    # for simplicity, just make it "B"
    return "B"

...

这不会发生短路:

a = check_a()
b = check_b()
c = check_c()

if a and b and c:
    return a, b, c

如果检查很多,这很麻烦:

if a:
   b = check_b()

   if b:
      c = check_c()

      if c:
          return a, b, c

有更短的方法吗?

14 个答案:

答案 0 :(得分:27)

只需使用普通的旧循环:

public int example(int[] array) {

    int result = 15;
    int i = array.length;

    while(i > 1)
    {
      for(int x = 0; x < array.length;x++)
      {
        result+= 1;
        result+=2*array[x];
      }
      i = i/2; 
    }
return result;
}

结果将是函数名称与其返回值的映射,您可以在循环中断后使用值执行所需操作。

如果要对所有函数都返回真实结果的情况进行特殊处理,请在for循环中使用results = {} for function in [check_a, check_b, ...]: results[function.__name__] = result = function() if not result: break 子句。

答案 1 :(得分:8)

编写一个可以运行可迭代函数的函数。调用每个结果并将结果附加到列表中,如果结果为None,则返回False。该函数将在一个失败后停止调用进一步的检查,或者它将返回所有检查的结果。

def all_or_none(checks, *args, **kwargs):
    out = []

    for check in checks:
        rv = check(*args, **kwargs)

        if not rv:
            return None

        out.append(rv)

    return out
rv = all_or_none((check_a, check_b, check_c))

# rv is a list if all checks passed, otherwise None
if rv is not None:
    return rv
def check_a(obj):
    ...

def check_b(obj):
    ...

# pass arguments to each check, useful for writing reusable checks
rv = all_or_none((check_a, check_b), obj=my_object)

答案 2 :(得分:6)

使用assignments as expressions的其他语言,您可以使用

if (a = check_a()) and (b = check_b()) and (c = check_c()):

但Python不是这样的语言。不过,我们可以绕过限制并模仿这种行为:

result = []
def put(value):
    result.append(value)
    return value

if put(check_a()) and put(check_b()) and put(check_c()):
    # if you need them as variables, you could do
    # (a, b, c) = result
    # but you just want
    return tuple(result)

这可能会使变量和函数调用之间的连接松散一些,所以如果你想用变量做很多单独的事情,而不是按照它们放入的顺序使用result元素列表,我宁愿避免这种方法。不过,它可能比某些循环更快更短。<​​/ p>

答案 3 :(得分:3)

您可以使用列表或OrderedDict,使用for循环可以起到模拟短路的作用。

from collections import OrderedDict


def check_a():
    return "A"


def check_b():
    return "B"


def check_c():
    return "C"


def check_d():
    return False


def method1(*args):
    results = []
    for i, f in enumerate(args):
        value = f()
        results.append(value)
        if not value:
            return None

    return results


def method2(*args):
    results = OrderedDict()

    for f in args:
        results[f.__name__] = result = f()
        if not result:
            return None

    return results

# Case 1, it should return check_a, check_b, check_c
for m in [method1, method2]:
    print(m(check_a, check_b, check_c))

# Case 1, it should return None
for m in [method1, method2]:
    print(m(check_a, check_b, check_d, check_c))

答案 4 :(得分:2)

有很多方法可以做到这一点!这是另一个。

您可以使用生成器表达式来推迟函数的执行。然后,您可以使用itertools.takewhile来实现短路逻辑,方法是使用生成器中的项目,直到其中一个为假。

from itertools import takewhile
functions = (check_a, check_b, check_c)
generator = (f() for f in functions)
results = tuple(takewhile(bool, generator))
if len(results) == len(functions):
    return results

答案 5 :(得分:2)

解决这个问题的另一种方法是使用生成器,因为生成器使用惰性求值。首先将所有支票放入发电机:

def checks():
    yield check_a()
    yield check_b()
    yield check_c()

现在,您可以通过将其转换为列表来强制评估所有内容:

list(checks())

但是标准的all函数对从checks()返回的迭代器进行了正确的快捷方式评估,并返回所有元素是否真实:

all(checks())

最后,如果您希望成功检查的结果达到失败,您可以使用itertools.takewhile仅执行第一次运行的truthy值。由于takewhile的结果本身是懒惰的,你需要将它转换为列表以在REPL中查看结果:

from itertools import takewhile
takewhile(lambda x: x, checks())
list(takewhile(lambda x: x, checks()))

答案 6 :(得分:1)

主要逻辑:

results = list(takewhile(lambda x: x, map(lambda x: x(), function_list)))
if len(results) == len(function_list):
  return results

如果你查看api的所有方法,如http://www.scala-lang.org/api/2.11.7/#scala.collection.immutable.List和搜索/实现python等价物,你可以学到很多关于集合转换的知识

逻辑与设置和替代方案:

import sys
if sys.version_info.major == 2:
  from collections import imap
  map = imap

def test(bool):
  def inner():
    print(bool)
    return bool
  return inner

def function_for_return():
  function_list = [test(True),test(True),test(False),test(True)]

  from itertools import takewhile

  print("results:")

  results = list(takewhile(lambda x:x,map(lambda x:x(),function_list)))
  if len(results) == len(function_list):
    return results

  print(results)
  #personally i prefer another syntax:
  class Iterator(object):
    def __init__(self,iterable):
      self.iterator = iter(iterable)

    def __next__(self):
      return next(self.iterator)

    def __iter__(self):
      return self

    def map(self,f):
      return Iterator(map(f,self.iterator))

    def takewhile(self,f):
      return Iterator(takewhile(f,self.iterator))

  print("results2:")
  results2 = list(
    Iterator(function_list)
      .map(lambda x:x())
      .takewhile(lambda x:x)    
  )

  print(results2)

  print("with additional information")
  function_list2 = [(test(True),"a"),(test(True),"b"),(test(False),"c"),(test(True),"d")]
  results3 = list(
    Iterator(function_list2)
      .map(lambda x:(x[0](),x[1]))
      .takewhile(lambda x:x[0])    
  )
  print(results3)

function_for_return()

答案 7 :(得分:1)

灵活的短路最好用异常来完成。对于一个非常简单的原型,您甚至可以断言每个检查结果:

try:
    a = check_a()
    assert a
    b = check_b()
    assert b
    c = check_c()
    assert c
    return  a, b, c
except AssertionException as e:
    return None

您应该提出自定义异常。您可以以任意嵌套方式更改check_X函数以引发自己的异常。或者你可以包装或修饰你的check_X函数,以便在错误的返回值上引发错误。

简而言之,异常处理非常灵活,正是您所寻找的,不要害怕使用它。如果您在某处学习了异常处理不用于您自己的流控制,那么这不适用于python。自由使用异常处理被认为是pythonic,如EAFP

答案 8 :(得分:1)

如果您不需要在运行时使用任意数量的表达式(可能包含在lambdas中),您可以将代码直接扩展为此模式:

def f ():
    try:
        return (<a> or jump(),
                <b> or jump(),
                <c> or jump())
    except NonLocalExit:
        return None

这些定义适用的地方:

class NonLocalExit(Exception):
    pass

def jump():
    raise NonLocalExit()

答案 9 :(得分:1)

您在回答中提到了“短路”,这可以通过“或”语句完成。最佳答案基本上做同样的事情,但如果有人想更多地了解这种行为,你可以这样做;

class Container(object):
    def __init__(self):
        self.values = []

    def check_and_cache(self, value, checking_function):
        value_true = checking_function(value)
        if value_true:
            self.values.append(value)
            return True

c = Container()
if not c.check_and_cache(a, check_a) or not c.check_and_cache(b, check_b) or not c.check_and_cache(c, check_c):
    print 'done'
return tuple(c.values)

如果检查失败,if 语句的“not .. or”设置将导致“True”,因此整个 if 语句通过而不评估剩余值。

答案 10 :(得分:0)

由于我无法评论“wim”:作为嘉宾的答案,我只会添加一个额外的答案。 由于你需要一个元组,你应该在列表中收集结果,然后转换为元组。

def short_eval(*checks):
    result = []
    for check in checks:
        checked = check()
        if not checked:
            break
        result.append(checked)
    return tuple(result)

# Example
wished = short_eval(check_a, check_b, check_c)

答案 11 :(得分:0)

您可以尝试使用@lazy_function中的lazy_python装饰器 package。用法示例:

from lazy import lazy_function, strict

@lazy_function
def check(a, b):
    strict(print('Call: {} {}'.format(a, b)))
    if a + b > a * b:
        return '{}, {}'.format(a, b)

a = check(-1, -2)
b = check(1, 2)
c = check(-1, 2)

print('First condition')
if c and a and b: print('Ok: {}'.format((a, b)))

print('Second condition')
if c and b: print('Ok: {}'.format((c, b)))
# Output:
# First condition
# Call: -1 2
# Call: -1 -2
# Second condition
# Call: 1 2
# Ok: ('-1, 2', '1, 2')

答案 12 :(得分:0)

这类似于Bergi的答案,但我认为答案忽略了想要单独的函数(check_a,check_b,check_c):

list1 = []

def check_a():
    condition = True
    a = 1
    if (condition):
        list1.append(a)
        print ("checking a")
        return True
    else:
        return False

def check_b():
    condition = False
    b = 2
    if (condition):
        list1.append(b)
        print ("checking b")
        return True
    else:
        return False

def check_c():
    condition = True
    c = 3
    if (condition):
        list1.append(c)
        print ("checking c")
        return True
    else:
        return False


if check_a() and check_b() and check_c():
    # won't get here

tuple1 = tuple(list1)    
print (tuple1)    

# output is:
# checking a
# (1,)

或者,如果您不想使用全局列表,请将本地列表的引用传递给每个函数。

答案 13 :(得分:0)

如果主要提出异议

  

如果检查很多,这很麻烦:

if a:
   b = check_b()

   if b:
      c = check_c()

      if c:
          return a, b, c

一个相当不错的模式是扭转条件并提前返回

if not a:
    return  # None, or some value, or however you want to handle this
b = check_b()
if not b:
    return
c = check_c()
if not c:
    return

# ok, they were all truthy
return a, b, c