惯用的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}