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的内容.
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)
填充牌组最简单的办法是使用嵌套循环.
外层循环从0到3遍历各个花色. 内层循环从1到13遍历开牌大小.
每次迭代使用当前的花色和大小创建一个新的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之间的关系.
空心三角形箭头的线代表着一个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关系.
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()
相关文章:
18. 第十八章 继承
18. 继承 和面向对象编程最常相关的语言特性就是继承(inheritance). 继承值得是根据一个现有的类型, 定义一个修改版本的新类的能力. 本章中我会使用几个类来表达扑克牌, 牌组以及扑克牌性, 用于展示继承特性.如果你不玩扑克, 可以在http://wikipedia.org/wiki/Poker里阅读相关…...
OperationalError: (_mysql_exceptions.OperationalError)
OperationalError: (_mysql_exceptions.OperationalError) (2006, MySQL server has gone away) 这个错误通常表示客户端(例如你的 Python 程序使用 SQLAlchemy 连接到 MySQL 数据库)和 MySQL 服务器之间的连接被异常关闭了。这个问题可能由多种原因引起,以下是一些常见的原…...
DocGraph相关概念
结合简化版的直观性和专业版的深度,我们可以得到一个既易于理解又包含专业细节的DocGraph概念讲解。 DocGraph概述(简化版) 想象DocGraph就像是文章信息的地图。它通过拆分文档、识别关键词、分析关系,并最终以图形方式呈现这些…...
MySQL限制登陆失败次数配置
目录 一、限制登陆策略 1、Windows 2、Linux 一、限制登陆策略 1、Windows 1)安装插件 登录MySQL数据库 mysql -u root -p 执行命令安装插件 #限制登陆失败次数插件 install plugin CONNECTION_CONTROL soname connection_control.dll;install plugin CO…...
洛谷题解 - P1192 台阶问题
目录 题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示代码 题目描述 有 N N N 级台阶,你一开始在底部,每次可以向上迈 1 ∼ K 1\sim K 1∼K 级台阶,问到达第 N N N 级台阶有多少种不同方式。 输入格式 两个正整数 N , K …...
Unity贪吃蛇改编【详细版】
Big and small greedy snakes 游戏概述 游戏亮点 通过对称的美感,设置两条贪吃蛇吧,其中一条加倍成长以及加倍减少,另一条正常成长以及减少,最终实现两条蛇对整个界面的霸占效果。 过程中不断记录两条蛇的得分情况,…...
React中数据响应式原理
React作为当下最流行的前端框架之一,以其声明式编程和组件化架构而广受开发者喜爱。而React的数据响应式原理,是其高效更新DOM的核心机制。本文将深入探讨React中数据响应式原理,并结合代码示例进行论证。 响应式原理概述 在React中&#x…...
【FreeRTOS】ARM架构汇编实例
目录 ARM架构简明教程1. ARM架构电脑的组成1.2 RISC1.2 提出问题1.3 CPU内部寄存器1.4 汇编指令 2. C函数的反汇编 学习视频 【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 https://www.…...
【Linux】常见指令的使用
文章目录 which指令stat 指令wc指令echo指令tree 指令whoami指令clear指令alias指令ls指令pwd指令cd 指令touch指令mkdir指令(重要)rmdir指令 && rm 指令(重要)man指令(重要)cp指令(重要…...
C#面:详细阐述什么是 DTO
DTO(Data Transfer Object)是一种设计模式,用于在不同层之间传输数据。它的主要目的是在应用程序的不同部分之间传递数据,而不是直接传递实体对象。DTO通常是一个简单的POCO(Plain Old CLR Object)…...
「TCP 重要机制」三次握手四次挥手
🎇个人主页:Ice_Sugar_7 🎇所属专栏:计网 🎇欢迎点赞收藏加关注哦! 三次握手&四次挥手 🍉连接管理🍌三次握手🍌意义🍌四次挥手🍌TCP 状态转换…...
Java数据库编程
引言 在现代应用开发中,与数据库交互是不可或缺的一部分。Java提供了JDBC(Java Database Connectivity) API,允许开发者方便地连接到数据库并执行SQL操作。本文将详细介绍Java数据库编程的基础知识,包括JDBC的基本概念…...
决策树算法介绍:原理与案例实现
一、引言 决策树是一种常用于分类和回归任务的机器学习算法,因其易于理解和解释的特点,在数据分析和挖掘领域有着广泛应用。本文将介绍决策树算法的基本原理,并通过一个具体案例展示如何实现和应用该算法。 二、决策树算法原理 1. 决策树结…...
业务代表模式
业务代表模式 引言 在软件工程中,设计模式是解决常见问题的经典解决方案。它们为开发人员提供了一种方法,以优雅和可重用的方式处理软件开发中的挑战。业务代表模式(Business Delegate Pattern)是一种行为设计模式,它主要关注于将业务逻辑与表示层(如用户界面)分离,以…...
LeetCode 算法:反转链表 c++
原题链接🔗:反转链表 难度:简单⭐️ 题目 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1] 示例 2:…...
【多线程】Thread类及其基本用法
🥰🥰🥰来都来了,不妨点个关注叭! 👉博客主页:欢迎各位大佬!👈 文章目录 1. Java中多线程编程1.1 操作系统线程与Java线程1.2 简单使用多线程1.2.1 初步创建新线程代码1.2.2 理解每个…...
Springboot 整合 Flowable(一):使用 flowable-UI 绘制流程图
目录 一、Flowable简介 二、Flowable 与 Activiti 的区别 三、流程图的绘制(以员工请假流程图为例) 1、下载 flowable 的压缩包: 2、启动包中的 tomcat 3、登录页面 4、绘制结束,导出 bpmn20.xml文件 一、Flowable简介 Fl…...
课设--学生成绩管理系统(一)
欢迎来到 Papicatch的博客 文章目录 🍉技术核心 🍉引言 🍈标识 🍈背景 🍈项目概述 🍈 文档概述 🍉可行性分析的前提 🍈项目的要求 🍈项目的目标 🍈…...
thinkphp5模型的高级应用
ThinkPHP5 是一个基于 PHP 的轻量级框架,它提供了许多便利的功能来简化 Web 开发。在 ThinkPHP5 中,模型(Model)是 MVC(Model-View-Controller)架构中的重要组成部分,负责处理数据逻辑。以下是一…...
XML XSLT:技术与应用解析
XML XSLT:技术与应用解析 XML(可扩展标记语言)和XSLT(XML样式表转换语言)是现代信息技术中不可或缺的工具。本文将深入探讨XML和XSLT的概念、技术细节以及它们在实际应用中的作用。 XML简介 XML是一种用于存储和传输…...
嵌入式单片机中项目在线仿真工具分享
前段时间,无意间发现了一个不错的在线仿真工具(Wokwi),支持多种平台,支持市面上主流的开发板,比如:STM32、ESP32、Arduino、树莓派等。 还支持常见的传感器、显示器件(LCD、LED屏幕)等,还可以播放音乐、联网、逻辑分析仪等,关键还提供了很多实际项目的案例。 这款工…...
Unity动态添加聊天文本
1.创建一个滚动视图 2.调整滚动视图的位置并删掉这个 3.创建一个输入框和一个按钮 这里插一句一定要给content添加这个组件并设置单元格大小 4创建一个脚本并编写下面代码 using System.Collections; using System.Collections.Generic; using TMPro; using Unity.VisualScrip…...
力扣-2269. 找到一个数字的 K 美丽值
文章目录 力扣题目代码工程C实现python实现 力扣题目 一个整数 num 的 k 美丽值定义为 num 中符合以下条件的 子字符串 数目: 子字符串长度为 k 。 子字符串能整除 num 。 给你整数 num 和 k ,请你返回 num 的 k 美丽值。 注意: 允许有 前…...
一个在C#中集成Python的例子
一个在C#中集成Python的例子。在C#中可以执行Python脚本,在Python中也可以调用C#宿主中的功能(clr.AddReference(Business))。 文件说明 Debug为执行目录 Mgr.exe为执行文件 Py\init.py为python初始化脚本 Py\Lib.zip为python需要的模块&…...
基于RandLA-Net深度学习模型的激光点云语义分割
一、场景要素语义分割部分的文献阅读笔记 RandLA-Net是一种高效、轻量级的神经网络,其可直接逐点推理大规模点云的语义标签。RandLA-Net基于随机点采样获得了显著的计算和内存效率,并采用新的局部特征聚合模块有效地保留了几何细节,弥补了随机…...
C语言的结构体与联合体
引言 C语言提供了结构体和联合体两种聚合数据类型,使得程序员可以创建包括多个数据类型的复杂数据结构。结构体用于将不同类型的数据组合成一个单元,而联合体用于在同一存储空间中存储不同类型的数据。本篇文章将详细介绍C语言中的结构体和联合体&#x…...
React Hooks小记(三)_forwardRef
forwardRef 【写在前面】 1、ref 的作用是获取实例,但由于函数组件不存在实例,因此无法通过 ref 获取函数组件的实例引用,而 React.forwardRef 就是用来解决这个问题的。 2、React.forwardRef 会创建一个 React 组件,这个组…...
面试复习记录
六级终于结束了,之前背的八股几乎也忘得差不多了,今天开始继续准备秋招,以下是每天的安排,会按时更新,就当是一种对自己的督促,也欢迎小伙伴们一起来互相监督。 2024.6.16 力扣:sql基础题库50…...
块级元素与行内元素详解
在网页设计与开发中,元素根据其在页面布局中的表现可分为两大类:块级元素(Block-level Elements)和行内元素(Inline Elements)。理解它们的特性和使用规则对于构建结构清晰、布局合理的网页至关重要。 块级…...
Kotlin编程实践-【Java如何调用Kotlin中带默认值参数的函数】
问题 如果你有一个带有默认参数值的 Kotlin 函数,如何从 Java 调用它而无须为每个参数显式指定值? 方案 为函数添加注解JvmOverloads。 也就是为Java添加重载方法,这样Java调用Kotlin的方法时就不用传递全部的参数了。 示例 在 Kotlin …...
门户网站建设周期/汕头网站建设方案维护
等待中的任务编辑 有一些任务只能由主节点去处理,比如创建一个新的 索引或者在集群中移动分片。由于一个集群中只能有一个主节点,所以只有这一节点可以处理集群级别的元数据变动。在 99.9999% 的时间里,这不会有什么问题。元数据变动的队列基…...
建筑bim工程网报入口/如何优化关键词排名快速首页
1.题目描述 题目来源:https://pintia.cn/problem-sets/14/problems/734 基础编程题目集 760 分 函数题共 13 小题,共计 185 分编程题共 38 小题,共计 575 分 函数题编程题 6-2 多项式求值 (15 分) 函数接口定义&…...
长春公司网站建设/百度seo2022新算法更新
linux安装Navicat,界面出现乱码解决方法 (转发)参考文章: (1)linux安装Navicat,界面出现乱码解决方法 (转发) (2)https://www.cnblogs.com/miao…...
建站平台社区/口碑优化
我们平时所见的打车app例如:滴滴打车,曹操专车......都是走的JT808协议,有人问为什么呢?小编告诉大家,原因是:国家有关部门规定,所有运营车辆必须接入这样的协议,实时监测车辆信息&a…...
19手机网站/seo外包优化
回望2020 2020年第一天。2020年第一个月。2020年第一季度。十一,国庆。现在,2021.1.12020年第一天。 今天是2020年的第一天,昨晚我刚刚经历了一场紧张刺激且尴尬的转正答辩。我来到这家公司6个月了,说实话,我很不开心。昨晚的答辩是我们的部门大boss亲自坐镇,我看得出来…...
企业网站设计费用/百度关键词优化策略
2019独角兽企业重金招聘Python工程师标准>>> jsp页面上实现表格的竖向合并。 01 /** 02 * 合并表格的列 03 * param {String} tableId要合并的表格的id 04 * param {int} fCol开始的列 05 * param {int} eCol结束的列 06 * return void 07 */ 08 func…...