Python高效技巧(一)

前言:

  本文假定读者已经有了一定的Python 基础,看完过几本入门书籍。本文也不是那种快速参考手册 (可以很快的查询某个模块下的某个函数)。旨在聚焦几个最重要的主题,演示几种可能的高效解决方案,作为一个自己提升的记录。

一、内置模块

1、deque模块

 返回一个从左到右(使用append())初始化的新 deque 对象,其中的数据来自iterable。如果未指定iterable,则新的双端队列为空。
 双端队列是栈和队列的泛化(名称发音为“deck”,是“双端队列”的缩写)。双端队列支持线程安全、内存高效的追加和从双端队列的任一侧弹出,在任一方向上的 O(1) 性能大致相同。
 虽然list对象支持类似的操作,它们被用于快速固定长度的操作进行了优化和招致O(n)的存储器移动成本 pop(0)和操作,这些操作的改变的大小和底层数据表示的位置。insert(0, v)
 如果未指定maxlen或者是None,则双端队列可能会增长到任意长度。否则,双端队列受限于指定的最大长度。一旦有界长度的双端队列已满,当添加新项目时,从另一端丢弃相应数量的项目。有界长度的双端队列提供类似于tailUnix 中的过滤器的功能。它们还可用于跟踪仅对最近的活动感兴趣的交易和其他数据池。
  应用:保留最后 N 个元素
  因为它有maxlen参数,所以指定最大后,前面的就会剔除,只保留后N个

from collections import deque
items = deque([0, 1, 2, 3, 4, 5, 6])
items
>>>deque([0, 1, 2, 3, 4, 5, 6])
items.append(7)# 该方法等同于下面这种
items.appendright(7)
>>>deque([0, 1, 2, 3, 4, 5, 6, 7])
items.appendleft(7)
>>>deque([7, 0, 1, 2, 3, 4, 5, 6, 7])
# 保留最新的N条记录
A = deque([1, 2, 3], maxlen=3)
A
>>>deque([1, 2, 3])
A.append(4)
A
>>>deque([2, 3, 4])
"""
它的pop方法也是可以左右使用的。
它比传统的列表灵活、方便很多,但要实现随机访问,不建议用这个,请用列表.
"""

2、heapq模块

取一个集合中的最大/最小的N个,如何实现?
heapq 模块有两个函数:nlargest() 和 nsmallest() 可以完美解决这个问题。

nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums))
>>>[42, 37, 23]
print(heapq.nsmallest(3, nums))
>>>[-4, 1, 2]

两个函数都能接受一个关键字参数,用于更复杂的数据结构中:

portfolio = [ {'name': 'IBM', 'shares': 100, 'price': 91.1},
{'name': 'AAPL', 'shares': 50, 'price': 543.22},
{'name': 'FB', 'shares': 200, 'price': 21.09},
{'name': 'HPQ', 'shares': 35, 'price': 31.75},
{'name': 'YHOO', 'shares': 45, 'price': 16.35},
{'name': 'ACME', 'shares': 75, 'price': 115.65} ]
cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
cheap
>>>[{'name': 'YHOO', 'shares': 45, 'price': 16.35},
 {'name': 'FB', 'shares': 200, 'price': 21.09},
 {'name': 'HPQ', 'shares': 35, 'price': 31.75}]
 
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
expensive
>>>[{'name': 'AAPL', 'shares': 50, 'price': 543.22},
 {'name': 'ACME', 'shares': 75, 'price': 115.65},
 {'name': 'IBM', 'shares': 100, 'price': 91.1}]

  注意:当要查找的元素个数相对比较小的时候,函数 nlargest() 和 nsmallest() 是很
合适的。如果你仅仅想查找唯一的最小或最大 (N=1) 的元素的话,那么使用 min() 和
max() 函数会更快些。类似的,如果 N 的大小和集合大小接近的时候,通常先排序这
个集合然后再使用切片操作会更快点 ( sorted(items)[:N] 或者是 sorted(items)[-
N:] )

  

3、关于zip函数的使用

  怎样在数据字典中执行一些计算操作 (比如求最小值、最大值、排序等等)?
  为了对字典值执行计算操作,通常需要使用 zip() 函数先将键和值反转过来。比
如,下面是查找最小和最大股票价格和股票值的代码:

prices = { 
'ACME': 45.23, 'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.20, 'FB': 10.75
}
min_price = min(zip(prices.values(), prices.keys()))
min_price 
>>>(10.75, 'FB')
类似的还有下面方法:
>>> prices = { 'AAA' : 45.23, 'ZZZ': 45.23 }
>>> sorted(zip(prices.values(), prices.keys()))
[(45.23, 'AAA'), (45.23, 'ZZZ')]

4、命名切片

内置的 slice() 函数创建了一个切片对象,可以被用在任何切片允许使用的地方。
比如:

>>> items = [0, 1, 2, 3, 4, 5, 6]
>>> a = slice(2, 4)
>>> items[2:4]
[2, 3]
>>> items[a]
[2, 3]
>>> items[a] = [10,11]
>>> items
[0, 1, 10, 11, 4, 5, 6]
>>> del items[a]
>>> items
[0, 1, 4, 5, 6]

从结果来看,上面两种方法结果一致,但后者避免了大量无法理解的硬编码下标,使得你的代码更加清晰可读。
参数:s.start , s.stop , s.step

5、collections 中的Counter模块

怎样找出一个序列中出现次数最多的元素呢?
collections.Counter 类就是专门为这类问题而设计的,它甚至有一个有用的
most common() 方法直接给了你答案。

words = [ 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes', 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into', 'my', 'eyes', "you're", 'under' ]
word_counts = Counter(words)
# 出现频率最高的 3 个单词
top_three = word_counts.most_common(3)
print(top_three)
>>>[('eyes', 8), ('the', 5), ('look', 4)]

Counter的返回结果是键值对形式。

二、字符串/文本处理

1、分割字符串

需求:你需要将一个字符串分割为多个字段,但是分隔符 (还有周围的空格) 并不是固定的。

  string 对象的 split() 方法只适应于非常简单的字符串分割情形,它并不允许有多个分隔符或者是分隔符周围不确定的空格。当你需要更加灵活的切割字符串的时候,最好使用 re.split() 方法

line = 'asdf fjdk; afed, fjek,asdf, foo'
"""
分隔符可以是逗号,分号或者是空格(\s:任何不可见空格,制表/换行/换页等),并且后面紧跟着任意个的空格。只要这个模式被找到,那么匹配的分隔符两边的实体都会被当成是结果中的元素返回。
"""
re.split(r'[;,\s]\s*', line)
等价于:
re.split(r'(?:;|,|\s)\s*', line)
>>>['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
# 需要i注意的是下面的格式,有分组,那么被匹配的文本也将出现在结果列表中
fields = re.split(r'(;|,|\s)\s*', line)
fields
>>>['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', 'asdf', ',', 'foo']
# 转换思路:确保你的分组是非捕获分组,形如(?:...)
re.split(r'(?:,|;|\s)\s*', line)
>>>['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']

2、替换字符串

需求:假设你想将形式为 11/27/201 的日期字符串改成 2012-11-27

对于简单的字面模式,直接使用 str.repalce() 方法即可,但是像上面的这种需求,简单模式没法处理

>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> import re
>>> re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text)
'Today is 2012-11-27. PyCon starts 2013-3-13.'

sub() 函数中的第一个参数是被匹配的模式,第二个参数是替换模式,第三个是文本/字符串。反斜杠数字比如 \3 指向前面模式的捕获组号。

如果除了替换后的结果外,你还想知道有多少替换发生了,可以使用 re.subn()
来代替。比如:

>>> newtext, n = datepat.subn(r'\3-\1-\2', text)
>>> newtext
'Today is 2012-11-27. PyCon starts 2013-3-13.'
>>> n 
2

对于一般的忽略大小写的匹配操作,简单的传递一个 re.IGNORECASE 标志参数就已经足够了,这个参数适用于所有re方法

3、换行匹配

需求:你正在试着使用正则表达式去匹配一大块的文本,而你需要跨越多行去匹配

comment = re.compile(r'/\*(.*?)\*/')
text1 = '/* this is a comment */'
text2 = '''/* this is a
... multiline comment */
... '''
comment.findall(text1)
>>>[' this is a comment ']
comment.findall(text2)
>>>[]
为了解决上述问题,就必须适应之前提到的方法,
方法一:
comment2 = re.compile(r'/\*((?:.|\n)*?)\*/')
comment2.findall(text2)
>>>[' this is a\nmultiline comment ']
方法二:直接使用标志位
comment = re.compile(r'/\*(.*?)\*/', re.DOTALL)
comment.findall(text2)
>>>[' this is a\nmultiline comment ']

需要注意的是,这两种匹配会把换行符也匹配出来,实际应用时可以使用re.sub()替换掉

4、字符串对齐

需求:你想通过某种对齐方式来格式化字符串

对于基本的字符串对齐操作,可以使用字符串的 ljust() , rjust() 和 center()方法。

s = "hello world"
s.ljust(20,"*")
>>>'hello world*********'
s.rjust(20,"*")
>>>'*********hello world'
s.center(20,"*")
>>>'****hello world*****'
"""
实现上述操作的还有format()函数,而且,format()函数比上述方法通用,因为它不仅可以格式化字符,还可以时其他格式的东西。使用format时,你要使用 <,> 或者ˆ 字符后面紧跟一个指定的宽度
"""
format(s,">20")
>>>'         hello world'
format(s,"^20")
>>>'    hello world     '
# 当然也可以指定填充符号,使用方法如下
format(s,"*<20")
>>>'hello world*********'
format(s,"*^20")
>>>'****hello world*****'
# 它相较于ljust()的强大之处
format(1.23456,"*^20")
>>>'******1.23456*******'
format(1.23456,"*^20.2")
>>>'********1.2*********'
format((1.23456*100),"^.2f")
>>>'123.46'

5、字符串拼接

需求:你想拼接多个字符串
当你只需要简单的将它们放到一起,那就直接用加号 (+),当需要其他处理时,使用 .join()方法。

a = 'Is Chicago'
b = 'Not Chicago?'
a + ' ' + b
>>>'Is Chicago Not Chicago?'
等价于:
" ".join([a,b])
>>>'Is Chicago Not Chicago?'

  如果两个字符串很小,那么第一个版本性能会更好些,因为 .join()调用I/O, I/O 系统调用天生就慢。另外一方面,如果两个字符串很大,那么第二个版本可能会更加高效,因为它避免了创建一个很大的临时结果并且要复制大量的内存块数据

这节就写这么多,下节介绍数字日期和时间、以及迭代的相关操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值