【问题标题】:select single item from a collection : Python从集合中选择单个项目:Python
【发布时间】:2010-10-03 02:55:40
【问题描述】:

我创建了一个实用函数来从生成器表达式返回预期的单个项目

print one(name for name in ('bob','fred') if name=='bob')

这是一个好方法吗?

def one(g):
    try:
        val = g.next()
        try:
            g.next()
        except StopIteration:
            return val
        else:
            raise Exception('Too many values')
    except StopIteration:
        raise Exception('No values')

【问题讨论】:

  • 你为什么要关心是否不止一件?
  • 我正在处理来自关系数据库的数据。我想在处理的同时检查它的完整性,完整性规则可以是“父母必须只有一个特定类型的孩子”或“父母必须有一个且只有一个特定类型的孩子”
  • 这听起来真是个坏主意(tm)
  • 我是认真的!任何产生 g 的东西都应该检查一致性,而不是用掉 g 的东西。如果我向 one() 要一件 g 并且它会返回“对不起,我可以给你一个,但我不会因为我有太多!”我会诅咒[你,可能]。
  • 接受这在我使用它的条件下是完全有效的,以后请坚持回答具体问题,不要试图让人们失望。

标签: python iterator generator


【解决方案1】:

对于那些使用或对第三方库感兴趣的人,more_itertools 实现了这样一个带有本机错误处理的工具:

> pip install more_itertools

代码

import more_itertools as mit


mit.one(name for name in ("bob", "fred") if name == "bob")
# 'bob'

mit.one(name for name in ("bob", "fred", "bob") if name == "bob")
# ValueError: ...

mit.one(name for name in () if name == "bob")
# ValueError: ...

详情请见more_itertools docsunderlying source code 类似于接受的答案。

【讨论】:

    【解决方案2】:

    在带有计数器的语法中使用 Python 的 for .. 怎么样?类似于未知的答案。

    def one(items):
        count = 0
        value = None
    
        for item in items:
            if count:
                raise Exception('Too many values')
    
            count += 1
            value = item
    
        if not count:
            raise Exception('No values')
    
        return value
    

    【讨论】:

      【解决方案3】:

      一个更简单的解决方案是使用元组解包。这已经可以满足您的所有需求,包括检查它是否只包含一项。

      单件:

       >>> name, = (name for name in ('bob','fred') if name=='bob')
       >>> name
       'bob'
      

      项目太多:

      >>> name, = (name for name in ('bob','bob') if name=='bob')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: too many values to unpack
      

      没有项目:

      >>> name, = (name for name in ('fred','joe') if name=='bob')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: need more than 0 values to unpack
      

      【讨论】:

      • 聪明,但也许太聪明了。
      • 另外,很容易错过。我更喜欢写[name] = (name for name in ('bob','fred') if name=='bob')——编译时完全一样,但更明显的是发生了什么。
      【解决方案4】:

      首先,(回答实际问题!)您的解决方案将像其他提议的变体一样正常工作。

      我要补充一点,在这种情况下,IMO 生成器过于复杂。如果你希望有一个值,你可能永远不会有足够的内存使用成为一个问题,所以我会使用明显和更清晰的:

      children = [name for name in ('bob','fred') if name=='bob']
      if len(children) == 0:
          raise Exception('No values')
      elif len(children) > 1:
          raise Exception('Too many values')
      else:
          child = children[0]
      

      【讨论】:

        【解决方案5】:

        这是我对one() 函数的尝试。我会避免显式的 .next() 调用,而是使用 for 循环。

        def one(seq):
            counter = 0
            for elem in seq:
                result = elem
                counter += 1
                if counter > 1:
                    break
            if counter == 0:
                raise Exception('No values')
            elif counter > 1:
                raise Exception('Too many values')
            return result
        

        【讨论】:

          【解决方案6】:

          简单的方法:

          print (name for name in ('bob', 'fred') if name == 'bob').next()
          

          如果你真的想在有多个值时出现错误,那么你需要一个函数。我能想到的最简单的是(EDITED 也可以使用列表):

          def one(iterable):
              it = iter(iterable)
              val = it.next()
              try:
                  it.next()
              except StopIteration:
                  return val
              else:
                  raise Exception('More than one value')
          

          【讨论】:

          • 这个答案很好,因为它可以很容易地适应有效地执行类似的 linqy 事情,比如 SingleOrDefault,单行技巧虽然很酷,但不能。
          【解决方案7】:

          你的意思是?

          def one( someGenerator ):
              if len(list(someGenerator)) != 1: raise Exception( "Not a Singleton" )
          

          你想用所有额外的代码来完成什么?

          【讨论】:

          • TypeError:“生成器”类型的对象没有 len()。我想我可以:len(list(someGenerator))
          • 这就是我要做的,它看起来更清晰,应该对性能影响最小。
          • 由于不期望生成器是无限的,这似乎是解决这个不雅点的最优雅的解决方案。
          • @hop 生成器可以是无限的就好了。例如。我曾经写过一个 CPU 使用率的生成器。无论如何,读取整个生成的列表以仅使用一个元素是无法衡量的浪费。
          【解决方案8】:

          查看itertools.islice() 方法。

          >>> i2=itertools.islice((name for name in ('bob','fred') if name=='bob'),0,1,1)
          >>> i2.next()
          'bob'
          >>> i2.next()
          Traceback (most recent call last):
            File "<interactive input>", line 1, in <module>
          StopIteration
          >>> 
          

          该模块实现了许多受 Haskell 和 SML 编程语言构造启发的迭代器构建块。每个都以适合 Python 的形式重铸。

          该模块标准化了一组核心的快速、高效的内存工具,这些工具本身或组合使用都很有用。标准化有助于避免当许多不同的人创建自己的略有不同的实现时出现的可读性和可靠性问题,每个人都有自己的怪癖和命名约定。

          这些工具旨在轻松地相互结合。这使得在纯 Python 中简洁高效地构建更专业的工具变得容易。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-12-21
            • 1970-01-01
            • 1970-01-01
            • 2015-07-13
            • 2017-01-23
            • 1970-01-01
            相关资源
            最近更新 更多