python进阶基本库-不可不知道的提升python编程技巧代码汇总

在这里插入图片描述

写在前面

最近一段时间,经常在问答区闲逛,发现好多新手,在老师的指导下都用上了pycharm,实际上对于新手来说pycharm并不友好,内存占用高,启动慢,我觉得老师用,那是因为得心应手,一个pycharm解决各种python需求,但是对于新手来说,各种不知名的报错,光配置pycharm,就让人累觉不爱了。所以我推荐大家用vscode,或者刚开始干脆直接用python自带的idle,特别是新手,装库就在命令行里面装,这是基本操作,也是基础。工具是被用来使用的,别被工具玩了。建议而已,不喜勿喷。

python很好上手,但是想要精通,并不简单,想要更好的使用它,我们就要了解它更多的特性,更多方便的集成好的算法和函数,光看不动你是学不会编程的,每一个优秀的程序员都是代码堆出来的,知识每天都更新的很快,各种各样的算法,协议,容器,你不可能一天学会,但是打好基础,掌握原理,会让你进步的更快。

文中部分内容,实例,借鉴于《python cookbook(第3版》,《Python语言程序设计》,《流畅的Python》,《The Python Library Reference》官方文档,如有错误遗漏之处,欢迎指正交流。在编程这条路上,我们每一个人都是学生。

本文所讲算法没有高深的数学知识,是我们经常碰到的,排序,查询,过滤。

1. 解压序列赋值给多个变量

>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> name, shares, price, date = data
>>> date
(2012, 12, 21)

如果你前面的都不想要,只想要最后的时间

>>> *_, date = data
>>> date
(2012, 12, 21)

扩展的方式还有很多,你也可以把他延伸到字符串上,再结合字符串的split。当然你也可以用内置函数slice,来避免硬编码。

class slice(start, stop [ , step ] )
返回一个slice 对象,代表由 range(start, stop, step) 指定索引集的切片。其中参数 start 和
step 的默认值为 None。切片对象具有只读数据属性 start 、stop 和 step,只是返回对应的参
数值(或默认值)。这几个属性没有其他明确的功能;不过 NumPy 和其他第三方扩展会用到。在使
用扩展索引语法时,也会生成切片对象。例如:a[start:stop:step] 或 a[start:stop, i]。
另一种方案是返回迭代器对象,可参阅itertools.islice()

比如实现一个递归算法

a=[1,2,3,4]
				
def dsum(seq):
	a,*arg=seq
	return a+dsum(arg) if arg else a

print(dsum(a))

由于语言层面的限制,递归并不是 Python 擅长的。不过递归有时候就是很有用,可以大量简化代码,特别是在你不知道要循环多少次才能有结果的情况下。
**

2. 查找最大或最小的N个元素

**
基本库:heapq — 堆队列算法

这个模块提供了堆队列算法的实现,也称为优先队列算法。
堆是一个二叉树,它的每个父节点的值都只会小于或等于所有孩子节点(的值)。它使用了数组来实现:从零开始计数,对于所有的 k ,都有
heap[k] <= heap[2k+1] 和 heap[k] <=
heap[2
k+2]。为了便于比较,不存在的元素被认为是无限大。堆最有趣的特性在于最小的元素总是在根结点:heap[0]。 这个 API 与教材的堆算法实现有所不同,具体区别有两方面:(a)我们使用了从零开始的索引。这使得节点和其孩子节点索引之间的关系不太直观但更加适合,因为Python 使用从零开始的索引。(b)我们的pop方法返回最小的项而不是最大的项(这在教材中称为“最小堆”;而“最大堆”在教材中更为常见,因为它更适用于原地排序)。基于这两方面,把堆看作原生的Python list 也没什么奇怪的:heap[0] 表示最小的元素,同时 heap.sort() 维护了堆的不变性!

import heapq
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]
print(heapq.nsmallest(3, nums)) # Prints [-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'])
expensive = heapq.nlargest(3, portfolio, key=lambda s:s['price'])

上面代码在对每个元素进行对比的时候,会以 price 的值进行比较。

如果你想在一个集合中查找最小或最大的 N 个元素,并且 N 小于集合元素数量,那么这些函数提供了很好的性能。因为在底层实现里面,首先会先将集合数据进行堆排序后放入一个列表中:

>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> import heapq
>>> h = list(nums)
>>> heapq.heapify(h)
>>> h
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]

堆数据结构最重要的特征是 heap[0] 永远是最小的元素。并且剩余的元素可以很容易的通过调用 heapq.heappop() 方法得到,该方法会先将第一个元素弹出来,然后用下一个最小的元素来取代被弹出元素(这种操作时间复杂度仅仅是 O(log N),N 是堆大小)。比如,如果想要查找最小的 3 个元素,你可以这样做:

>>> heapq.heappop(h)
-4
>>> heapq.heappop(h)
1
>>> heapq.heappop(h)
2

基本库的heapq库,还有很多有趣的函数,能大大简化我们的代码,感兴趣的同学可以深入了解下。

保留最后 N 个元素

在迭代操作或者其他操作的时候,怎样只保留最后有限几个元素的历史记录?

保留有限历史记录正是 collections.deque 大显身手的时候,比如下面的代码,保留最后3次操作的函数:


from collections import deque

def search(func,history=3):
    search.history = deque(maxlen=history)
    def do(*args):
        search.history.append([func.__name__,args])
        return func(*args) 
    return do

@search
def foo(n):
    print("我是foo",n)
    
@search
def foo2(n):
    print("我是foo2",n)
    
for i in range(2):
    foo(i)
    foo2(i)

print(search.history)
======================= RESTART: D:\Users\Desktop\test.py ======================
我是foo 0
我是foo2 0
我是foo 1
我是foo2 1
deque([['foo2', (0,)], ['foo', (1,)], ['foo2', (1,)]], maxlen=3)

实现一个优先级队列

import heapq

def priority():
    queue = []
    index = 0
    def push(item,pri):
        nonlocal index
        heapq.heappush(queue, (-pri, index, item))
        index += 1
    def pop():
        return heapq.heappop(queue)[-1]
    
    priority.push = push
    priority.pop = pop

    return priority


p = priority()
p.push("abc",1)
p.push("foo",4)
#优先级最高
p.push("spam",5)
p.push("xxx",1)
print(p.pop())
print(p.pop())
print(p.pop())
print(p.pop())
======================= RESTART: D:\Users\Desktop\test.py ======================
spam
foo
abc
xxx
>>> 

仔细观察可以发现,第一个 pop() 操作返回优先级最高的元素。另外注意到如果两个有着相同优先级的元素,pop 操作按照它们被插入到队列的顺序返回的。

字典中的键映射多个值

一个字典就是一个键对应一个单值的映射。如果你想要一个键映射多个值,那么你就需要将这多个值放到另外的容器中,比如列表或者集合里面。
选择使用列表还是集合取决于你的实际需求。如果你想保持元素的插入顺序就应该使用列表,如果想去掉重复元素就使用集合(并且不关心元素的顺序问题)。
你可以很方便的使用 collections 模块中的 defaultdict 来构造这样的字典。defaultdict 的一个特征是它会自动初始化每个 key 刚开始对应的值,所以你只需要关注添加元素操作了

>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> d['a'].append(10)
>>> d['b'].append(15)
>>> d['a'].append(30)
>>> d
defaultdict(<class 'list'>, {'a': [10, 30], 'b': [15]})
>>> d['a']
[10, 30]
>>> d['b']
[15]

需要注意的是,defaultdict 会自动为将要访问的键(就算目前字典中并不存在这样的键)创建映射实体。如果你并不需要这样的特性,你可以在一个普通的字典上使用 setdefault() 方法来代替。

>>> d = {}
>>> d.setdefault('a',[]).append(11)
>>> d.setdefault('a',[]).append(22)
>>> d.setdefault('b',[]).append(32)
>>> d
{'a': [11, 22], 'b': [32]}
>>> 

字典的运算

怎样在数据字典中执行一些计算操作(比如求最小值、最大值、排序等等)?

prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}

为了对字典值执行计算操作,通常需要使用 zip() 函数先将键和值反转过来。比如,

min_price = min(zip(prices.values(), prices.keys()))
# min_price is (10.75, 'FB')
max_price = max(zip(prices.values(), prices.keys()))
# max_price is (612.78, 'AAPL')

类似的,可以使用 zip() 和 sorted() 函数来排列字典数据:

prices_sorted = sorted(zip(prices.values(), prices.keys()))

执行这些计算的时候,需要注意的是 zip() 函数创建的是一个只能访问一次的迭代器
前面的 zip() 函数方案通过将字典”反转”为 (值,键) 元组序列来解决了上述问题。当比较两个元组的时候,值会先进行比较,然后才是键。这样的话你就能通过一条简单的语句就能很轻松的实现在字典上的求最值和排序操作了。需要注意的是在计算操作中使用到了 (值,键) 对。当多个实体拥有相同的值的时候,键会决定返回结果。比如,在执行 min() 和 max() 操作的时候,如果恰巧最小或最大值有重复的,那么拥有最小或最大键的实体会返回。

怎样在两个字典中寻寻找相同点(比如相同的键、相同的值等等)?

a = {
'x' : 1,
'y' : 2,
'z' : 3
}
b = {
'w' : 10,
'x' : 11,
'y' : 2
}

为了寻找两个字典的相同点,可以简单的在两字典的 keys() 或者 items() 方法返回结果上执行集合操作。比如:


a.keys() & b.keys() # { 'x', 'y' }

a.keys() - b.keys() # { 'z' }

a.items() & b.items() # { ('y', 2) }

这些操作也可以用于修改或者过滤字典元素。比如,假如你想以现有字典构造一个排除几个指定键的新字典。下面利用字典推导来实现这样的需求:

c = {key:a[key] for key in a.keys() - {'z', 'w'}}
# c is {'x': 1, 'y': 2}

一个字典就是一个键集合与值集合的映射关系。字典的 keys() 方法返回一个展现键集合的键视图对象。键视图的一个很少被了解的特性就是它们也支持集合操作,比如集合并、交、差运算。所以,如果你想对集合的键执行一些普通的集合操作,可以直接使用键视图对象而不用先将它们转换成一个 set。字典的 items() 方法返回一个包含 (键,值) 对的元素视图对象。这个对象同样也支持集合操作,并且可以被用来查找两个字典有哪些相同的键值对。尽管字典的 values() 方法也是类似,但是它并不支持这里介绍的集合操作。某种程度上是因为值视图不能保证所有的值互不相同,这样会导致某些集合操作会出现问题。

一般来讲,代码中如果出现大量的硬编码下标值会使得可读性和可维护性大大降低。比如,如果你回过来看看一年前你写的代码,你会摸着脑袋想那时候自己到底想干嘛啊。这里的解决方案是一个很简单的方法让你更加清晰的表达代码到底要做什么。内置的 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]

如果你有一个切片对象 a,你可以分别调用它的 a.start , a.stop , a.step 属性来获取更多的信息。比如:

>>> a = slice(5, 50, 2)
>>> a.start
5
>>> a.stop
50
>>> a.step
2
>>>

另外,你还能通过调用切片的 indices(size) 方法将它映射到一个确定大小的序列上,这个方法返回一个三元组 (start, stop, step) ,所有值都会被合适的缩小以满足边界限制,从而使用的时候避免出现 IndexError 异常。比如:

>>> a = slice(5, 50, 2)
>>> s = 'HelloWorld'
>>> a.indices(len(s))
(5, 10, 2)
>>> for i in range(*a.indices(len(s))):
... 	print(s[i])

序列中出现次数最多的元素
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'
]
from collections import Counter
word_counts = Counter(words)
# 出现频率最高的 3 个单词
top_three = word_counts.most_common(3)
print(top_three)
# Outputs [('eyes', 8), ('the', 5), ('look', 4)]

>>> word_counts['not']
1
>>> word_counts['eyes']
8
>>>

作为输入,Counter 对象可以接受任意的由可哈希(hashable)元素构成的序列对象。在底层实现上,一个 Counter 对象就是一个字典,将元素映射到它出现的次数上。

Counter 实例一个鲜为人知的特性是它们可以很容易的跟数学运算操作相结合。

>>> a = Counter(words)
>>> b = Counter(morewords)
>>> a
Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2,
"you're": 1, "don't": 1, 'under': 1, 'not': 1})
>>> b
Counter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,
'my': 1, 'why': 1})

>>> c = a + b
>>> c
Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2,
'around': 2, "you're": 1, "don't": 1, 'in': 1, 'why': 1,
'looking': 1, 'are': 1, 'under': 1, 'you': 1})

>>> d = a - b
>>> d
Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2,
"you're": 1, "don't": 1, 'under': 1})
>>>

毫无疑问,Counter 对象在几乎所有需要制表或者计数数据的场合是非常有用的工具。在解决这类问题的时候你应该优先选择它,而不是手动的利用字典去实现。

通过某个关键字排序一个字典列表

你有一个字典列表,你想根据某个或某几个字典字段来排序这个列表。
通过使用 operator 模块的 itemgetter 函数,可以非常容易的排序这样的数据结构。假设你从数据库中检索出来网站会员信息列表,并且以下列的数据结构返回:

rows = [
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]
from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))
print(rows_by_fname)
print(rows_by_uid)
======================= RESTART: D:\Users\Desktop\test.py ======================
[{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'}]
[{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'}]

itemgetter() 函数也支持多个 keys,比如下面的代码

rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
print(rows_by_lfname)
======================= RESTART: D:\Users\Desktop\test.py ======================
[{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'}]

在上面例子中,rows 被传递给接受一个关键字参数的 sorted() 内置函数。这个参数是 callable 类型,并且从 rows 中接受一个单一元素,然后返回被用来排序的值。itemgetter() 函数就是负责创建这个 callable 对象的。operator.itemgetter() 函数有一个被 rows 中的记录用来查找值的索引参数。可以是一个字典键名称,一个整形值或者任何能够传入一个对象的 getitem() 方法的值。如果你传入多个索引参数给 itemgetter() ,它生成的 callable 对象会返回一个包含所有元素值的元组,并且 sorted() 函数会根据这个元组中元素顺序去排序。但你想要同时在几个字段上面进行排序(比如通过姓和名来排序,也就是例子中的那样)的时候这种方法是很有用的。

itemgetter() 有时候也可以用 lambda 表达式代替,比如:

>>> min(rows, key=itemgetter('uid'))
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
>>> max(rows, key=itemgetter('uid'))
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
>>>

这种方案也不错。但是,使用 itemgetter() 方式会运行的稍微快点。因此,如果你对性能要求比较高的话就使用 itemgetter() 方式。
同样适用于 min() 和 max() 等函数

>>> min(rows, key=itemgetter('uid'))
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
>>> max(rows, key=itemgetter('uid'))
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}

通过某个字段将记录分组

你有一个字典或者实例的序列,然后你想根据某个特定的字段比如 date 来分组迭代访问。

itertools.groupby() 函数对于这样的数据分组操作非常实用。为了演示,假设你已经有了下列的字典列表:

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 分组后的数据块上进行迭代。为了这样做,你首先需要按照指定的字段 (这里就是 date ) 排序,然后调用 itertools.groupby() 函数:

from operator import itemgetter
from itertools import groupby
# 排序
rows.sort(key=itemgetter('date'))
# 迭代group
for date, items in groupby(rows, key=itemgetter('date')):
	print(date)
	for i in items:
	print(' ', i)
======================= RESTART: D:\Users\Desktop\test.py ======================
07/01/2012
{'date': '07/01/2012', 'address': '5412 N CLARK'}
{'date': '07/01/2012', 'address': '4801 N BROADWAY'}
07/02/2012
{'date': '07/02/2012', 'address': '5800 E 58TH'}
{'date': '07/02/2012', 'address': '5645 N RAVENSWOOD'}
{'date': '07/02/2012', 'address': '1060 W ADDISON'}
07/03/2012
{'date': '07/03/2012', 'address': '2122 N CLARK'}
07/04/2012
{'date': '07/04/2012', 'address': '5148 N CLARK'}
{'date': '07/04/2012', 'address': '1039 W GRANVILLE'}

groupby() 函数扫描整个序列并且查找连续相同值(或者根据指定 key 函数返回值相同)的元素序列。在每次迭代的时候,它会返回一个值和一个迭代器对象,这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。一个非常重要的准备步骤是要根据指定的字段将数据排序。因为 groupby() 仅仅检查连续的元素,如果事先并没有排序完成的话,分组函数将得不到想要的结果。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~白+黑

真乃人中龙凤,必成大器,

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值