python cookbook pdf java1234_Python Cookbook 学习笔记(一):数据结构和算法

写在前面人生苦短,我用Python。

Python是一门简洁又强大的编程语言。比起C++/Java等语言,Python拥有简单易懂的语法,极易上手,专注于解决问题,而非语言本身。同时,Python拥有丰富的库,为各种工作提供支持,如网页开发(Django)、科学计算(Numpy)、软件开发(SCons)等[1]。虽然Python运行效率比不上C++/Java,但开发者会使用Python进行快速开发原型、试验想法,条件成熟后再迁移到C++/Java。总而言之,人生苦短,我用Python。

想要高效地使用Python解决问题,不仅需要学习基础语法,如条件、循环语句,还要知晓Python式的方式进行编程,如运用Python提供的语法糖(Syntactic sugar),充分利用Python的简洁、可读性强的语法。为此,David Beazley和Brian K. Jones著作了Python Cookbook,面向实际问题展示Python式的解决方案,并提供详实的讨论,是Python的“菜谱”大合集。

本系列学习笔记,旨在总结Python Cookbook每一章的重点难点,对每章讨论的问题进行归类,记录容易混淆和忽视的小技巧,方便学习和查询。

最后,本系列学习笔记不是Python语法的入门笔记,而是关于Python的进阶,适合在了解基础语法后想掌握更高效编程方式、或者熟悉Python2但对Python3不了解的同学。想学习Python基础语法的同学,可以看Learn Python the Hard Way,史诗般的入门书籍(笑)。以及,尽管本系列学习笔记将尽可能覆盖重点,但它不能代替原书,感兴趣的同学还是推荐阅读Python Cookbook,有免费的网页版/电子版。

本文总结Python Cookbook的第一章,数据结构和算法。希望这里的笔记能在大家学习Python的过程中提供一定帮助。

目录如何从序列解包(unpack)变量

可读性更强的切片(slice)

数据类型(一):高性能容器collections

数据类型(二):heapq

在min, max和sorted中使用key

正确使用生成器(generator)

一些高效的小技巧

1. 如何解包(unpack)变量

有时候,我们需要把一个含有若干元素的序列赋值给一些变量,即解包。序列可以是tuple,list,string,或任何iterable。根据序列的长度和变量的个数,解包可以分为两类:序列长度 = 变量个数

序列长度 > 变量个数

Python提供了非常简洁的解决方案:仅需要一次简单的赋值,就可以完成解包。

对于第一种情况的解包,我们只需要在将序列赋值给和序列长度相等个数的变量,序列中的元素按顺序赋值给变量:

>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]

>>> name, shares, price, date = data

对于第二种情况的解包,可以使用星号表达式*:

>>> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')

>>> name, email, *phone_numbers = user_record

>>> phone_numbers

['773-555-1212', '847-555-1212']

有时候我们并不需要所有解包得到的变量,如我们不需要上个例子中的name,那么可以用下划线代替name,这是一种Python式的表示“不使用该变量”:

>>> _, email, *phone_numbers = user_record

2. 可读性更强的切片(slice)

有时我们需要从一个有固定格式的字符串的不同部分提取相应数据,并进行操作,如:

>>> res = '100-0.9'

>>> cost = float(res[0:2])*float(res[4:7])

>>> cost

9.0

上述方法虽然直接,但可读性差,不利于代码的维护,而Python内置的slice()函数则能解决这一不足。slice()能创建slice对象,用于任何需要切片的场合:

>>> NUMBER = slice(0, 2)

>>> PRICE = slice(4, 7)

>>> cost = float(res[NUMBER])*float(res[PRICE])

>>> cost

9.0

这样可读性是不是更强了呢。

此外,我们还可以改变slice()默认的step,并访问slice的start、stop和step:

>>> a = slice(10, 50, 2)

>>> a.start

10

>>> a.stop

50

>>> a.step

2

3. 数据类型(一):高性能容器collections

collections模块实现了几个专用数据类型,适用于一些特殊场合,可以作为Python内置的通用数据容器(dict、list、set和tuple)的替代。本章对collections的介绍涵盖了最常用的几种专用数据类型:deque、defaultdict、OrderedDict、Counter和namedtuple等。

deque

deque指double-ended queue,是栈(stack)和队列(queue)的推广,在deque两端进行append和pop只需要大约O(1)。固定长度的deque适用于保留固定长度的历史:

>>> from collections import deque

>>> dq = deque(maxlen=2)

>>> dq.append(0)

>>> dq

deque([0], maxlen=2)

>>> dq.append(1)

>>> dq

deque([0, 1], maxlen=2)

>>> dq.append(2)

>>> dq

deque([1, 2], maxlen=2)

可以看到,当新元素插入deque时,如果此时元素个数超过创建deque时设定的长度maxlen,旧的元素自动pop。

defaultdict

defaultdict不仅提供了和dict相同的功能,还能自动初始化初值,使程序员专注于元素的添加:

>>> s = 'mississippi'

>>> d = defaultdict(int)

>>> for k in s:

... d[k] += 1

...

>>> sorted(d.items())

[('i', 4), ('m', 1), ('p', 2), ('s', 4)]

关于如何指定初值的初始化,可以参见default_factory。

OrderedDict

当我们遍历Python内置的dict时,返回的元素并不按照添加的顺序。当我们需要控制元素的顺序时,可以使用OrderedDict:

from collections import OrderedDict

d = OrderedDict()

d['foo'] = 1

d['bar'] = 2

d['spam'] = 3

# Outputs "foo 1", "bar 2", "spam 3"

for key in d:

print(key, d[key])

需要注意的是,保持顺序有空间上的代价,OrderedDict通常占用dict两倍的空间。

Counter

当我们使用dict的目的是记录不同元素出现的次数时,可以使用专用数据类型Counter:

>>> from collections import Counter

>>> words = ['a', 'this', 'a', 'is', 'list', 'list', 'list']

>>> word_counts = Counter(words)

>>> word_counts

Counter({'list': 3, 'a': 2, 'this': 1, 'is': 1})

Counter还提供了most_common(n)方法,返回频率最高的n个元素:

>>> word_counts.most_common(2)

[('list', 3), ('a', 2)]

看到这里,Counter不就是一个将元素映射到次数的dict吗?Counter当然有它不一样的地方,最大的特点是Counter支持一些dict不支持的数学运算,特别适合频次的计算:

>>> morewords = ['another', 'list']

>>> moreword_counts = Counter(morewords)

>>> word_counts + moreword_counts

Counter({'list': 4, 'a': 2, 'this': 1, 'is': 1, 'another': 1})

>>> word_counts - moreword_counts

Counter({'a': 2, 'list': 2, 'this': 1, 'is': 1})

namedtuple

namedtuple是tuple的子类,通过namedtuple我们可以名字访问元组的元素,类似于C++的struct或简单的class。首先需要通过namedtuple()创建可以实例化的class,然后通过名字对field访问和赋值:

>>> from collections import namedtuple

>>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])

>>> sub = Subscriber('jonesy@example.com', '2012-10-19')

>>> sub

Subscriber(addr='jonesy@example.com', joined='2012-10-19')

>>> sub.addr

'jonesy@example.com'

>>> sub.joined

'2012-10-19'

需要注意的是,namedtuple是immutable的,一旦对某个field赋值,不能进行in-place的改变。

4. 数据类型(二):heapq

heapq模块实现了priority queue,它的nlargest()和nsmallest()方法适用于从一个数列中找到最大/最小的n个元素,且n相对数列长度较小时。

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]

我们还可以使用heapq.heapify()显式地将序列转化为heap:

>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]

>>> import heapq

>>> heap = list(nums)

>>> heapq.heapify(heap)

>>> heap

[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]

5. 在min, max和sorted中使用key

当我们有一系列字典,并且希望以字典的某个公共key为标准找到最小、最大的字典,或者对这些字典进行排序时,可以在min、max和sorted中使用key:

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'))

rows_by_fname按fname排序,rows_by_uid则按uid排序。

这里itemgetter('fname')相当于以下lambda表达式,根据key提取value:

lambda r: r['fname']

itemgetter()函数也接受多个key,如:

rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))

排序时优先按照lname排序,lname相同时再按fname排序。

同样的,我们在min和max中也可以用key:

>>> min(rows, key=itemgetter('uid'))

{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}

>>> max(rows, key=itemgetter('uid'))

{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}

6. 正确使用生成器(generator)

在Python中,生成器是高效Python的重要组成部分,它提供了创建iterator的快捷方式。生成器的重要特点是,按需生成值,避免无谓地浪费内存,这点可以通过以下例子说明[2]:

def firstn(n):

num, nums = 0, []

while num < n:

nums.append(num)

num += 1

return nums

sum_of_first_n = sum(firstn(1000000))

firstn()返回一个list,当n很大时,list将占用较大空间,即使我们只需要这个list的元素和。而使用生成器时,firstn返回的只是一个lazy的iterator,只有当需要访问元素时才生成该元素,因此在求和前,firstn返回值占用远少于返回list时的内存;而求和时,sum依次累加生成的元素。在下面的代码中,我们使用yield构造生成器:

def firstn(n):

num = 0

while num < n:

yield num

num += 1

>>> g = firstn(1000000)

>>> g

sum_of_first_n = sum(g)

我们同样可以通过生成器表达式(generator expression),即一对圆括号(),构建生成器:

a = (i for i in range(1000000))

生成器在list compression中也特别有用:

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]

>>> pos = (n for n in mylist if n > 0)

>>> pos

at 0x1006a0eb0>

>>> for x in pos:

... print(x)

生成器也适用于当我们需要先变换再reduce数据的场合,Python还提供了相关的语法糖。具体的,我们可以不用:

nums = [1, 2, 3, 4, 5]

s = sum((x * x for x in nums))

而用:

nums = [1, 2, 3, 4, 5]

s = sum(x * x for x in nums)

即当生成器作为函数唯一的输入时,可以省去重复的括号,生成器表达式的括号和函数的括号可以合并成一对括号。

此外,当我们希望函数的返回更通用,而非局限于list,也可以使用generator:

def general_purpose_iterable(n):

for i in range(n):

yield i

关于生成器更详细的介绍,可以参考Python Wiki。

7. 一些高效的小技巧

很多时候,我们有不同方式解决同一个问题。尽管达成的效果一样,但是效率千差万别。这里总结这章提到的技巧。

itemgetter()和attrgetter()

在对一系列dictionary按照公共key排序时,我们可以通过以下两种方式实现:

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}

]

# 使用itemgetter()

rows_by_fname = sorted(rows, key=itemgetter('fname'))

# 使用lambda表达式

rows_by_fname = sorted(rows, key=lambda r: r['fname']))

尽管两种方式的效果一样,但itemgetter()通常比lambda表达式更高效。类似的,在对一系列对象排序时,attrgetter()通常也比lambda表达式更快。

dictionary comprehension

处理字典时,dictionary comprehension比dict()更快:

p2 = { key:value for key,value in prices.items() if key in tech_names }

快过

p1 = dict((key, value) for key, value in prices.items() if value > 200)

除了本文总结的内容,这一章还提到一些数据结构和算法,如整合多个字典的collections.ChainMap,过滤iterable的itertools.compress(),还有寻找两个字典的公共key等,具体参见Python Cookbook。

参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值