38

有没有更好的方法在 python 中编写这段代码?

result = slow_function()
if result:
    return result
[...]

该函数slow_function可以返回一个值,或者None它很慢,所以这是不可行的:

if slow_function():
    return slow_function()

第一种方法没有什么问题,但是使用临时变量对 python 来说似乎有点过头了。

当您通过本地假设使用递归调用解决问题时,此代码非常有用f,例如,您从列表中选择一个项目,然后检查是否有可行的解决方案,否则您必须选择另一个。就像是:

def f(n):
    for x in xrange(n):
        result = slow_function(x):
        if result:
            return result
        [...]

像这样更惯用的东西不是更好吗:

def f(n):
    for x in xrange(n):
        return slow_function(x) if is not None

这可以扩展到检查任何类型的值。这将是一个易于阅读的return if语句。


代码爱好者的附加示例

假设您有一个数字列表列表:

lists = [[1,2,3],[4,5],[6,7,8],[9,10],...]

并且您想为每个列表选择一项,以便选择中最多有一个偶数。可能有很多列表,因此尝试每种组合都是浪费的,因为您已经可以看出,如果您开始选择 [1,2,4,...],则可能没有可行的解决方案。

def check(selected):
    even_numbers = filter(lambda n: (n % 2) == 0, selected)
    return len(even_numbers) < 2

def f(lists, selected=[]):
    if not lists:
        return selected

    for n in lists[0]:
        if check(selected + [n]):
            result = f(lists[1:], selected + [n])
            if result:
                return result

像这样的语法不是更好吗:

def f(lists, selected=[]):
    return selected if not lists
    for n in lists[0]:
        if check(selected + [n]):
            return f(lists[1:], selected + [n]) if is not None

到目前为止,我所做的最好的事情是将函数转换为可行解决方案的生成器:

def f(lists, selected=[]):
    if not lists:
        yield selected
    else:
        for n in lists[0]:
            if check(selected + [n]):
                for solution in f(lists[1:], selected + [n]):
                    yield solution
4

6 回答 6

14

在不知道您可能还想返回什么的情况下,有一些选择。

  1. 您可以只返回函数的结果,也可以None不返回:

    return slow_function()
    

    在这种情况下,您依靠调用者知道如何处理该None值,并且实际上只是将您的逻辑转移到哪里。

  2. 如果你有一个默认值来返回而不是 None,你可以这样做:

    return slow_function() or default
    

    在上面的这个,如果slow_functionNone(这是“假的”),它将返回后一个值,否则,如果slow_function返回一个“真实”的值,它将返回那个值。请注意,如果slow_function可以返回其他“虚假”值,例如False[]0,则这些值将被忽略。

  3. 或者,有时您拥有的是完全有效的代码。您想与一个值进行比较,如果它是值,则返回它。你拥有的代码在它的作用上是显而易见的,有时这比代码的“聪明”更重要。

根据评论,如果您的代码必须继续运行(如果该值是),None那么最明显的方法是将其存储为临时值。但是,这不是一件坏事,因为它读起来很干净。

  • 计算一个值并将其存储为结果
  • 如果有一个有效的结果,返回它。
  • 否则,继续做事以获得更好的结果。

更好通常是非常主观的,从计算的角度来看,我看不到任何明显的改进方法,而且正如所写的那样,它非常易于阅读,这是一个明显的优势。其他解决方案可能更短或更聪明,但人类可读性通常是代码的一个被忽视的优势。

于 2013-10-16T23:22:12.743 回答
10

您的最新评论可能更清楚您想要做什么:

想象一下,您传递 fa list 并选择一个项目,然后调用自己传递没有项目的列表,依此类推,直到您没有更多项目。您检查解决方案是否可行,如果可行,您将返回解决方案,这需要一直通过调用堆栈,否则返回 None。通过这种方式,您将按拓扑顺序探索所有问题,但当您知道先前选择的项目无法创建可行的解决方案时,您也可以跳过检查。

也许您可以尝试使用yield而不是return. 也就是说,您的递归函数不会生成一种解决方案,而是会产生所有可能的解决方案。没有一个具体的例子,我不能确定你在做什么,但在它之前说是这样的:

def solve(args, result_so_far):
    if not slow_check_is_feasible(result_so_far):
        #dead-end
        return None

    if not args:
        #valid and done
        return result_so_far

    for i, item in enumerate(args):
        #pass list without args - slow
        new_args = args[:]
        del new_args[i]
        result = solve(new_args, accumulate_result(result_so_far, item)
        if result is not None:
            #found it, we are done
            return result
        #otherwise keep going

现在看起来像这样:

def solve_all(args, result_so_far):
    if not slow_check_is_feasible(result_so_far):
        #dead-end
        return

    if not args:
        #yield since result was good
        yield result_so_far
        return

    for i, item in enumerate(args):
        #pass list without args - slow
        new_args = args[:]
        del new_args[i]
        for result in solve(new_args, accumulate_result(result_so_far, item):
            yield result

好处是:

  • 您会生成所有答案而不仅仅是第一个答案,但如果您仍然只想要一个答案,那么您可以获得第一个结果。
  • 在您将返回值用于错误检查和答案之前。现在你只有在有答案时才会屈服。
于 2013-10-17T00:21:44.680 回答
7

本质上,您想要评估一个表达式,然后使用它两次而不将其绑定到局部变量。由于我们没有匿名变量,因此唯一的方法是将其传递给函数。幸运的是,当前函数是否返回的控制流不受它调用的函数的控制……但是,异常确实会向上传播调用堆栈。

我不会说这更好,但你可以滥用异常来得到你想要的。这永远不应该真正使用,它更像是一种好奇心的练习。结果最终看起来像这样(注意装饰器的使用):

def slow_function(x):
    if x % 5 == 0:
        return x * 200

@if_returner
def foobme(l):
    for i in l:
        print "Checking %s..." % (i,)
        return_if(slow_function(i))

print foobme([2, 3, 4, 5, 6])

输出是:

Checking 2...
Checking 3...
Checking 4...
Checking 5...
1000

诀窍是捎带异常处理,因为它们会在函数调用中传播。如果你喜欢它,这里是实现:

class ReturnExc(Exception):
    def __init__(self, val):
        self.val = val

def return_if(val):
    if val is not None:
        raise ReturnExc(val)

def if_returner(f):
    def wrapped(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except ReturnExc, e:
            return e.val
    return wrapped
于 2013-10-16T23:27:47.167 回答
2

不是真正的建议,但您可以滥用列表理解并按照以下方式做一些事情:

# Note: Doesn't work in python 3.
def func():
    if [value for value in (slow_function(),) if value is not None]:
        return value
    # continue processing...
于 2013-10-17T02:28:08.970 回答
2

对于在slow_function循环上操作的问题,生成器表达式似乎是要走的路。在 Python 3 中,这里的一切都是惰性的,因此您可以免费获得过滤器:

f = filter(slow_function(x) for x in range(...))

在 Python 2 中,您只需要 itertools:

from itertools import ifilter

f = ifilter(slow_function(x) for x in xrange(...))

每次迭代只会在您要求时发生。如果在函数返回false的情况下需要继续操作,那么你需要将Falseness作为哨兵,所以你的解决方案很好:

def f():
  for x in xrange(...):
    sentinel = slow_function(x)
    if sentinel:
      return sentinel
    # continue processing

或者您也可以在这里使用生成器为自己保存一个变量:

from itertools import imap

def f():
  for x in imap(slow_function, xrange(...)):
    if x:
      return x
    # continue processing
于 2013-10-16T23:58:33.453 回答
-3

你写的看起来不错,但如果你想避免多个 return 语句,你可以这样做:

def f():
    result = slow_function()
    if result is None:
        [...]
        result = [...]
    return result
于 2013-10-16T23:30:54.303 回答