地道的Python has_one

3 投票
8 回答
584 浏览
提问于 2025-04-15 15:09

我刚刚发明了一个小助手函数:

def has_one(seq, predicate=bool):
    """Return whether there is exactly one item in `seq` that matches
    `predicate`, with a minimum of evaluation (short-circuit).
    """
    iterator = (item for item in seq if predicate(item))
    try:
        iterator.next()
    except StopIteration: # No items match predicate.
        return False
    try:
        iterator.next()
    except StopIteration: # Exactly one item matches predicate.
        return True
    return False # More than one item matches the predicate.

因为我能想到的最简单易懂的写法是:

[predicate(item) for item in seq].count(True) == 1

... 这在我的情况下是可以的,因为我知道序列(seq)很小,但感觉就是怪怪的。 有没有什么更好的写法可以让我不需要单独写这个助手函数呢?

澄清

回头看,这个问题问得有点糟糕,不过我们得到了很棒的答案!我当时想找的是:

  • 一个明显且易读的内联写法或者标准库函数,急切求值在这种情况下是可以接受的。
  • 一个更明显且易读的助手函数——因为要单独写一个函数,所以只希望最少的求值是可以接受的。

@Stephan202 提出了一个很酷的助手函数写法,而 @Martin v. Löwis 在假设条件返回布尔值的情况下,提出了一个更简单的内联写法。感谢大家的帮助!

8 个回答

3

也许这样的写法更符合你的口味?

def has_one(seq,predicate=bool):
    nwanted=1
    n=0
    for item in seq:
        if predicate(item):
            n+=1
            if n>nwanted:
                return False

    return n==nwanted

这有点像列表推导的例子,但只需要对一个序列进行一次遍历。跟第二个 has_one 函数相比,这种写法和列表推导的代码一样,更容易适应其他计数的需求。我通过添加一个变量来表示想要的项目数量来演示这一点(希望没有错误...)。

10

如果在一个迭代器上调用any两次,会怎么样呢?(这在Python 2.x和3.x中都适用)

>>> def has_one(seq, predicate=bool):
...     seq = (predicate(e) for e in seq)
...     return any(seq) and not any(seq)
... 
>>> has_one([])
False
>>> has_one([1])
True
>>> has_one([0])
False
>>> has_one([1, 2])
False

any函数最多只会从迭代器中取出一个元素,并判断这个元素是否为True。如果第一次成功找到了一个符合条件的元素,而第二次没有找到,那就说明只有一个元素符合这个条件。

补充:我看到Robert Rossney提到了一种更通用的写法,可以检查是否恰好有n个元素符合条件。让我也来参与一下,用all来实现:

>>> def has_n(seq, n, predicate=bool):
...     seq = (predicate(e) for e in seq)
...     return all(any(seq) for _ in range(n)) and not any(seq)
... 
>>> has_n(range(0), 3)
False
>>> has_n(range(3), 3)
False
>>> has_n(range(4), 3)
True
>>> has_n(range(5), 3)
False
2

不太确定这是否比你提议的版本更好,不过……

如果这个条件(predicate)保证只会返回真(True)或假(False),那么

sum(map(predicate, seq)) == 1

这样写是可以的(不过它不会在第二个元素时就停止)

撰写回答