在存储检查结果时像 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 语句通过而不评估剩余值。
我有多个 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 语句通过而不评估剩余值。