19. 第十九章拾珍

2024-06-16 08:36
文章标签 19 第十九章 拾珍

本文主要是介绍19. 第十九章拾珍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

19. 拾珍

本书的一大目标一直是尽可能少的介绍Python语言.
如果做某种事情有两种方法, 我会选择一种, 并避免提及另一种.
或者有时候, 我会把另一种方法作为练习进行介绍.本章我会带领大家回顾那些遗漏的地方.
Python提供了不少并不是完全必需的功能(不用它们也能写出好代码),
但有时候, 使用这些功能可以写出更简洁, 更可读或更高效的代码, 甚至有时候三者兼得.
19.1 条件表达式
我们在5.4节中见过条件语句.
条件语句通常用来从两个值中选择一个. 例如:
if x > 0:y = math.log(x)
else:y = float('nan')
# 完整代码
import mathdef main(x):if x > 0:y = math.log(x)else:y = float('nan')return yif __name__ == '__main__':res = main(0)print(res, type(res))  # nan <class 'float'>
这条语句检查x是否为正数.
如果为整数, 则计算math.log; 如果为负数, math.log会抛出ValueError异常.
为了避免程序停止, 我们直接生成一个'NaN', 一个特殊的浮点数, 代表'不是数'(Not A Number).我们可以使用'条件表达式'来更简洁地写出这条语句:
y = math.log(x) if x > 0 else float('nan')
这条语句几乎可以用英语直接读出来'y gets log-x if x greater than 0; otherwise is gets NaN'
(y的值在大于0时时math.log(x), 否则是NaA).
递归函数有时候可以使用条件表达式重写.
例如, 下面是factorial的一个递归版本:
def factorial(n):if n == 0:return 1else:return n * factorial(n - 1)
我们可以将其重写为:
def factorial(n):return 1 if n == 0 else n * factorial(n - 1)
条件表达式的另一个用途是处理可选参数.
例如, 下面是GoodKangaroo的init方法(参见练习17-2):
def __init__(self, name, contents=None):self.name = name# 推荐使用is比较if contents == None:contents = []self.pouch_contents = contents
我们可以将其重写为:
def __init__(self, name, contents=None):self.name = nameself.pouch_contents = [] if contents == None else contents
一般来说, 如果条件语句的两个条件分支都包含简单的返回或对同一变量进行赋值的表达式,
那么这个语句可以转化为条件表达式.
19.2 列表理解
10.7节中我们已经见过映射和过滤模式. 例如, 下面的函数接收一个字符串列表, 
将每个元素通过字符串方法capitalize进行映射, 并返回一个新的字符串列表:
def capitalize_all(t):res = []for s in t:# 转为大写res.append(s.capitalize())return res
我们可以用列表理解(list comprehension)把这个函数写得更紧凑:
def capitalize_all(t):return [s.capitalize() for s in t]
上面的方法括号操作符说明我们要构建一个新的列表.
方括号之内的表达式指定了列表的元素, 而for子句则表示我们要遍历的序列.列表理解的语法有一点粗糙的地方, 因为里面的循环变量, 即本例中的s, 在表达式出现在定义之前.列表理解也可以用于过滤操作.
例如, 下面的函数选择列表t中的大写元素, 并返回一个新列表:
def only_upper(t):res = []for s in t:if s.isupper():res.append(s)return res
我们可以用列表理解将其重写为:
def only_upper(t)return [s for s in t if s.isupper()]
对于检查表达式来说, 列表理解更紧凑, 更易于阅读,
并且它们通常都比实现相同功能的循环更快, 有时候甚至快很多.
因此, 如果你因为我没有早些提到它而恼怒, 我表示十分理解.但是我们辩解一下, 列表理解更难以调试, 因为你没法再循环内添加打印语句.
我建议你只在计算简单到一次就能弄对的时候才使用它.
对初学者来说, 这意味着从来不用.
19.3 生成器表达式
生成器表达式(generator  expression) 和列表理解类似, 但是它使用圆括号, 而不是使用方括号:
>>> g = (x ** 2 for x in range(5))
>>> g
>>> <generator objrct <genexpr> at 0x7f4c5a786c0>
结果是一个生成器对象, 它知道该如何便遍历值的序列.
但它又和列表理解不同, 它不会一次把结果计算出来, 而是等待请求.
内置函数next会从生成器中获取下一个值:
>>> next(g)
0
>>> next(g)
1
当到达序列的结尾后, next会抛出一个StopIteration异常.
可以使用for循环来遍历所有值:
>>> for val in g:
...		print(val)
4
9
16
生成器对象会跟踪记录访问序列的位置, 所以for循环会从上一个next所在的位置继续.
一旦生成器遍历结束, 再访问它就会抛出StopException:
>>> next(g)
StopException
生成器表达式经常和sum, max, 和min之类的函数配合使用:
>>> sum(x ** 2 for x in range(5))
30
19.4 any和all
Python提供了一个内置函数any, 它接收一个由布尔值组成的序列, 并在其中任何(一个)值是True时返回True.
它可以用于列表:
>>> any([False, False, True])
>>> True
但它更常用于生成器表达式:
>>> any(letter == 't' for letter in 'monty')
True
上面这个例子用处不大, 因为它做的事情和in表达式一样.
但是我们可以用any来重写9.3节中的搜索函数.
例如, 我们可以将aviods函数重新为:
def avoids(word, forbidden):return not any(letter in forbidden for letter in word)
这个函数读起来几乎和英语一致:
'word avoids forbidden if there are not any forbidden letters in word'
(我们说一个word避免被禁止, 是指word中没有任何被禁的字母).
Python还提供了另一个内置函数all, 它在序列中所有元素都是True时返回True.
作为练习, 请使用all重写9.3节中的users_all函数.
# 字符串, 组成字符串的字母.
def user_all(word, required):# 从字母组中遍历字母for letter in required:# 判断字母是否在word中, 有一个字母不在, 则返回False.if letter not in word:return False# 全部存在则返回True.return Trueprint(user_all('hello', 'h'))  # True
# 重写
def user_all(word, required):return all(letter in word for letter in required)print(user_all('hello', 'h'))  # True
19.5 集合
我曾在13.6节中使用字典来寻找在文档中出现但不属于一个单词列表的单词.
我写的函数接收一个字典参数d1, 其中包含文档总所有的单词作为键; 以及另一个参数d2, 包含单词列表.
它返回一个字典, 包含d1中所有不在d2之中的键:
def substracr(d1, d2):res = dict()for key in d1:if key not in d2:res[key] = Nonereturn res
在这些字典中, 值都是None, 因为我们从来不用它们.
因此, 我们实际上浪费了一些存储空间.
Python还提供了另一个内置类型, 称为集合(set), 它表现得和没有值而使用键集合的字典类似.
向一个集合添加元素很快, 检查集合成员也很快. 集合还提供方法和操作符来进行常见的集合操作.例如, 几何减法可以使用方法difference或者操作符'-'来实现.
因此我们可以将substract函数重写为:
def substract(d1, d2):return set(d1) - set(d2)
结果是一个集合而不是字典, 但是对于遍历之类的操作, 表现是一样的.
本书中的一些练习可以使用集合来更加简洁且高效地实现.
例如, 练习10-7中的has_duplicates函数, 下面是使用字典来实现的一个解答:
def has_duplicates(t):d = {}for x in t:if x in d:return Trued[x] = Truereturn False
一个元素第一次出现的时候, 把它加入到字典中. 如果相同的元素再次出现时, 函数就返回True.
使用集合, 我们可以这样写同一个函数:
def has_duplicates(t):return len(set(t) < len(t))
一个元素在一个结合中只能出现异常, 所以如果t中间的某个元素出现超过一次, 那么变成集合后其长度比t小.
如果没有任何重复元素, 那么集合的长度应当和t相同.我们也可以使用集合来解决第9章中的一些练习.
例如, 下面是users_only函数使用循环来实现的版本:
def users_only(word, available):for letter in word:if letter not in available:return Falsereturn True
users_only检查word中所有的字符是不是在available中出现.
我们可以这样重写:
def users_only(word, available):return set(word) <= set(available)
操作符 <= 检查一个集合是否是零一个集合的子集, 包括两个集合相等的情况.
这正好符合word中所有字符都出现在Available中.
19.6 计数器
计数器(counter)和集合类似, 不同之处在于, 如果一个元素出现超过一次, 计数器会记录它出现了多少次.
如果你熟悉多重集(multiset)这个数学概念, 就会发现计数器是多重集的一个自然的表达方式.计数器定义在标准模块collections中, 所有需要导入它在使用.
可以用字符串, 列表或者其他任何之处迭代访问的类型对象来初始化计数器:
>>> from collections import Counter
>>> count = Counter('parrot')
>>> count
Counter({'r': 2, 't': 1, 'o': 1, 'p': 1, 'a': 1})
计数器有很多地方和字典相似. 它们将每个键映射到其出现次数. 和字典一样, 键必须是可散列的.
但和字典不同的是, 在访问计数器中不存在的元素时, 它并不会抛出异常, 相反, 它会返回0:
>>> count['d']
0
我们可以使用计数器来重写练习10-6中的is_anagram函数:
def is_anagram(word1, word2):return Counter(word1) == Counter(word2)
如果两个单词互为回文, 则它们会包含相同的字母, 且各个字母计数相同, 所有他们对应的计数器对象也会相等.
计数器提供方法和操作符来进行类似集合的操作, 包括集合加法, 减法, 并集和交集.
计数器还提供一个非常常用的方法most_common, 它返回一个'值-频率对'的列表, 
按照最常见到最少见来排序:
>>> count = Counter('parrot')
>>> for val, freq in count.most_common(3):
... 	print(val, freq)r 2
p 1
a 1
19.7 defaultdict
collections模块提供了defaultdict, 它和字典相似, 不同的是, 如果你访问一个不存在的键,
它会自动创建一个新值.创建一个defaultdict对象时, 需要提供一个用于创建新值的函数. 
用来创建对象的函数有时被称为'工厂(factory)'函数.
用于创建列表, 集合以及其他类型对象的内置函数, 都可以用作工厂函数:
>>> from collections import defaultdict
# 这里的list是工厂函数
>>> d = defaultdict(list)
请注意, 参数是list(一个类对象), 而不是list()(一个新的列表).
你提供的函数直到访问不存在的键时, 才会被调用的:
# t是一个别名
>>> t = d['new key']
# 访问不存在的键, 返回一个空列表.
>>> t
[]
新列表t也会加到字典中. 所有, 如果我们修改t, 改动也会在d中体现:
# 往空列表中追加值.
>>> t.append('new value')
>>> d
defaultdict(<class 'lsit'>, {'new key': ['new value']})
如果创建一个由列表组成的字典, 使用defaultdict往往能够帮你写出更简洁的代码.
在练习12-2的解答中, 我创建了一个字典, 将排序的字母字符串映射到可以由那些字母拼写的单词列表.
例如, 'opst'映射到列表['opst', 'pots', 'spot', 'stop', 'tops'].
可以从↓下载该解答.
https://github.com/AllenDowney/ThinkPython2/blob/master/code/anagram_sets.py
下面是原始的代码:
def all_anagrams(filename):# 创建字典d = {}# 遍历文件对象的每一行for line in open(filename):# 每一行按去除首尾空白字符(包含\n换行符), 最后转为小写字母.word = line.strip().lorwe()# 对单词按字母排序(升序), 返回一个排序后的单词.t = signature(word)# 单词不在字典中则新建项(排序后的单词, [排序前的单词1, 排序前的单词2, ...])if t not in d:d[t] = [word]else: d[t].append(word)# 返回字典return ddef signature(s):t = list(s)t.sort()t = ''.join(t)return t
这个函数(all_anagrams)可以用setdefault简化, 你可能在练习11-2中也用过:
def all_anagrams1(filename):d = {}for line in open(filename):word = line.strip().lower()t = signature(word)d.setdefault(t, []).append(word)return d
但这个解决方案有一个缺点, 它不管是否需要, 每次都会新建一个列表.
(因为列表被作为参数传到进去了, 传递给setdefault方法, 所有每次调用都要创建.)
我们可以使用defaultdict来避免这个问题, 并进一步简化代码:
def all_anagrams(filename):d = defaultdict(list)for line in open(filename):t = signature(word)d[t].append(word)return d
在练习18-3的解答中, 函数has_straightflush中使用了setdefault.
可以从↓下载它.
https://github.com/AllenDowney/ThinkPython2/blob/master/code/PokerHandSoln.py
但这个解决方法的缺点是, 不管是否必需, 每次循环迭代都会创建一个新的Hand对象.
作为练习, 请使用dedaultdict重写该函数.
from collections import defaultdict# 在PokerHand类中def has_straightflush(self):# 工厂函数d = defaultdict(PokerHand)# 遍历卡牌for c in self.cards:# 往字典中保存项, (键为花色, 值为一个列表) PokerHand()得到一个扑克手对象,d[c.suit].add_card(c)  # add_card将卡牌添加到列表中.# 遍历字典的值, 值时一个列表for hand in d.values():# 花色列表的值小于5则跳过if len(hand.cards) < 5:continue# 5张卡片的花色相同, 使用直方图统计, 花色和大小.hand.make_histograms()# 判断是否有顺子, 如果有则返回Trueif hand.has_straight():return Truereturn False
19.8 命名元组
很多简单的对象其实都可以看作是几个相关值的集合.
例如, 15章中定义的Point对象, 包含两个数字, 即x和y.
定义一个这样的类时, 通常会从init方法和str方法开始.
class Point:def __init__(self, x=0, y=0):self.x = xself.y = ydef __str__(self):return '(%g, %g)' % (self.x, self.y)
这里用来更多代码来传达很少的信息.
Python提供了一个更简单的方式来表达用一个意思:
from collections import namedetuple
Point = namedtuple('Point', ['x', 'y'])
第一个参数是你想要创建的类名.
第二个参数是Point对象应当包含的属性的列表, 以字符串表示.
namedtuple的返回值是一个类对象:
>>> Point
<class '__main__.Point'>
这里Point类会自动提供__init__和__str__这样的方法, 所以你不需要写它们.
要创建一个Point对象, 可以把Point类当作函数来用:
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
init方法使用你提供的名字把实参赋值给属性.
str方法会打印出Point对象及其属性的字符串表示.可以使用名称来访问命名元组的元素:
>>> p.x, p.y
(1, 2)
也可以直接把它当作元组来处理:
>>> p[0], p[1]
(1, 2)
>>> x, y = p
>>> x, y
(1, 2)
命名元组还提供了快速定义简单类的方法, 但其缺点是简单的类并不会总保持简单.
可能之后你需要给命名元组条件方法. 如果定义一个新类, 继承当前的命名元组:
class Pointier(Point):# 在这里添加更多的方法
或者也可以直接切换成传统的类定义.
19.9 收集关键词参数
12.4节中, 我们见过如何编写函数将其参数收集成一个元组:
def printall(*args):print(args)
可以使用任意个数的按位实参(也就是说, 不带名称的实参)来调用这个函数:
>>> printall(1, 2.0, '3')
(1, 2.0, '3')
但是*号操作符并不会收集关键字实参:
>>> printall(1, 2.0, third='3')
...
TypeError: printall() got an unecpected keyword argument 'third'
要收集关键词实参, 可以使用**操作符:
def printall(*args, **kwargs): print(args, kwargs)
这里收集关键字形参可以任意命名, 但kwargs是一个常见的选择.
收集的结果是一个将关键词映射到值的字典:
>>> printall(1, 2.0, third='3')
(1, 2.0) {'third': ''}
如果有一个关键词到值的字典, 就可以使用分散操作符**来调用函数:
>>> d = dict(x=1, y=2)
>>> Point(**d)
Point(x=1, y=2)
没有用分散操作符的话, 函数会把d当作一个单独的按位实参, 所有它会把d赋值给x, 并因为没有提供y的赋值而报错:
>>> d = dict(x=1, y=2)
>>> Point(d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeEroor: __new__() missing 1 required positional argument: 'y'
当处理参数很多的函数时, 创建和传递字典来指定常用的选项是非常有用的.
19.10 术语表
条件表达式(conditional expression): 一个根据条件返回一个或两个值的表达式.列表理解(list comprehension): 一个以方框包含一个for循环, 生成新列表的表达式.生成器表达式(generator ecpression): 一个以括号包含一个for循环, 返回一个生成器对象的表达式.多重集(multiset): 一个用来表达从一个集合的元素到它们出现次数的映射的数学概念.工厂函数(factory): 一个用来创建对象, 并常常当作参数使用的函数.
19.11 练习
1. 练习1
下面的函数可以递归地计算二项式系数:
def binomial_coeff(n, k):"""计算(n, k)的二项式系数,n: 实验次数k: 成功次数返回: int"""if k == 0:return 1if n == 0:return 0res = binomial_coeff(n-1, k) + binomial_coeff(n-1, k-1)return res
使用内嵌条件表达式来重写该函数.
注意: 这个函数频率不高, 因为它会不停得重复计算相同的值.
可以通过使用备忘(memoizing, 参见11.6)来提高它的效率.
但你可能会发现, 使用条件表达式之后, 添加备忘会变得比较困难.
def binomial_coeff(n, k):""" 计算(n, k)的二项式系数, n: 实验次数 k: 成功次数 返回: int """ return 1 if k == 0 else 0 if n == 0 else binomial_coeff(n-1, k) + binomial_coeff(n-1, k-1)
memo_k = {}def binomial_coeff0(n, k):if k == 0:return 1if n == 0:return 0res = binomial_coeff0(n - 1, k) + binomial_coeff0(n - 1, k - 1)return resdef binomial_coeff1(n, k):key = n, kres = 1 if k == 0 else 0 if n == 0 else \binomial_coeff1(n - 1, k) + binomial_coeff1(n - 1, k - 1)memo_k[key] = resreturn resdef binomial_coeff2(n, k):return 1 if k == 0 else 0 if n == 0 else \binomial_coeff2(n - 1, k) + binomial_coeff2(n - 1, k - 1)def main(n, k):res0 = binomial_coeff0(n, k)print(res0)res1 = binomial_coeff1(n, k)print(res1)res2 = binomial_coeff2(n, k)print(res2)if __name__ == '__main__':main(7, 2)

这篇关于19. 第十九章拾珍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1065965

相关文章

详解Spring Boot接收参数的19种方式

《详解SpringBoot接收参数的19种方式》SpringBoot提供了多种注解来接收不同类型的参数,本文给大家介绍SpringBoot接收参数的19种方式,感兴趣的朋友跟随小编一起看看吧... 目录SpringBoot接受参数相关@PathVariable注解@RequestHeader注解@Reque

react笔记 8-19 事件对象、获取dom元素、双向绑定

1、事件对象event 通过事件的event对象获取它的dom元素 run=(event)=>{event.target.style="background:yellowgreen" //event的父级为他本身event.target.getAttribute("aid") //这样便获取到了它的自定义属性aid}render() {return (<div><h2>{

系统架构师考试学习笔记第三篇——架构设计高级知识(19)嵌入式系统架构设计理论与实践

本章考点:         第19课时主要学习嵌入式系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分)。在历年考试中,案例题对该部分内容都有固定考查,综合知识选择题目中有固定分值的考查。本课时内容侧重于对知识点的记忆、理解和应用,按照以往的出题规律,嵌入式系统架构设计基础知识点基本来源于教材内。本课时知识架构如图19.1所示。 一、嵌入式系统发展历程

C++笔记19•数据结构:红黑树(RBTree)•

红黑树 1.简介:         红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。 当搜索二叉树退化为单支树时,搜索效率极低,为了使搜索效率高,建立平衡搜索二叉树就需要"平衡树"来解决。上一篇博客介绍了AVL树,这

张飞硬件11~19-电容篇笔记

电容作用 作为源,对后级电路提供能量,对源进行充电。简单讲就是放电和充电。在电路设计中,源往往与负载相隔很远,增加电容就可以起到稳定作用。电容两端的电压不能激变,增加电容可以稳定电压。 电容可以类比为水坝,来让水保持一个供给量稳定。 提供能量时容量要偏大 滤波时容量要偏小 电容特性 电容的电场相吸,正负极电子增多,电场的形成就越快越强大。 相等电量(q)电容越大,则电压值

【C++学习笔记 19】C++中的对象生存周期

对象如何生存在栈上 在C++中,我们每次进入一个作用域时,我们就是在push栈帧。就像把书堆叠起来,将最新的书放在最上层,在这个作用域上创建变量就像在书中写内容,当作用域结束的时候,就把书拿走,此时每个基于栈的变量就结束了。 举个例子 #include <iostream>#include <string>class Entity{public:Entity(){std::cout <

设计模式 19 观察者模式

设计模式 19 创建型模式(5):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式结构型模式(7):适配器模式、桥接模式、组合模式、装饰者模式、外观模式、享元模式、代理模式行为型模式(11):责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式 文章目录 设计模式 19观察者模式(Observer Pat

2015年1月19日 对阿里巴巴点赞VS拍砖

肖峰说: 1.建立了一个信用体系 2.当认为交易不安全的时候建立了支付宝(担保体系) 3.当认为创业非常危险的时候他提供了一个创业的榜样 马光远说: 1.中国有没有假货跟有没有阿里巴巴没有关系 2.对中国经济的拉动史无前例 3.买东西更便宜了 李银认为 1.阿里巴巴的信用污点至今无法让人放心 2.假货问题 3.安全问题 阿里巴

kubernetes视频教程笔记 (19)-代理模式的分类

一、VIP 和 Service 代理     在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。 kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。 在 Kubernetes v1.0 版本,代理完全在 userspace。在 Kubernetes v1.1 版本,新增了

区块链 Fisco bcos 智能合约(19)-区块链性能腾飞:基于DAG的并行交易执行引擎PTE

在区块链世界中,交易是组成事务的基本单元。 交易吞吐量很大程度上能限制或拓宽区块链业务的适用场景,愈高的吞吐量,意味着区块链能够支持愈广的适用范围和愈大的用户规模。 当前,反映交易吞吐量的TPS(Transaction per Second,每秒交易数量)是评估性能的热点指标。 为了提高TPS,业界提出了层出不穷的优化方案,殊途同归,各种优化手段的最终聚焦点,均是尽可能提高交易的并行处理能力