惯用的Python有一个

惯用的Python有一个,python,idioms,Python,Idioms,我刚刚发明了一个愚蠢的小助手函数: 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))

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

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很小,但感觉很奇怪这里有没有一个我忘了的习惯用法,可以阻止我摆脱这个助手?

澄清 回顾过去,这是一个很糟糕的问题,尽管我们得到了一些很好的答案!我想要的是:

  • 一个明显易读的内联习惯用法或stdlib函数,在这种情况下可以接受急切的求值
  • 一个更明显、更可读的帮助函数——因为它是一个完整的其他函数,所以只有最小的求值量看起来是可以接受的

为helper函数提出了一个非常酷的习惯用法,并在谓词返回bool的假设下提出了一个更简单的内联习惯用法。谢谢大家的帮助

不确定它是否比您建议的版本更好,但是

若谓词保证只返回True/False,则

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

可以(虽然不会在第二个元素处停止)

也许这样的东西更符合你的口味

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
这与列表理解示例非常相似,但只需要对一个序列进行一次传递。与第二个
相比,它有一个
功能,并且与列表理解代码一样,它更容易推广到其他计数。我已经通过添加所需项数的变量来演示了这一点(希望没有错误…)。

在迭代器上调用两次如何(与Python 2.x和3.x兼容)

any
将从迭代器中最多获取一个计算结果为
True
的元素。如果第一次成功,第二次失败,那么只有一个元素与谓词匹配

Edit:我看到了一个通用版本,它检查n个元素是否与谓词完全匹配。让我通过以下方式参与其中:


我喜欢Stephan202的答案,但我更喜欢这个,尽管它是两行而不是一行。我喜欢它,因为它同样疯狂,但对于它的疯狂是如何运作的,有一点更加明确:

def has_one(seq):
    g = (x for x in seq)
    return any(g) and not any(g)
编辑:

下面是一个更通用的版本,它支持谓词:

def has_exactly(seq, count, predicate = bool):
    g = (predicate(x) for x in seq)
    while(count > 0):
        if not any(g):
            return False
        count -= 1
    if count == 0:
        return not any(g)
那么

import functools
import operator

def exactly_one(seq):
    """
    Handy for ensuring that exactly one of a bunch of options has been set.
    >>> exactly_one((3, None, 'frotz', None))
    False
    >>> exactly_one((None, None, 'frotz', None))
    True
    """
    return 1 == functools.reduce(operator.__add__, [1 for x in seq if x])
以下是修改后的:

差异:

  • predicate()
    默认为无。其含义与内置的
    filter()
    和stdlib的
    itertools.ifilter()函数的含义相同

  • 更明确的函数和参数名称(这是主观的)

  • repeat()
    允许使用较大的
    n

  • 例如:

    if exactly_n_is_true(seq, 1, predicate):
       # predicate() is true for exactly one item from the seq
    
    看,妈!没有rtfm(“itertools”),没有对返回布尔值的谓词()的依赖,最小求值,只是工作

    Python 1.5.2 (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)] on win32
    Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
    >>> def count_in_bounds(seq, predicate=lambda x: x, low=1, high=1):
    ...     count = 0
    ...     for item in seq:
    ...         if predicate(item):
    ...             count = count + 1
    ...             if count > high:
    ...                 return 0
    ...     return count >= low
    ...
    >>> seq1 = [0, 0, 1, 0, 1, 0, 1, 0, 0, 0]
    >>> count_in_bounds(seq1)
    0
    >>> count_in_bounds(seq1, low=3, high=3)
    1
    >>> count_in_bounds(seq1, low=3, high=4)
    1
    >>> count_in_bounds(seq1, low=4, high=4)
    0
    >>> count_in_bounds(seq1, low=0, high=3)
    1
    >>> count_in_bounds(seq1, low=3, high=3)
    1
    >>>
    
    简单的计数循环解决方案绝对是最清晰的

    作为一项运动,这里有一个关于
    any(g)而不是any(g)
    主题的变体,它表面上看起来不那么神奇,但实际上在调试/修改它时,它同样脆弱(您不能交换订单,您必须了解短路
    如何在两个短路消费者之间传递单个迭代器…)

    也可以使用谓词而不是
    bool
    ,但我认为最好遵循
    any()
    all()
    将其留给调用者-如果需要,可以很容易地传递生成器表达式

    使用任意[start,stop]是一个不错的奖励,但它并不像我希望的那样通用。通过
    stop=None
    进行模拟是很有诱惑力的,例如
    any()
    ,它可以工作,但总是消耗所有输入;正确的模拟有点尴尬:

    def any(iterable):
      return not count_in_bounds(iterable, 0, 1)
    
    def all(iterable):
      return count_in_bounds((not x for x in iterable), 0, 1)
    
    获取一个可变数量的边界并指定哪个应该返回True/False将无法控制。
    也许一个简单的饱和计数器是最好的原语:

    def count_true(iterable, stop_at=float('inf')):
        c = 0
        for x in iterable:
            c += bool(x)
            if c >= stop_at:
                break
        return c
    
    def any(iterable):
        return count_true(iterable, 1) >= 1
    
    def exactly_one(iterable):
        return count_true(iterable, 2) == 1
    
    def weird(iterable):
        return count_true(iterable, 10) in {2, 3, 5, 7}
    

    all()
    仍然需要对输入求反,或者匹配的
    count\u false()
    helper。

    这里没有问题。或者,如果有问题,它是隐藏得很好的。@s.Lott:“这里有没有一个我忘记的习惯用法,可以阻止我破解这个helper?”在任何情况下,如果有0个或一个匹配项,你就必须查看每一个项目。考虑使用这样的东西来看看是否有一个偶数的素数。怎么可能有人不喜欢迭代器?我认为在第二个项目中停止执行是这个问题的要点。exercise@SilentGhost:我不确定。他贴在名单上的版本是ion没有;他只是说感觉“奇怪”(出于未指明的原因)。我更喜欢这个问题。对于内联习惯用法,count(True)更合适。在这种特殊情况下,对应的生成器表达式更难看,如sum(1表示seq if谓词(item))中的item),所以我认为map是一种方法。虽然从技术上讲,genexp不依赖谓词返回布尔值。这很聪明,但我认为这可能是一件坏事。我不会责怪人们认为它永远不会返回真值。我也喜欢这个,但它不允许传递谓词。无论如何,考虑到这一点,
    g如何=iter(seq)
    ?@cdleary,在这种情况下,这不是习语的一部分吗?@gnibler:你可能会提出理由,但我并不特别喜欢那些似乎违反基本逻辑定律的习语。(排除中间法则比Python更普遍;-)我当然会添加一条注释来解释它在生产代码中的作用。我喜欢它,这是迭代器的另一个聪明做法。对于python2,只需使用itertools.imap而不是map,更好的是
    seq=(seq if谓词(x)中的x代表x)
    因为2.x
    过滤器
    很急。@THC4K:是的,但我们需要的是
    映射
    ,而不是
    过滤器
    。更新了答案(没有更明确的
    映射
    )。我真的很喜欢t
    Python 1.5.2 (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)] on win32
    Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
    >>> def count_in_bounds(seq, predicate=lambda x: x, low=1, high=1):
    ...     count = 0
    ...     for item in seq:
    ...         if predicate(item):
    ...             count = count + 1
    ...             if count > high:
    ...                 return 0
    ...     return count >= low
    ...
    >>> seq1 = [0, 0, 1, 0, 1, 0, 1, 0, 0, 0]
    >>> count_in_bounds(seq1)
    0
    >>> count_in_bounds(seq1, low=3, high=3)
    1
    >>> count_in_bounds(seq1, low=3, high=4)
    1
    >>> count_in_bounds(seq1, low=4, high=4)
    0
    >>> count_in_bounds(seq1, low=0, high=3)
    1
    >>> count_in_bounds(seq1, low=3, high=3)
    1
    >>>
    
    def cumulative_sums(values):
        s = 0
        for v in values:
            s += v
            yield s
    
    def count_in_bounds(iterable, start=1, stop=2):
        counter = cumulative_sums(bool(x) for x in iterable)
        return (start in counter) and (stop not in counter)
    
    def any(iterable):
      return not count_in_bounds(iterable, 0, 1)
    
    def all(iterable):
      return count_in_bounds((not x for x in iterable), 0, 1)
    
    def count_true(iterable, stop_at=float('inf')):
        c = 0
        for x in iterable:
            c += bool(x)
            if c >= stop_at:
                break
        return c
    
    def any(iterable):
        return count_true(iterable, 1) >= 1
    
    def exactly_one(iterable):
        return count_true(iterable, 2) == 1
    
    def weird(iterable):
        return count_true(iterable, 10) in {2, 3, 5, 7}