Python3 CookBook |

发布时间:2019-10-14 09:22:07编辑:auto阅读(1923)

    文章首发于知乎专栏,欢迎关注。
    https://zhuanlan.zhihu.com/py...

    以下测试代码全部基于 Python3

    1、查找最大或最小的 N 个元素

    工作中有时会遇到这样的需求,取出数据中前面 10% 的值,或者最后 10% 的值。

    我们可以先对这个列表进行排序,然后再进行切片操作,很轻松的解决这个问题。但是,有没有更好的方法呢?

    heapq 模块有两个函数 nlargest() 和 nsmallest() 可以完美解决这个问题。

    In [50]: import heapq
    
    In [51]: n = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2, 23, 45, 76]
    
    In [52]: heapq.nlargest(3, n)
    Out[52]: [76, 45, 42]
    
    In [53]: heapq.nsmallest(3, n)
    Out[53]: [-4, 1, 2]

    如果是取排在前面的 10% 应该怎么做?

    heapq.nlargest(round(len(n)/10), n)

    而且,使用这两个函数还会有更好的性能,因为在底层实现里面,会先把数据进行堆排序后放入一个列表中,然后再进行后续操作。大家如果对堆数据结构感兴趣的话,可以继续进行深入研究,由于我了解的并不深,也没办法再展开了。

    但是也并不是什么时候都是这两个函数效果更好,比如只取一个最大值或者最小值,那还是 min() 或 max() 效果更好;如果要查找的元素个数已经跟集合元素个数接近时,那还是用 sorted(items)[:N] 更好,具体情况具体分析吧。

    2、序列中出现次数最多的元素

    以前碰到这类问题时,我都会手动创建一个字典,然后以列表中元素作为 key,进而统计出 key 出现的次数,再进行比较得到出现次数最多的元素。

    殊不知 collections 中就有专门为这类问题设计的类 Counter,瞬间感觉自己蠢爆了,话不多说,直接上代码。

    In [54]: from collections import Counter
    
    In [55]: w = ['a', 'b', 'c', 'd', 'a', 'a', 'b']
    
    In [56]: w_count = Counter(w)
    
    In [57]: w_count
    Out[57]: Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1})
    
    In [58]: w_count['a']
    Out[58]: 3
    
    In [59]: top = w_count.most_common(2)
    
    In [60]: top
    Out[60]: [('a', 3), ('b', 2)]

    可以看到,Counter 返回的就是一个字典,想知道哪个元素出现几次,直接取,是不是很方便?

    而且还有 most_common 函数,简直不要太棒。

    3、过滤序列元素

    有一个列表,如下:

    In [61]: a = [1, 2, 3, 4, 5, -3]

    要求过滤所有负数。需要新建一个列表?直接一行代码搞定。

    In [64]: [n for n in a if n > 0]
    Out[64]: [1, 2, 3, 4, 5]

    如果要把负数替换成 0 呢?

    In [67]: [n if n > 0 else 0 for n in a]
    Out[67]: [1, 2, 3, 4, 5, 0]

    但是有时候过滤条件可能比较复杂,这时就需要借助于 filter() 函数了。

    values = ['1', '2', '-3', '-', '4', 'N/A', '5']
    def is_int(val):
      try:
        x = int(val)
          return True
      except ValueError:
        return False
    
    ivals = list(filter(is_int, values))
    print(ivals)
    # Outputs ['1', '2', '-3', '4', '5']

    4、通过某个关键字将记录分组

    有下面这个字典:

    rows = [
      {'address': '5412 N CLARK', 'date': '07/01/2012'},
      {'address': '5148 N CLARK', 'date': '07/04/2012'},
      {'address': '5800 E 58TH', 'date': '07/02/2012'},
      {'address': '2122 N CLARK', 'date': '07/03/2012'},
      {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
      {'address': '1060 W ADDISON', 'date': '07/02/2012'},
      {'address': '4801 N BROADWAY', 'date': '07/01/2012'},
      {'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
    ]

    那么怎么对这个字典按照 date 进行分组呢?借助于 itertools.groupby() 函数可以解决这个问题,代码如下:

    # Sort by the desired field first
    rows.sort(key=itemgetter('date'))
    # Iterate in groups
    for date, items in groupby(rows, key=itemgetter('date')):
      print(date)
      for i in items:
        print(' ', i)

    输出结果如下:

    07/01/2012
      {'address': '5412 N CLARK', 'date': '07/01/2012'}
      {'address': '4801 N BROADWAY', 'date': '07/01/2012'}
    07/02/2012
      {'address': '5800 E 58TH', 'date': '07/02/2012'}
      {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
      {'address': '1060 W ADDISON', 'date': '07/02/2012'}
    07/03/2012
      {'address': '2122 N CLARK', 'date': '07/03/2012'}
    07/04/2012
      {'address': '5148 N CLARK', 'date': '07/04/2012'}
      {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}

    需要注意的是,groupby() 函数仅仅检查连续相同的元素,所以在分组之前,一定要先对数据,按照分组字段进行排序。如果没有排序,便得不到想要的结果。

    5、映射名称到序列元素

    我常常有这样的苦恼,就是有一个列表,然后通过下标来取值,取值时很认真的数所需要元素在第几个,很怕取错值。取到值后开始下面的运算。

    一段时间之后,再看这段代码,感觉很陌生,已经忘了带下标的值是什么了,还需要重新看一下这个列表的由来,才找到回忆。

    如果能有一个名称映射到元素上就好了,直接通过名称就可以知道元素的含义。collections.namedtuple() 函数就可以解决这个问题。

    In [76]: from collections import namedtuple
    
    In [77]: subscriber = namedtuple('Subscriber', ['addr', 'joined'])
    
    In [78]: sub = subscriber('jonesy@example.com', '2012-10-19')
    
    In [79]: sub
    Out[79]: Subscriber(addr='jonesy@example.com', joined='2012-10-19')
    
    In [80]: sub.addr
    Out[80]: 'jonesy@example.com'
    
    In [81]: sub.joined
    Out[81]: '2012-10-19'

    这样就可以通过名称来取值了,代码可读性也更高。

    需要注意的是,这种命名元祖的方式不能直接修改其中的值,直接修改会报错

    In [82]: a = namedtuple('SSS', ['name', 'shares', 'price'])
    
    In [83]: _a = a('yongxinz', 1, 2)
    
    In [84]: _a.shares = 4
    -----------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-84-f62a5288a29a> in <module>()
    > 1 _a.shares = 4
    
    AttributeError: can't set attribute

    想要修改的话可以使用 _replace() 函数。

    In [85]: _a._replace(shares=4)
    Out[85]: SSS(name='yongxinz', shares=4, price=2)

    但是还有一个疑问,如果这个列表元素比较多的话,那就需要定义很多的名称,也比较麻烦,还有更好的方式吗?

    未完待续。。。

    欢迎留言,或添加我个人微信 zhangyx6a 交流,不是微商。

关键字