在存储检查结果时像 Python 的 "and" 那样进行短路评估

Short-circuit evaluation like Python's "and" while storing results of checks

我有多个 return 结果的昂贵功能。如果所有检查都成功,我想 return 所有检查结果的元组。但是,如果一次检查失败,我不想调用后面的检查,例如 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

有更短的方法吗?

只需使用一个普通的旧 for 循环:

results = {}
for function in [check_a, check_b, ...]:
    results[function.__name__] = result = function()
    if not result:
        break

结果将是函数名称到它们的 return 值的映射,您可以在循环中断后对这些值执行您想要的操作。

如果您想要对所有函数都具有 returned truthy 结果的情况进行特殊处理,请在 for 循环中使用 else 子句。

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

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)

您可以使用列表或 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))

在其他有 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 元素放在列表中,我宁愿避免这种方法。不过,它可能比某些循环更快更短。

主要逻辑:

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 和 search/implement 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()

由于我不能评论"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)

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

您可以使用生成器表达式来延迟函数的执行。然后你可以使用 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

您可以尝试使用来自 lazy_python@lazy_function 装饰器 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')

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

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 函数以在错误的 return 值上引发错误。

简而言之,异常处理非常灵活,正是您想要的,不要害怕使用它。如果你在某处了解到异常处理不是用于你自己的流程控制,这不适用于python。异常处理的自由使用被认为是 pythonic,如 EAFP.

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

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()

这类似于 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,)

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

解决此问题的另一种方法是使用生成器,因为生成器使用惰性求值。首先将所有检查放入生成器:

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

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

list(checks())

但是标准的 all 函数对从 checks() 返回的迭代器进行适当的快捷方式评估,returns 是否所有元素都为真:

all(checks())

最后,如果你想要成功检查直到失败的结果,你可以使用 itertools.takewhile 只取第一个 运行 的真值。由于 takewhile 的结果本身是惰性的,您需要将其转换为列表才能在 REPL 中查看结果:

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

如果反对的主要是

This is messy if there are many checks:

if a:
   b = check_b()

   if b:
      c = check_c()

      if c:
          return a, b, c

一个相当不错的模式是扭转这种情况,return尽早

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

你在回答中提到了'short-circuiting',这可以用'or'语句来完成。最佳答案基本上做同样的事情,但如果有人想了解更多关于这种行为的信息,你可以这样做;

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 语句通过而不评估剩余值。