18. 第十八章 继承

18. 继承

和面向对象编程最常相关的语言特性就是继承(inheritance).
继承值得是根据一个现有的类型, 定义一个修改版本的新类的能力.
本章中我会使用几个类来表达扑克牌, 牌组以及扑克牌性, 用于展示继承特性.如果你不玩扑克, 可以在http://wikipedia.org/wiki/Poker里阅读相关介绍, 但其实并不必要;
我会在书中介绍练习中所需知道的东西.本章的代码示例可以从https://github.com/AllenDowney/ThinkPython2/blob/master/code/Card.py下载.
18.1 卡片对象
一副牌里有52张牌, 共有4个花色, 每种花色13, 大小各不相同.
花色有黑桃(Spade), 红桃(Heart), 方片(Diamond)和草花(Club)(在桥牌中, 这几个花色是降序排列的).
每种花色的13张牌分别为: Ace, 2, 3, 4, 5, 6, 7, 7, 9, 10, Jack, Queen, King.
根据你玩的不同游戏, Ace可能比King大, 可能比2.如果我们定义一个新对象来表示卡牌, 
则其属性显然应该是rank(大小)和suit(花色). 但属性的值就不那么直观了.
一种可能是使用字符串, 例如: 'Spade'表示花色, 'Queen'表示大小.
这种实现的问题之一是比较大小和花色的高低时会比较困难.另一种方案是使用整数类给大小和花色'编码'. 在这个语境中, '编码'意味着我们要定义一个数字到花色,
或者数字到大小的隐射. 这种编码并不意味着它是密码(那样就因该称为'加密').例如, 下表展示了花色和对应的整数编码:
黑桃 Spades    --> 3
红桃 Hearts    --> 2
方片 Diamonds  --> 1
草花 Clubs     --> 0
这个编码令我们可以很容易地比较卡牌; 因为更大的花色隐射到更大的数字上, 我们可以直接使用编码来比较花色.
卡牌大小的映射相当明显; 每个数字形式的大小映射到相应的整数上, 而对于花牌:
Jack  --> 11
Queen --> 12
King  --> 13
我使用'->'符号, 是为了说明这些映射并不是Python程序的一部分,
它们是程序设计的一部分, 但并不在代码中直接表现.
Card类的定义如下:
class Card:"""Represents a standard playing card.(代表一张标准的扑克牌.)"""def __init__(self, suit=0, rank=2):# 花色默认为草花self.suit = suit# 大小默认为2 self.rank = rank
和前面一样, init方法对每个属性定义一个可选形参. 默认的的卡牌是花草2.
要创建一个Card对象, 使用你想要的花色和大小调用Card:
queen_of_diaminds = Card(1, 13)
18.2 类属性
为了能将Card对象打印成人门容易约定的格式, 我们需要将整数编码映射成对应的大小和花色.
自然地做法是使用字符串列表. 我们将这些列表赋值到'类属性':
# 在Card类里:# 花色名称列表suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']# 大小名称列表rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King']def __str__(self):return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])
suit_names和rank_names这样的变量, 定义在类之中, 
但在任何方法之外的我们成为类属性, 因为它们是和类对象Card相关联的.这个术语和suit与rank之类的变量相区别. 
那些称为'示例属性', 因为他们是和一个特定的实例相关联的.两种属性都是使用句点表示法访问.
例如, 在__str__中, self是一个Card对象, 而self.rank是它的大小.
相似地, Card是一个类对象, 而Card.rank_names是关联到这个类的一个字符串列表.每个卡牌都有它自己的suit和rank, 但总共只有一个suit_names和rank_names.综合起来, 表达式Card.rank_names[self.rank]意思是
'使用对象self的属性rank作为索引, 从类Card的列表rank_names中选择对应的字符串'.rank_names的第一个元素是None, 因为没有小大为0的卡牌. (让索引对齐数字, 更加直观.)
因为使用None占据了一个位置, 我们就可以得到从下标2到字符串'2'这样整齐的映射. 
如果要避免这种操作, 可以使用字典而不是列表.利用现有的方法, 可以创建并打印开牌:
>>> card1 = Card(2, 11)
>>> print(card1)
Jack of Hearts
# 完整代码
class Card:"""Represents a standard playing card.(代表一张标准的扑克牌.)"""# 花色名称列表suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']# 大小名称列表rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']def __init__(self, suit=0, rank=2):# 花色默认为草花self.suit = suit# 大小默认为2self.rank = rankdef __str__(self):return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])card1 = Card(2, 11)
print(card1)  # Jack of Hearts
18-1展示了Card类对象和一个Card实例.
Card是一个类对象, 所以它的类型是type. card1的类型是Card. 
为了节省空间, 我没有画出suit_names, rank_names的内容.

2023-04-22_00001

18.3 对比卡牌
对应内置类型, 我们比较操作符(<, >, ==)来比较对象并决定哪一个更大, 更小, 或者相等.
对应用户定义类型, 我们可以通过提供一个方法__lt__, 代码'less than'. 来重载内置操作符的行为.__lt__接收两个形参, self和other, 当第一个对象严格小于第二个对象时返回True.卡牌的正确顺序并不显而易见. 例如, 草花3和方块2哪个更大?
一个排面数大, 另一个花色大. 为了比较卡牌, 需要决定大小和花色哪个更重要.这个问题的答案取决去你在玩哪种牌类游戏, 
但为了简单起见, 我们随意做一个决定, 认为花色更重要, 于是, 所有的黑桃比所有的方片都大, 依次类推.这一点决定后, 我们就可以编写__lt__函数:
# 在Card类里:def __lt__(self, other):# 检查花色if self.suit < other.suit:return Trueif self.suit > other.suit:return False# 花色相同, 检查大小return self.rank < other.rank
使用元组比较, 可以写得更紧凑:
# 在Card类里:def __lt__(self, other):t1 = self.suit, self.rankt2 = other.suit, other.rank# 元组作比较, 先对第一个元素做比较, 如果相同, 再对第二个元素作比较.return t1 < t2
作为练习, 为时间对象编写一个__lt__方法.
你可以使用元组比较(, , ), 也可以考虑使用整数比较(时间对象转为十进制秒数).
class Time:# 初始化对象.def __init__(self, name, hour=0, minute=0, second=0):self.name = nameself.hour = hourself.minute = minuteself.second = seconddef __lt__(self, other):print(self.name, other.name)t1 = self.hour, self.minute, self.secondt2 = other.hour, other.minute, other.secondreturn t1 > t2def main():t1 = Time('t1', 9, 45, 0)t2 = Time('t2')# 小于号 self是t1print(t1 < t2)# 大于号 self是t2print(t1 > t2)if __name__ == '__main__':main()
现在我们已经有了卡牌(card), 下一步就是定义牌组(deck).
由于牌组是由卡牌组成的, 很自然地, 每个Deck对象因该有一个属性包含卡牌的列表.
class Deck:def __init__(self):self.cards = []# 花色for suit in range(4):# 大小for rand in range(1, 14):# 一共抽 4 * 13 = 52张卡牌.card = Card(suit, rand)self.cards.append(card)
填充牌组最简单的办法是使用嵌套循环.
外层循环从03遍历各个花色. 内层循环从113遍历开牌大小.
每次迭代使用当前的花色和大小创建一个新的Card对象, 并将它添加到self.cards中.
18.5 打印牌组
下面是一个Deck的一个__str__方法:
# 在Deck类里:def __str__(self):res = []for card in self.cards:res.append(str(card))return '\n'.join(res)
这个方案展示了一种累积构建大字符串的方法: 先构建一个字符串的列表, 再使用字符串方法join.
内置函数str会对每个卡牌对每个卡牌对象调用__str__方法并返回字符串表达形式.
( str(卡牌对象), 会调用卡牌对象的__str__方法.)由于我们对一个换行符调用join函数, 卡片之间用换行分隔.
下面是打印的结果:
>>> deck = Deck()
>>> print(deck)
Ace of Clubs
2 of Clubs
3 of Clubs
...
10 of Spades
Jack of Spades
Queen of Spades
King of Spades
虽然结果显示了52, 它任然是一个包含换行符的字符串.
# 完整代码
# 完整代码
class Card:"""Represents a standard playing card.(代表一张标准的扑克牌.)"""# 花色名称列表suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']# 大小名称列表rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']def __init__(self, suit=0, rank=2):# 花色默认为草花self.suit = suit# 大小默认为2self.rank = rankdef __str__(self):return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])def __lt__(self, other):t1 = self.suit, self.rankt2 = other.suit, other.rank# 元组作比较, 先对第一个元素做比较, 如果相同, 再对第二个元素作比较.return t1 < t2class Deck:# 初始化牌组对象, 按顺序创建52张卡牌.def __init__(self):self.cards = []# 花色for suit in range(4):# 大小for rand in range(1, 14):# 一共抽 4 * 13 = 52张卡牌.card = Card(suit, rand)self.cards.append(card)def __str__(self):res = []for card in self.cards:res.append(str(card))return '\n'.join(res)deck = Deck()
print(deck)
18.6 添加,删除,洗牌和排序
为了能够发牌, 我们需要一个方法从牌组中抽取一张牌并返回.
列表方法pop为此提供了一个方便的功能:
# 在Deck类里def pop_card(self):return self.cards.pop()
由于pop从列表中抽出最后一张牌, 我们其实从牌组的低端发牌的.
要添加一个卡牌, 我们可以使用列表方法append:
# 在Deck类里:def add_card(self, card):self.cards.append(card)
像这样调用另一个方法, 却不做其他更多工作的方法, 有时候称为一个'饰面(veneer)'.
这个比喻来自于木工行业, 在木工行业饰面是为了改善外观而粘贴到便宜的木料表面的薄薄的一层优质木料.
(名字很高大尚, 里面没什么..)
在这个例子里, add_card是一个'薄薄'的方法, 用更适合牌组的术语来表达一个列表操作.
它改善了实现的外观(或接口).作为另一个示例, 我们可以使用random模块的函数shuffle来编写一个Deck方法shuffle(洗牌):
# 在Deck类里def shuffle(self):random.shuffle(self.cards)
不要忘记导入random模块.
作为练习, 编写一个Deck方法sort, 使用列表方法sord来对一个Deck中的卡牌进行排序.
sort使用我们定义的__lt__方法来决定顺序.
后面这句话的解释:
在Python中, sort方法用于对列表进行排序. 
默认情况下, sort方法会按照元素的大小顺序来排序, 而对于用户自定义的类,
如果想要使用sort方法进行排序, 需要定义该类的比较方法.在本例中, 我们定义了Card类, 并在其中实现了__lt__方法, 该方法用于比较两张卡牌的大小.
当我们调用sort方法对Deck中的卡牌进行排序时, sort方法会自动调用Card类中的__lt__方法来比较卡牌的大小,
从而实现对卡牌的排序.因此, 我们可以说, sort方法使用了我们定义的__lt__方法来决定卡牌的顺序.
# 在Deck类里def sort(self):"""按升序排列卡片."""self.cards.sort()
18.7 继承
继承是一个能够定义一个新类对现有的某个类稍作修改的语言特性.
作为示例, 假设我们想要一个类来表达一副'手牌', 即玩家手握的一副牌.
一副手牌和一套牌组相似: 都是由卡牌的集合组成, 并且都需要诸如增加和移除卡牌的操作.一副手牌和一套牌组也有区别: 我们期望手牌拥有的一些操作, 对牌组来说并无意义.
例如, 在扑克牌中, 我们可能想要比较两副手牌来判断谁获胜了.
在桥牌中, 我们可能需要为一副手牌计算分数以叫牌.这种类之间的关系--相似, 但不相同--让它称为继承.
要定义一个继承现有类的新类, 可以把现有类的名称放在括号之中:
class Hand(Deck):"""Represents a hand of playing cards."""
这个定义说明Hand从Deck继承而来;
这意味着我们可以像Deck对象那样在Hand对象上使用pop_card和add_card方法.当你类继承现有类时, 现有的类被称为'父类(parent)', 而新类则称为'子类(child)'.在本例中, Hand也会继承Deck的__init__方法, 但它和我们想要的并不一样:
我们不需要填充52张卡牌, Hand的init方法应当初始化cards为一个空列表.如果我们为Hand类提供了一个init方法, 它会覆盖Deck类的方法:
# 在Head类里:def __init__(self, lable=''):self.cards = []self.lable = lable
在创建Hand对象时, Python会调用这个init方法而不是Deck中的那个:
>>> hand = Hand('new hand')
>>> hand.cards
[]
>>> hand.label
'new hand'
其他的方法是从Deck中继承而来的, 所以我们可以使用pop_card和add_cards来出牌.
>>> deck = Deck()
# 牌组出一张牌
>>> card = deck.pop_card()
# 手牌添加这张牌
>>> hand.add_card(card)
# 打印这张牌
print(hand)
King of Spades
下一步很自然地就是将这段代码封装起来成为一个方法move_cards:
# 在Deck类里:def move_cards(self, hand, num):# 发多张牌for i in range(num):hand.add_card(self.pop_card())
# 完整代码
# 完整代码
import randomclass Card:"""Represents a standard playing card.(代表一张标准的扑克牌.)"""# 花色名称列表suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']# 大小名称列表rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']def __init__(self, suit=0, rank=2):# 花色默认为草花self.suit = suit# 大小默认为2self.rank = rankdef __str__(self):return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])def __lt__(self, other):t1 = self.suit, self.rankt2 = other.suit, other.rank# 元组作比较, 先对第一个元素做比较, 如果相同, 再对第二个元素作比较.return t1 < t2class Deck:def __init__(self):self.cards = []# 花色for suit in range(4):# 大小for rand in range(1, 14):# 一共抽 4 * 13 = 52张卡牌.card = Card(suit, rand)self.cards.append(card)def __str__(self):res = []for card in self.cards:res.append(str(card))return '\n'.join(res)# 发牌def pop_card(self):return self.cards.pop()# 添加卡牌def add_card(self, card):self.cards.append(card)# 洗牌def shuffle(self):random.shuffle(self.cards)# 排序def sort(self):"""按升序排列卡片."""self.cards.sort()def move_cards(self, hand, num):# 发多张牌for i in range(num):hand.add_card(self.pop_card())class Hand(Deck):"""Represents a hand of playing cards.""""""当子类未显式调用父类的__init__()方法时, 会在子类提示该提示:Call to __init__ of super class is missed代码并没有错误, 只是提示用户不要忘记调用父类初始化方法."""def __init__(self, lable=''):self.cards = []self.lable = labledef main():# 实例一个牌组对象deck = Deck()# 实例手牌对象, 起一个名称hand = Hand('new hand')# 从牌组末尾发13张牌到手上.deck.move_cards(hand, 13)# 查看手上的牌(所有的黑桃牌)for card in hand.cards:print(card)if __name__ == '__main__':main()
move_cards接收两个参数, 一个Hand对象以及需要出牌的牌数. 它会修改seld和hand, 返回None.有的情况下, 卡牌会从一副手牌中移除转入到另一副手牌中, 或者从手牌中回到牌组.
你可以使用move_cards来处理全部这些操作: self即可以是一个Deck对象, 也可以是一个Hand对象.
而hand参数, 虽然名字是hand却也可以是一个Deck对象.
继承是很有用的语言特性.
有些程序不用继承些, 会有很多重复代码, 使用继承后就会更加优雅.
继承也能促进代码复用, 因为你可以在不修改父类的前提下对它的行为进行定制化.
有的情况喜爱, 继承结构反映了问题的自然结构, 所以也让设计更容易理解.但另一方面, 继承也可能会让代码更难读.
有时候当一个方法被调用时, 并不清楚到哪里能找到它的定义.
相关的代码可能散布在几个不同的模块中.
并且, 很多可以用继承实现的功能, 也能不用它实现, 甚至可以实现得更好.
18.8 类图
至此我们已见过用于显示程序状态的栈图, 以及用于显示对象的属性值的对象图.
这些图表展示了程序运行中的一个快照, 所以当程序继续运行时它们会跟着改变.它们也极其详细; 在某些情况下, 是过于详细了. 而类图对象结构的展示相对来说更加抽象.
它不会具体显示每个对象, 而是显示各个类以及它们之间的关联.类之间有下面几种关联.
* 一个类的对象可以包含其他类的对象的引用. 例如, 米格Rectangle对象都包含一个到Point对象的引用, 而每一个Deck对象包含到很多Card对象的引用.这种关联称为'HAS-A(有一个)', 也就是说, '矩形(rectangle)中有一个点(Point)'.* 一个类可能继承自另一个类. 这种关系称为IS-A(是一个), 也就是说'一副手牌(Hand)是一个牌组(Deck)'.* 一个类可能依赖于另一个类. 也就是说, 一个类的对象接收另一个类的对象作为参数, 或者使用另一个类的对象来进行某种计算.这种关系称为'依赖(dependency)'.类图用图形展示了这些关系. 例如, 下图展示了Card, Deck和Hand之间的关系.

2023-04-24_00001

空心三角形箭头的线代表着一个IS-A关系; 这里表示Head是继承自Deck的.标准的箭头表示HAS-S关系; 这里表示Deck对象中用到Card对象的引用.箭头附近的星号(*)表示是'关联重复标记'; 它表示Deck中有多个Cards.
这个数可以是一个简单的数字, 52, 或者一个范围, 5..7, 或者一个星号, 表示Deck可以有任意数量的Card引用.上图中没有任何依赖关系. 依赖关系通常使用虚线箭头表示.
或者, 如果有太多的依赖, 有时候会忽略它们.更详细的图可能会显示出Deck对象实际上包含了一个Card的列表.
但在类图中, 像列表, 字典这样的内置类型通常是不显示的.
18.9 数据封装
前几章展示了一个我们可以称为'面向对象设计'的开发计划.
我们发现需要的对象, 如Point, Rectangle和Time并定义类来表达它们.
每个类都是一个对象到现实世界(或者最少是数学世界)中某种实体的明显对应.但有时候你到底需要哪些对象, 它们如何交互, 并不那么显而易见.
这时候你需要另一种开发计划. 
和之前我们通过封装和泛化来发现函数接口的方式相同, 我们可以通过'数据封装'来发现类的接口.13.8节提供了一个很好的示例.如果从↓下载我的代码.
https://github.com/AllenDowney/ThinkPython2/blob/master/code/markov.py
你会发现它使用了两个全局变量(suffix_map和prefix)并且在多个函数中进行读写.
suffix_map = {}
prefix = ()
因为这些变量是全局的, 我们每次只能运行一个分析.
如果读入两个文本, 它们的前缀和后缀就会添加到相同的数据结构中(最后可以用来产生一些有趣的文本).若要多此运行分析, 并保证他们之间的独立, 我们可以将每次分析的状态信息封装成一个对象.
下面是它的样子:
class Markov:def __init__(self):self.suffix_map = {}self.prefix = ()
接下来我们将那些函数转换为方法. 
例如, 下面是process_word:
def process_word(self, word, order=2):if len(self.prefix) < order:self.prefix += (word,)returntry:self.suffix_map[self.prefix].append(word)except:# 如果前缀不存在, 创建一项self.suffix_map[self.prefix] = [word]self.prefix = shift(self.prefix, word)
像这样转换程序--修改设计单不修改其行为--是重构(参见4.7)的另一个示例.
这个例子给出了一个设计对象和方法的开发计划.
* 1. 从编写函数, (如果需要的话)读写全局变量开始.
* 2. 一旦你的程序能够正确运行, 查看全局变量与使用它们的函数的关联.
* 3. 将相关的变量封装成对象的属性.
* 4. 将相关的函数转换为这个新类的方法.
作为练习, 从↓下载我的Markov代码, 并按照上面描述的步骤将全局变量封装为一个叫作Markov的新类的属性.
https://github.com/AllenDowney/ThinkPython2/blob/master/code/markov.py
解答: https://github.com/AllenDowney/ThinkPython2/blob/master/code/Markov.py (注意M是大写的).
解答使用这个: https://github.com/AllenDowney/ThinkPython2/blob/master/code/markov2.py
import sys
import random# global variables
suffix_map = {}  # map from prefixes to a list of suffixes
prefix = ()  # current tuple of wordsdef process_file(filename, order=2):# 实例文件对象fp = open(filename)# 跳过开头skip_gutenberg_header(fp)# 跳过结尾for line in fp:if line.startswith('*** END OF THIS'):break# 将每一行的末尾\n去除, 再按空格切分得到单词列表. 最后遍历单词.for word in line.rstrip().split():# 基于马尔可夫分析process_word(word, order)# 跳过开头
def skip_gutenberg_header(fp):for line in fp:if line.startswith('*** START OF THIS'):breakdef process_word(word, order=2):# 声明prefix是全局的.global prefix# 将前缀元组填满# 统计前缀元组的长度是否小于规定的前缀单词长度if len(prefix) < order:#  如果小于则往元组内添加单词prefix += (word,)returntry:# 前缀为键, 单词为值suffix_map[prefix].append(word)except KeyError:# 键不存在则新建项suffix_map[prefix] = [word]# 重新设置前缀prefix = shift(prefix, word)def random_text(n=100):# 从字典的键中随机选一个开始的键start = random.choice(list(suffix_map.keys()))# 选出100个单词for i in range(n):# 获取这个键的后缀值, 如果没有这, 则返回Nonesuffixes = suffix_map.get(start, None)# 后缀为None, 则从新执行random_text在选一个键. n的次数需要减去1.if suffixes is None:# 执行这句则不会执行return下面几行的代码random_text(n - i)return# 随机选择一个后缀word = random.choice(suffixes)# 打印后缀print(word, end=' ')# 重新设置前缀start = shift(start, word)def shift(t, word):# 修改前缀, 前缀2改为前缀1, 单词改为前缀2return t[1:] + (word,)# 参数1: 脚本路径, 参数2: 打开的文件名, 参数3: 生成单词文本的长度, 参数4: 前缀单词个数.
def main(script, filename='158-0.txt', n=100, order=2):try:# 如果用户提供的是字符串参数, 则可以使用int将纯字符串的数字转为整数.n = int(n)order = int(order)# 如果发生错误, 提示使用方法不正确.except ValueError:print('Usage: %d filename [# of words] [prefix length]' % script)# try正常结束执行下面的代码.else:# 参数1: 文件名, 前缀单词个数process_file(filename, order)# 随机生成文本random_text(n)if __name__ == '__main__':# *sys.argv 获取文件的地址 C:\Backup\Program\ThinkPython\t1\t1.pymain(*sys.argv)
import sys
import random# 跳过开头
def skip_gutenberg_header(fp):for line in fp:if line.startswith('*** START OF THIS'):break# 重置前缀元组
def shift(t, word):return t[1:] + (word,)# 马尔可夫类
class Markov:# 初始化def __init__(self):# 后缀字典self.suffix_map = {}# 前缀元组self.prefix = ()# 读取文件的过程def process_file(self, filename, order):# 实例文件对象fp = open(filename)# 跳过开头skip_gutenberg_header(fp)# 跳过结尾for line in fp:if line.startswith('*** END OF THIS'):break# 将每一行的末尾\n去除, 再按空格切分得到单词列表. 最后遍历单词.for word in line.rstrip().split():# 基于马尔可夫分析self.process_word(word, order)# 分析单词的过程def process_word(self, word, order):if len(self.prefix) < order:self.prefix += (word,)returntry:self.suffix_map[self.prefix].append(word)except:# 如果前缀不存在, 创建一项self.suffix_map[self.prefix] = [word]self.prefix = shift(self.prefix, word)def random_text(self, n=100):# 从字典的键中随机选一个开始的键start = random.choice(list(self.suffix_map.keys()))# 选出100个单词for i in range(n):# 获取这个键的后缀值, 如果没有这, 则返回Nonesuffixes = self.suffix_map.get(start, None)# 后缀为None, 则从新执行random_text在选一个键. n的次数需要减去1.if suffixes is None:# 执行这句则不会执行return下面几行的代码self.random_text(n - i)return# 随机选择一个后缀word = random.choice(suffixes)# 打印后缀print(word, end=' ')# 重新设置前缀start = shift(start, word)def main(script, filename='emma.txt', n=100, order=2):try:# 如果用户提供的是字符串参数, 则可以使用int将纯字符串的数字转为整数.n = int(n)order = int(order)# 如果发生错误, 提示使用方法不正确.except ValueError:print('Usage: %d filename [# of words] [prefix length]' % script)else:# 实例马尔可夫对象markov = Markov()# 分析文本文件markov.process_file(filename, order)# 随机生成文本markov.random_text(n)if __name__ == '__main__':main(*sys.argv)
18.10 调试
继承会给调试带来新的挑战, 因为当你调用对象的方法时, 可无法知道调用的到底是哪个方法.
假设你在编写一个操作Hand对象的函数. 你可能希望能够处理所有类型的Hand, 如PokerHands, BridgeHands等.
如果你调用一个方法, 如shuffle(排序), 可能调用的是Decck中定义的方法, 到如果任何子类重载了这个方法,
则你调用的会是那个重载的版本.一旦你无法确认程序的运行流程, 最简单的解决办法是在相关的方法开头添加一个打印语句.
如果Deck.shuffle打印一句Running Deck.shuffle这样的信息, 则当程序运行时会跟踪运行的流程.或者, 你也可以使用下面这个函数.
它接收一个对象和一个方法名(字符串形式), 并返回提供这个方法的定义的类:
def find_defining_class(obj, meth_name):for ty in type(obj).mro():if math_name in ty.__dict__:return ty
下面是使用的示例:
>>> hand = Hand()
>>> find_defining_class(hand, 'shuffle')
<class 'Card.Deck'>
所以这个Hand对象的shuffle方法是在Deck类中定义的那个.find_defining_class使用mro方法来获得用于搜索调用方法的类对象(类型)列表.
'MRO'意思是'method resolution order'(方法查找顺序), 是Python解析方法名称的时候搜索的类的顺序.一个设计建议: 每次重载一个方法时, 新方法的接口应当和旧方法的一致.
它应当接收相同的参数, 返回相同的类型, 并服从同样的前置条件与后置条件.
如果遵循这个规则, 你会发现任何为如Deck这样的父类设计的函数,
都可以使用Hand或PokerHand这样的子类的实例.如果你破坏这个也称为'Liskov替代原则'的规则, 你的代码可能会像一堆(不好意思)纸牌屋一样崩塌.
18.11 术语表
编码(encode): 使用一个集合的值来表示另一个集合的值, 需要在它们之间建立映射.类属性(class attribute): 关联到类对象上的属性. 类属性定义在类定义之中, 但在所有方法定义之外.实例属性(instance attribute): 和类的实例关联的属性.饰面(veneer): 一个方法或函数, 它调用另一个函数, 却不做其他计算, 只是为了提供不同的接口.继承(inheritance): 可以定义一个新类, 它是一个现有的类的修改版本.父类(parent class): 被子类所继承的类.子类(child class): 通过继承一个现有的类来创建的新类, 也叫作'subclass'.IS-A关联(IS-A relationship): 子类个父类之间的关联.HAS-A关联(HAS-A relationship): 连个类之间的一种关联: 一个类包含另一个类的对象的引用.依赖(dependency): 两个类之间的一种关联. 一个类的实例使用另一个类的实例, 但不把它们作为属性存储起来.类图(class diagram): 用来展示程序中的类以及它们之间的关联的图.重数(multiplicity): 类图中的一种标记方法, 对于HAS-A关联, 用来表示一个类中有多少对另一个类的对象的引用.数据封装(data encapsulation): 一个程序开发计划. 先使用全局变量来进行原型设计, 然后将全局变量转换为实例属性做出最终版本.
18.12 练习
1. 练习1
针对下面的程序, 画一张UML类图, 展示这些类以及它们之间的关联:
UML是什么?
统一建模语言(Unified Modeling Languag, UML)是一种为面向对象系统的产品进行说明,
可视化和编制文档的一种标准语言, 是非专利的第三代建模和规约语言.
UML是面向对象设计的建模工具, 独立于任何具体程序设计语言.
class PingPongParent:passclass Ping(PingPongParent):def __init__(self, pong):self.pong = pongclass Pong(PingPongParent):def __init__(self, pings=None):if pings is None:self.pings = []else:self.pings = pingsdef add_ping(self, ping):self.pings.append(ping)pong = Pong()
ping = Ping(pong)
pong.add_ping(ping)
这些类之间的关联可以用以下方式表示:* 1. Ping类IS-A PingPongParent类, 即Ping类是PingPongParent类的子类.
* 2. Pong类IS-A PingPongParent类, 即Pong类也是PingPongParent类的子类.
* 3. Ping类HAS-A Pong类的实例, 即Ping类具有一个名为pong的属性, 保存一个Pong实例的引用.
* 4. Pong类HAS-A Ping类的实例列表, 即Pong类具有一个名为pings的属性, 保存多个Ping实例的列表.(目前只有一个, 则不使用*.)综上所述, Ping类和Pong类之间存在HAS-A关系, 
而Ping类和PingPongParent类以及Pong类和PingPongParent类之间存在IS-A关系.

2023-04-25_00001

2. 练习2
编写一个名称deal_hands的Deck方法, 接收两个形参: 手牌的数量以及每副手牌的牌数.
它会根据形参创建新的Head对象, 按照每副手牌的牌数出牌, 并返回一个Hand对象列表.
(意思就是, 发几个人牌, 每一副牌多少张.)
# 完整代码
# 完整代码
import randomclass Card:"""Represents a standard playing card.(代表一张标准的扑克牌.)"""# 花色名称列表suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']# 大小名称列表rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']def __init__(self, suit=0, rank=2):# 花色默认为草花self.suit = suit# 大小默认为2self.rank = rankdef __str__(self):return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])def __lt__(self, other):t1 = self.suit, self.rankt2 = other.suit, other.rank# 元组作比较, 先对第一个元素做比较, 如果相同, 再对第二个元素作比较.return t1 < t2class Deck:def __init__(self):self.cards = []# 花色for suit in range(4):# 大小for rand in range(1, 14):# 一共抽 4 * 13 = 52张卡牌.card = Card(suit, rand)self.cards.append(card)def __str__(self):res = []for card in self.cards:res.append(str(card))return '\n'.join(res)# 发牌def pop_card(self):return self.cards.pop()# 添加卡牌def add_card(self, card):self.cards.append(card)# 洗牌def shuffle(self):random.shuffle(self.cards)# 排序def sort(self):"""按升序排列卡片."""self.cards.sort()def move_cards(self, hand, num):# 发多少张牌for i in range(num):hand.add_card(self.pop_card())# 发牌 (手牌数量, 手牌牌数)def deal_hands(self, hands, cards):# 手牌对象列表hands_list = []# 对52张牌进行洗牌self.shuffle()# for循环创建多个手牌对象for i in range(int(hands)):hand = Hand()self.move_cards(hand, cards)hands_list.append(hand)return hands_listclass Hand(Deck):"""Represents a hand of playing cards.""""""当子类未显式调用父类的__init__()方法时, 会在子类提示该提示:Call to __init__ of super class is missed代码并没有错误, 只是提示用户不要忘记调用父类初始化方法."""def __init__(self, lable=''):self.cards = []self.lable = labledef main(hands, cards):# 超出52张牌则提示输入错了:if hands * cards > 52:print('超出52张牌了!')return# 创建牌组对象deck = Deck()# 发牌hand_list = deck.deal_hands(4, 4)for index, hand in enumerate(hand_list):index += 1print('第%d个人的牌为:' % index)print(hand)if __name__ == '__main__':main(4, 4)
"""
TypeError: add_card() missing 1 required positional argument: 'card'
类型错误:添加卡()失踪1所需的位置参数:“卡”
"""
3. 练习3
下面列出的是扑克牌中可能的手牌, 按照牌值大小的增序(也是可能性的降序)排列.
* 对子(pair): 两张牌大小相同.
* 两对(two pair): 连个对子.
* 三条(three of a Kind): 三张牌大小相同.
* 顺子(straight): 五张大小相连的牌. (Acc即可以是最大的也可以是最小, 所以Acc-2-3-4-5是顺子, 10-Jack-Queen-King-Acc也是, 但Queen-King-Acc-2-3不是).
* 同花(flush): 五张牌花色相同.
* 满堂红(full house): 三张牌大小相同, 另外两张牌大小相同.
* 四条(four of a Kind): 四张牌大小相同.
* 同花顺(straight flush): 顺子(如上面的定义)里的五张牌都是花色相同的.
本练习的目标是预测这些手牌的出牌概率.
1. 从↓下载这些文件.
https://github.com/AllenDowney/ThinkPython2/blob/master/code/Card.py
https://github.com/AllenDowney/ThinkPython2/blob/master/code/PokerHand.py
* Card.py: 本章中介绍的Card, Deck和Hand类的完整代码.
* PokerHand.py: 表达扑克手牌的一个类, 实现并不完整, 包含一些测试它的代码.
import random# 卡牌类
class Card:# 花色列表suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]# 大小列表rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7","8", "9", "10", "Jack", "Queen", "King"]# 初始化(默认卡牌为红色2)def __init__(self, suit=0, rank=2):self.suit = suitself.rank = rankdef __str__(self):# 打印对象时展示卡牌return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])# 在两个对象进行 == 比较值的时候触发 __eq__() 的执行def __eq__(self, other):# 比较花色与大小return self.suit == other.suit and self.rank == other.rank# 在两个对象进行 < 小于比较值的时候触发 __lt__() 的执行def __lt__(self, other):t1 = self.suit, self.rankt2 = other.suit, other.rankreturn t1 < t2# 牌组对象
class Deck:# 生成52张牌def __init__(self):self.cards = []for suit in range(4):for rank in range(1, 14):card = Card(suit, rank)self.cards.append(card)# 打印牌组def __str__(self):res = []for card in self.cards:res.append(str(card))return '\n'.join(res)# 添加卡牌到牌组def add_card(self, card):self.cards.append(card)# 从牌组移除某张卡牌def remove_card(self, card):self.cards.remove(card)# 从牌组中弹出出一张牌def pop_card(self, i=-1):return self.cards.pop(i)# 洗牌def shuffle(self):random.shuffle(self.cards)# 排序def sort(self):self.cards.sort()# 发牌def move_cards(self, hand, num):for i in range(num):hand.add_card(self.pop_card())# 手牌
class Hand(Deck):def __init__(self, label=''):# 手牌列表self.cards = []# 手牌名称self.label = labelif __name__ == '__main__':# 实例牌组对象deck = Deck()# 洗牌deck.shuffle()# 生成手牌对象hand = Hand()# 牌组发5张牌到手牌中deck.move_cards(hand, 5)# 排序hand.sort()# 查看手牌print(hand)
2. 如果你运行PokerHand.py, 它会连出7组包含7张卡片的扑克手牌, 并检查其中有没有顺子(因该是同花).在继续之前请仔细阅读代码.
# PokerHand.py
from Card import Hand, Deck# 扑克手
class PokerHand(Hand):# 花色直方图def suit_hist(self):# 构建一个在手中出现的花色的直方图.self.suits = {}#for card in self.cards:self.suits[card.suit] = self.suits.get(card.suit, 0) + 1def has_flush(self):# 构建一个在手中出现的花色的直方图.self.suit_hist()# 取出直方图的值for val in self.suits.values():# 手牌中同一个花色有五张或大于五张则返回True.if val >= 5:return Truereturn Falseif __name__ == '__main__':# 实例牌组对象deck = Deck()# 洗牌deck.shuffle()# 循环实例7组手牌for i in range(7):#  PokerHand调用hand的初始化方法, 实例手牌对象.hand = PokerHand()# 牌组发7张牌到手牌中deck.move_cards(hand, 7)# 手牌排序hand.sort()# 查看手牌print(hand)# 手牌上是否有同花(五张牌花色相同)print(hand.has_flush())print('')
3. 在PokerHand.py中添加方法, has_pair(对子), has_twopair(两对).它们根据手牌时候达到相对应的条件来返回True或False.你的代码应当对任意数量的手牌都适用(虽然最常见的手牌是5或者7).
from Card import Hand, Deck# 扑克手
class PokerHand(Hand):# 花色直方图def suit_hist(self):# 构建一个在手中出现的花色的直方图.self.suits = {}for card in self.cards:self.suits[card.suit] = self.suits.get(card.suit, 0) + 1# 大小直方图def rank_hist(self):self.ranks = {}for card in self.cards:self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1# 同花def has_flush(self):# 构建一个在手中出现的花色的直方图.self.suit_hist()# 取出直方图的值for val in self.suits.values():# 手牌中同一个花色有五张或大于五张则返回True.if val >= 5:return Truereturn False# 对子def has_pair(self):self.rank_hist()# 取出直方图的值for val in self.ranks.values():# 手牌中同一个花色有五张或大于五张则返回True.if val >= 2:return Truereturn False# 连对def has_twopair(self):# 对象计算pair_count = 0self.rank_hist()# 取出直方图的值for val in self.ranks.values():# 手牌中同一个花色有五张或大于五张则返回True.if val >= 2:pair_count += 1if pair_count >= 2:return Truereturn Falseif __name__ == '__main__':# 实例牌组对象deck = Deck()# 洗牌deck.shuffle()# 循环实例7组手牌for i in range(7):#  PokerHand调用hand的初始化方法, 实例手牌对象.hand = PokerHand()# 牌组发7张牌到手牌中deck.move_cards(hand, 7)# 手牌排序hand.sort()# 查看手牌print(hand)# 手牌上是否有同花(五张牌花色相同)print('是否有同花:', hand.has_flush())print('是否有对子:', hand.has_pair())print('是否有连对:', hand.has_twopair())print('')
4. 编写一个函数classsify(分类), 它可以弄清楚一副手牌中出现最大的组合, 并设置label属性.例如, 一副7张牌的手牌可能包含一个顺子以及一个对象; 它应当标记为'flush'(顺子).
5. 但你确保分类方法可用时, 下一步是预料各种手牌的概率.在PolerHand.py中编写一个函数, 对一副牌进行洗牌, 将其分成不同手牌, 对手牌进行分类,并记录每种分类出现的次数.
6. 打印一个表格, 展示各种分类以及它们的概率.更多次地运行你的程序, 直到输出收敛到一个合理程度的正确性为止.将你的结果和http://en.wikipedia.org/wiki/Hand_rankings上的值进行对比.解答: https://github.com/AllenDowney/ThinkPython2/blob/master/code/PokerHandSoln.py
import random# 卡牌类
class Card:# 花色列表suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]# 大小列表rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7","8", "9", "10", "Jack", "Queen", "King"]# 初始化(默认卡牌为红色2)def __init__(self, suit=0, rank=2):self.suit = suitself.rank = rankdef __str__(self):# 打印对象时展示卡牌return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])# 在两个对象进行 == 比较值的时候触发 __eq__() 的执行def __eq__(self, other):# 比较花色与大小return self.suit == other.suit and self.rank == other.rank# 在两个对象进行 < 小于比较值的时候触发 __lt__() 的执行def __lt__(self, other):t1 = self.suit, self.rankt2 = other.suit, other.rankreturn t1 < t2# 牌组对象
class Deck:# 生成52张牌def __init__(self):self.cards = []for suit in range(4):for rank in range(1, 14):card = Card(suit, rank)self.cards.append(card)# 打印牌组def __str__(self):res = []for card in self.cards:res.append(str(card))return '\n'.join(res)# 添加卡牌到牌组def add_card(self, card):self.cards.append(card)# 从牌组移除某张卡牌def remove_card(self, card):self.cards.remove(card)# 从牌组中弹出出一张牌def pop_card(self, i=-1):return self.cards.pop(i)# 洗牌def shuffle(self):random.shuffle(self.cards)# 排序def sort(self):self.cards.sort()# 发牌def move_cards(self, hand, num):for i in range(num):hand.add_card(self.pop_card())# 手牌
class Hand(Deck):def __init__(self, label=''):# 手牌列表self.cards = []# 手牌名称self.label = labelif __name__ == '__main__':# 实例牌组对象deck = Deck()# 洗牌deck.shuffle()# 生成手牌对象hand = Hand()# 牌组发5张牌到手牌中deck.move_cards(hand, 5)# 排序hand.sort()# 查看手牌print(hand)
from Card import Hand, Deckclass Hist(dict):"""从每个项目(x)映射到其频率。"""def __init__(self, seq=[]):# 从sep遍历值, 并统计值出现的次数for x in seq:self.count(x)def count(self, x, f=1):# 设置属性值, 值为x出现的次数self[x] = self.get(x, 0) + f# 数值为0, 则删除属性if self[x] == 0:del self[x]# 扑克手
class PokerHand(Hand):"""Represents a poker hand."""# 同花顺, 四条, 满堂红, 同花, 顺子, 三条, 两对, 对子,  高牌(由单牌且不连续不同花的组成)all_labels = ['straightflush', 'fourkind', 'fullhouse', 'flush','straight', 'threekind', 'twopair', 'pair', 'highcard']def make_histograms(self):# 花色统计self.suits = Hist()# 大小统计self.ranks = Hist()# 遍历扑克手的卡牌for c in self.cards:# 统计花色(花色为属性名, 出现的次数为属性值)self.suits.count(c.suit)# 统计大小(大小为属性名, 出现的次数为属性值)self.ranks.count(c.rank)# 将卡牌大小的统计值转为列表, 保存到sets属性中self.sets = list(self.ranks.values())# 降序self.sets.sort(reverse=True)# 是否为高牌def has_highcard(self):# 有一张牌即可return len(self.cards)def check_sets(self, *t):# *t收集所有的参数到元组中, self.sets 卡牌大小的统计值的倒序的列表. zip函数将两个序列合并成元组列表.for need, have in zip(t, self.sets):# 当提供的参数值 大于 类别中的值返回False, 否则返回True.if need > have:  # 这一句反正写看起来很变扭.return Falsereturn True# 是否有对子def has_pair(self):# zip((2, ), [x]) x的值 大于 提供的参数, 则说明有对子. 返回True.return self.check_sets(2)# 是否有两对def has_twopair(self):# zip((2, 2), [x1, x2]) x1与x2的 大于 提供的参数, 则说明有连个对子. 返回True.return self.check_sets(2, 2)# 三条def has_threekind(self):# zip((3, ), [x]) x的值 大于 提供的参数, 则说明有对子. 返回True.return self.check_sets(3)# 四条def has_fourkind(self):# zip((4, ), [x]) x的值 大于 提供的参数, 则说明有对子. 返回True.return self.check_sets(4)# 满堂红def has_fullhouse(self):# zip((3, 2), [x]) x的值 大于 提供的参数, 则说明有对子. 返回True.return self.check_sets(3, 2)# 同花def has_flush(self):# 获取花色直方图的值, 如果有一个值大于或等于5则返回True.for val in self.suits.values():if val >= 5:return Truereturn False# 顺子def has_straight(self):# 复制一份花色直方图ranks = self.ranks.copy()# 添加项 键为14, 值为键x.ranks[14] = ranks.get(1, 0)# 顺子牌计数return self.in_a_row(ranks, 5)def in_a_row(self, ranks, n=5):# 计数器count = 0# 遍历值1-14for i in range(1, 15):# 从直方图中按1-14取值, 取到值则计算器加1if ranks.get(i, 0):count += 1# 连续取出5张牌都有值, 则是顺子返回Trueif count == n:return True# 顺子中断, 计算器清零.else:count = 0# 没有顺子返回Falsereturn False# 同花顺def has_straightflush(self):# 集合(将手上的牌, 添加到集合中)s = set()# 遍历手牌上的牌for c in self.cards:# 按(大小, 花色) 添加到集合中s.add((c.rank, c.suit))# 卡牌为1, 集合添加(14, 花色)if c.rank == 1:s.add((14, c.suit))"""对于牌面为A的牌, 因为A可以被视为1或14, 所以在集合s中需要同时添加(1, c.suit)和(14, c.suit)两个元素, 以便在检查同花顺时能够正确判断."""# (生成所有的牌组数字形式)# 遍历0 - 3for suit in range(4):count = 0# 遍历1-14for rank in range(1, 15):# 判断这张牌是否在集合中if (rank, suit) in s:# 如果在则加1count += 1# 五张同花顺则返回Trueif count == 5:return Trueelse:count = 0return False# 同花顺-字典版本def has_straightflush(self):# 定义一个字典d = {}# 遍历卡牌for c in self.cards:# 往字典中保存项, (键为花色, 值为一个列表) PokerHand()得到一个扑克手对象,d.setdefault(c.suit, PokerHand()).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 Falsedef classify(self):# 对牌进行直方图统计self.make_histograms()# 设置对象的属性为列表(可以为一副牌打上多个标记)self.labels = []# 取出标记for label in PokerHand.all_labels:# 取出标记方法名(执行9个方法)f = getattr(self, 'has_' + label)# 执行方法名, 如果方法执行之后返回True则将标记添加到手牌属性的label列表中if f():self.labels.append(label)class PokerDeck(Deck):def deal_hands(self, num_cards=5, num_hands=10):# 牌手列表hands = []# 生成7个实例for i in range(num_hands):# 实例 扑克手对象hand = PokerHand()# 从牌组发牌给扑克手self.move_cards(hand, num_cards)# 为手上的牌设置标记hand.classify()# 将实例添加到列表hands.append(hand)# 返回实例列表return handsdef main():# 直方图对象lhist = Hist()# 每迭代循环n次,处理7的手,每7张牌n = 10000for i in range(n):# 打印循环的次数, 当i可以整除1000时打印i的值if i % 1000 == 0:print(i)# 生成牌组deck = PokerDeck()# 洗牌deck.shuffle()# 给7个人发7张牌hands = deck.deal_hands(7, 7)# 遍历7副牌for hand in hands:# 遍历牌组的标签for label in hand.labels:# 统计标签的数量lhist.count(label)# 70000 副牌的统计结果total = 7.0 * nprint(total, 'hands dealt:')# 遍历所有标签for label in PokerHand.all_labels:# 依据标签获取属性, 属性记录这在牌组中出现的次数.freq = lhist.get(label, 0)# freq 的值为0, 跳过.if freq == 0:continue# 计算比较p = total / freq# 打印概率print('%s happens one time in %.2f' % (label, p))if __name__ == '__main__':main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/854115.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

概率论拾遗

条件期望的性质 1.看成f(Y)即可 条件期望仅限于形式化公式&#xff0c;用于解决多个随机变量存在时的期望问题求解&#xff0c;即 E(?)E(E(?|Y))#直接应用此公式条件住一个随机变量&#xff0c;进行接下来的计算即可 定义随机变量之间的距离为&#xff0c;即均方距离 随机…

Redis分布式锁的实现、优化与Redlock算法探讨

Redis分布式锁最简单的实现 要实现分布式锁,首先需要Redis具备“互斥”能力,这可以通过SETNX命令实现。SETNX表示SET if Not Exists,即如果key不存在,才会设置它的值,否则什么也不做。利用这一点,不同客户端就能实现互斥,从而实现一个分布式锁。 举例: 客户端1申请加…

(科学:某天是星期几)泽勒一致性是由克里斯汀·泽勒开发的用于计算某天是星期几的算法。

(科学:某天是星期几)泽勒一致性是由克里斯汀泽勒开发的用于计算某天是星期几的算法。这个公式是: 其中: h是一个星期中的某一天(0 为星期六;1 为星期天;2 为星期一;3 为星期二;4 为 星期三;5 为星期四;6为星期五)。 q 是某月的第几天。 m 是月份(3 为三月&#xff0c;4 为四月,…

朴素贝叶斯分类器 #数据挖掘 #Python

朴素贝叶斯分类器是一种基于概率统计的简单但强大的机器学习算法。它假设特征之间是相互独立的&#xff08;“朴素”&#xff09;&#xff0c;尽管在现实世界中这通常不成立&#xff0c;但在许多情况下这种简化假设仍能提供良好的性能。 基本原理&#xff1a;朴素贝叶斯分类器…

笔记本开机原理

从按下开机键开始&#xff0c;机器是如何开到OS的呢&#xff1f;今天这篇文章和大家极少EC-BIOS-OS的整个开机流程。首先大家要对笔记本的基本架构有所了解&#xff0c;基本架构如下图所示&#xff08;主要组成部分为大写黑体内容&#xff09;。 一、按下PowerButton按钮&#…

人工智能发展历程了解和Tensorflow基础开发环境构建

目录 人工智能的三次浪潮 开发环境介绍 Anaconda Anaconda的下载和安装 下载说明 安装指导 模块介绍 使用Anaconda Navigator Home界面介绍 Environment界面介绍 使用Jupter Notebook 打开Jupter Notebook 配置默认目录 新建文件 两种输入模式 Conda 虚拟环境 添…

酷开会员丨酷开系统K歌模式,父亲节的家庭欢聚时光

K歌以其独特的魅力&#xff0c;为家庭娱乐带来了无限乐趣。想象一下&#xff0c;父亲节这天&#xff0c;打开电视进入K歌频道&#xff0c;与家人一起嗨唱&#xff0c;客厅里充满了欢声笑语&#xff0c;酷开系统的K歌应用也就成为了连接亲情的桥梁&#xff0c;让爸爸们都能在这个…

Nvidia芯片Jetson系列 系统烧录环境 搭建

一、序言 Jetson 系列产品烧录系统的方法一般有两种&#xff1a; 一种为使用 NVIDIA 官方提供 的 SDK manager 软件给 Jetson 设备烧录系统&#xff08;请查看说明文档《Jetson 产品使用 SDKmanager 烧录系统》&#xff09;。 另一种即为当前文档所描述的&#xff0c;在安装 Ub…

基于SSM+Jsp的旅游景点线路网站

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

开源新纪元:ChatTTS——引领对话式文本转语音的新潮流

✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的点赞、关注、收藏、评论&#xff0c;是对我最大…

好用的库函数,qsort函数大详解(干货满满!)(进阶)

前言&#xff1a; 小编在上一篇文章说了这一篇将要写qsort函数的模拟实现&#xff0c;那么废话不多说&#xff0c;现在开始进入今天的代码之旅喽&#xff01; 目录&#xff1a; 1.qsort函数的模拟实现的逻辑和思路 2.qsort函数模拟实现的代码实现 3.代码展示 1.qsort函数的模…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第41课-动态添加3D对象

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第41课-动态添加3D对象 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界引擎…

2.华为配置静态路由

通过配置静态路由让PC1和PC2互通 AR1 [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 192.168.1.254 24 [Huawei]int g0/0/1 [Huawei-GigabitEthernet0/0/1]ip add 1.1.1.1 24 [Huawei]ip route-static 192.168.2.0 24 1.1.1.2AR2 [Huawei]int g0/0/0 [Huawei-Gig…

盘点有趣的人工智能开源项目一

字幕导出 zh_recogn是一个专注于中文语音识别的字幕生成工具&#xff0c;基于魔塔社区Paraformer模型。它不仅支持音频文件&#xff0c;还能处理视频文件&#xff0c;输出标准的SRT字幕格式。这个项目提供了API接口和简单的用户界面&#xff0c;使得用户可以根据自己的需求灵活…

GitLab、jenkins

Gitlab服务器&#xff1a;192.168.10.20 jenkins服务器&#xff1a;192.168.10.30 web应用服务器&#xff1a;192.168.10.100 通过容器部署gitlab&#xff1a; 安装容器管理软件podman 修改主机的22端口&#xff0c;该gitlab软件包中会使用到该端口 gitlab容器需要使用/etc/res…

一二三应用开发平台应用开发示例(3)——生成库表及后端代码

生成库表 前端页面的配置&#xff0c;也就是视图功能&#xff0c;我们先放一放&#xff0c;来看看生成库表和后端代码。 关闭实体配置界面&#xff0c;回到实体列表&#xff0c;勾选“文件夹”实体&#xff0c;点击“生成库表”&#xff0c;并确定。 系统提示成功后&#xff…

【每天学会一个渗透测试工具】dirsearch安装及使用指南

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 ✨dirsearch介绍 dirsearch安装包百度网盘 disearch是基于Python开发的&#xff0c;因此需要确保你的系统中已经安装了pyth…

flstudio怎么调中文

FL Studio设置中文的步骤如下&#xff1a; 打开FL Studio&#xff1a;首先&#xff0c;需要打开FL Studio编曲软件。 进入常规设置&#xff1a;在软件顶部菜单栏中&#xff0c;选择“OPTIONS”&#xff0c;然后点击“General setting”&#xff0c;进入常规设置窗口。 切换语言…

经典电源电路基础(变压-整流-滤波-稳压)

1.电源电路的功能和组成 电子电路中的电源一般是低压直流电&#xff0c;先把220v交流电变换成低压直流电&#xff0c;再用整流电路变成脉动的直流电&#xff0c;最后用滤波电路滤除掉脉动直流中的交流成分后才能得到直流电。有的电子设备对电源的质量要求很高&#xff0c;所以…