python类与面向对象编程

news2024/11/25 20:54:51

⚠️⚠️⚠️本章后半部分难度激增,请一定认真学习⚠️⚠️⚠️

⚠️⚠️⚠️本章后半部分难度激增,请一定认真学习⚠️⚠️⚠️

⚠️⚠️⚠️本章后半部分难度激增,请一定认真学习⚠️⚠️⚠️

上篇回顾:

上篇我们帮天下第一武道会的选手登记了信息,编写了 hit()defend() 函数,并结合 while 循环实现了决斗过程。然而也遇到了两大问题:一是如何设计出一套模板,优化登记选手信息过程;二是如何设计一套 能体现出动作发起方与承受方 的函数。

想解决这些问题,需要我们转变思维方式,用 面向对象 方式分析问题。所谓 面向对象编程,就是将编程视作 对象 的集合,从 对象与对象间的交互 角度出发思考问题。

与此同时,我们还分析出了 选手类 具有的特征与行为:

从整体上分析出决斗过程中对象与对象间有什么样的交互后,我们就可以正式开始编写代码啦。下面让我们一起来定义选手类吧!

类与对象

在 Python 中,我们用关键字 class 来定义类。比如我们编写 class Player:,就定义好了一个名为 Player 的类:

class Player:
  pass

代码第 1 行的类名 Player 后有一个冒号 :,表示后面的语句都是类的具体内容。所有归属于类管辖的语句,都要增加一层缩进。这和我们之前学习的分支、循环、函数结构是一样的道理。

可以看到,现在 Player 类中只有一条语句 pass。这是 Python 中的特殊语句,没有实际含义,Python 在执行到它时也什么都不会做。不过它能够保证结构的完整性。例如,我们只写了一行 while True:,Python 会警告我们循环体为空;而如果我们在循环体位置添加一行 pass,Python 就不会报错了:

# 循环体为空,Python 会报错
while True:

# 虽然循环体内的 pass 没有实际含义
# 但它补全了 while 循环结构,因此 Python 不会报错
while True:
  pass

类中可以包含若干 属性 和 方法属性 用于描述对象的特征,方法 用来定义对象的行为。比如说,虽然信息表中没有包含这些信息,但我们知道,选手们都有两只胳膊、两条腿、一双眼睛。胳膊、腿、眼睛的数量,就是 Player 类的特征,我们在代码中可以这样表达:

# 选手类
class Player:
  arm_num = 2 # 两只胳膊
  leg_num = 2 # 两条腿
  eye_num = 2 # 一双眼睛

啊哈,原来类的 属性 就是定义在类中的 变量

定义好类后运行代码,却发现结果区域里什么也没有。这是怎么一回事呢?

这是因为,我们所定义的  只是一套模板。就像在生产零件时不能只有图纸,还要交给机器加工出一个个看得见摸得着的实物;我们有了模板后,也要创建出 实例(instance)才能实现对象与对象间的交互。

而创建类实例的过程,就叫做 类的实例化

类的实例化

说起来很玄乎,实际上,它对应的代码十分简单:

# 选手类
class Player:
  arm_num = 2 # 两只胳膊
  leg_num = 2 # 两条腿
  eye_num = 2 # 一双眼睛

# 实例化 Player 类,生成一个新的实例对象
kakarot = Player()

代码第 8 行的含义是实例化 Player 类,并将生成的新的实例对象赋值给变量 kakarot。此时的变量 kakarot 就是 Player 类实例化出来的实例 对象(object)。

是的,我们创建的变量也是 对象

拓展:实例 和 对象 是两个不同的词。当我们强调 类与对象之间的关系 时,会说“kakarot 是 Player 的实例”;当我们 单独提起某一具体实例时,通常会说“对于 kakarot 对象……”。

Python 奉行 万物皆对象 理念,你看到的“变量”,其实是一个个由基础的 数据类型类 实例化而来的实例对象。我们使用 type() 函数查看变量的数据类型,实际上就是在查看它所归属的 

同样的道理,我们也可以用 type() 函数查看变量 kakarot 的类型。

比如:

# 选手类
class Player:
  arm_num = 2 # 两只胳膊
  leg_num = 2 # 两条腿
  eye_num = 2 # 一双眼睛

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot
kakarot = Player()
# 打印 kakarot 变量的数据类型
print(type(kakarot))

# 输出结果为:
# <class '__main__.Player'>

这里的 __main__ 就是我们之前学习过的 程序入口,即我们刚刚运行的 main.py 文件。Player 是我们在文件中定义的类,__main__ 和 Player 中间的 . 运算符,表明了它们之间的从属关系,即变量 kakarot 属于我们在当前文件所定义的 Player 类型。

我们不是第一次见到 . 运算符啦。开动脑筋想一想,我们之前还在哪里遇到了它呢?

😉 没错!我们想调用列表、字符串或字典的特殊方法时,变量名与方法名之间要用 . 连接;使用某个模块中的变量或函数时,模块名和变量名/函数名之间,也要用 . 连接。

# 调用方法时,变量名和方法名之间用 . 连接
months = ['一月', '二月']
months.append('三月')

# 使用模块中某个变量或函数时,也要用 . 连接
import random
number = random.randint(1, 100)

放到类中也是一样的道理。当我们想要访问 对象的属性 时,需要用 . 运算符连接对象名和属性名,就像这样:

# 选手类
class Player:
  arm_num = 2 # 两只胳膊
  leg_num = 2 # 两条腿
  eye_num = 2 # 一双眼睛

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot
kakarot = Player()
print('卡卡罗特有 {} 只胳膊'.format(kakarot.arm_num))
# 输出:卡卡罗特有 2 只胳膊

注意看代码第 8 行,我们通过 kakarot.arm_num 访问到了 kakarot 的 arm_num 属性。这种用 . 运算符访问数据的方式,更贴合我们的认知方式。因此用面向对象思想编写的代码,也更容易阅读。

属性定义与修改

看着我们创建好的 Player 类,天津饭(Tien Shinhan)选手提出异议:“我是三目人与地球人后裔,天生三目,怎么能用‘一双眼睛’概括我的特征呢?不行,你得给我把我的第三只眼加上!”

面对这样的 特例,我们可以为天津饭选手单独修改 eye_num 属性:

# 类的定义与实例化代码省略
# 实例化 Player 类,生成一个新的实例对象,赋值给变量 tien
tien = Player()
# 将他的眼睛数量修改为三只
tien.eye_num = 3

print('天津饭选手有 {} 只眼睛'.format(tien.eye_num))
# 输出:天津饭选手有 3 只眼睛

有的同学可能会好奇:既然对象是由类实例化而来,那我们改变对象的属性后,其它由同一类实例化而来的对象属性,是否也跟着发生了变化呢?

编程练习

请按照注释提示,将天津饭的眼睛数量修改为 3 只;接着创建一个新的 Player 类对象,看看他有几只眼睛。

# 选手类
class Player:
  arm_num = 2 # 两只胳膊
  leg_num = 2 # 两条腿
  eye_num = 2 # 一双眼睛

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 tien
tien = Player()
# 将他的眼睛数量修改为三只


# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot

# 输出他的眼睛数量

优化后的代码为:

# 选手类
class Player:
  arm_num = 2 # 两只胳膊
  leg_num = 2 # 两条腿
  eye_num = 2 # 一双眼睛

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 tien
tien = Player()
# 将他的眼睛数量修改为三只
tien.eye_num = 3
print('天津饭的眼睛数量为{}只'.format(tien.eye_num))
# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot
kakarot = Player()
# 输出他的眼睛数量
print('卡卡罗特的眼睛数量为{}只'.format(kakarot.eye_num))

# 输出结果为:

# 天津饭的眼睛数量为3只

# 卡卡罗特的眼睛数量为2只

看来我们躲过了跟天津饭一样生出第三只眼睛的命运。不过这背后是怎么一回事呢?大家都是由 Player 类实例化而来的对象,为什么彼此之间不会干扰呢?

前面我们提到过,类是抽象的模板,对象可以看作生产出的一个个零件。零件与零件是 相互独立 的,我们加工 tien“零件”,自然不会影响到 kakarot“零件”,更不会对模板本身,即 Player 类本身产生影响。

了解了如何为类添加属性,我们再来看看如何为类添加 方法

方法定义与调用

类的 方法 用来描述对象的 行为。例如,每位选手都需要登记信息,那么我们可以定义一个 sign_up() 方法,帮指定选手登记姓名、生命值和攻击力信息。

拓展:sign up 意为注册、登记;与之对应的,sign in 表示登录、签到。

# 选手类
class Player:
  # 帮选手登记信息
  def sign_up(self, name, HP, ATK): # 这里的 self 指的是需要登记信息的选手
    self.name = name  # 为选手登记姓名
    self.HP = HP      # 为选手登记生命值
    self.ATK = ATK    # 为选手登记攻击力
    print('已成功登记信息')
    print('+ {}\tHP: {}\tATK: {}'.format(name, HP, ATK))

啊哈,原来所谓 方法,就是定义在类中的 函数。调用方法的方式和访问、修改对象属性类似,依然通过 . 运算符完成:

kakarot = Player()
Player.sign_up(kakarot, '卡卡罗特', 100 ,25)
# 输出:
# 已成功登记信息
# + 卡卡罗特    HP: 100 ATK: 25

上面代码第 1 行,我们实例化了 Player 类,并将新生成的实例对象赋值给了 kakarot。接着在第 2 行,Player 类调用自己的 sign_up() 方法,传入了 kakarot 等参数,为卡卡罗特登记信息。

这是比较符合 面向过程编程 思维方式的编写形式。实际上,有了 类与对象 的加持,我们可以写成这样:

kakarot = Player()
kakarot.sign_up('卡卡罗特', 100 ,25)
# 输出:
# 已成功登记信息
# + 卡卡罗特    HP: 100 ATK: 25

注意看第 2 行代码,此时是 kakarot 实例对象在调用 Player() 类的 sign_up() 方法。它与之前我们编写的 Player.sign_up(kakarot, '卡卡罗特', 100 ,25) 是等价的:

self 参数含义

还真说对了。当 实例对象调用方法 时,Python 会 强制性 地将 实例对象本身 传递给类方法的 第一个参数

因此,虽然我们在调用方法时写的是 kakarot.sign_up('卡卡罗特', 100 ,25),实际上对 Python 而言,它运行的是 Player.sign_up(kakarot, '卡卡罗特', 100 ,25)

这样一来,一是简化了代码,二是更能看清楚是什么对象产生了什么样的行为。就像老师在上一关末尾为你展示的代码结构一样:

kakarot = Player('卡卡罗特', 100, 25)
piccolo = Player('比克大魔王', 150, 15)

kakarot.hit(piccolo)

即使我们不清楚 hit() 方法内部细节,但我们一眼就能看出 kakarot.hit(piccolo) 的含义是 卡卡罗特打了比克大魔王一拳。这其中体现了  的 封装 特征。

除了 封装,类还有 继承 与 多态 两大特征,这些内容我们会在下一章深入了解。

当对象调用类方法时,Python 会自动地把对象自身传递给 self 参数,因此我们只需要传入 self 后面的参数即可。A 选项多传递了 kuririn 对象本身,错误;D 选项是 kakarot 在调用方法,因此会为 kakarot 登记信息,错误。

而如果是类调用方法,则需要在参数列表中写明,究竟是什么对象在发生这一行为,因此要写成 Player.sign_up(kuririn, '克林', 85, 12),B 选项错误。

敲黑板划重点啦。Python 是 强制性 地将 实例对象本身 传递给类方法的 第一个参数,而不是强制性传递给名为 self 的参数。假如我们在定义 sign_up() 方法时,人为地把 self 参数放到参数列表后面位置:

# 选手类
class Player:
  # 人为地把 self 参数放到参数列表后面位置
  def sign_up(name, HP, ATK, self):
    self.name = name  # 为选手登记姓名
    self.HP = HP      # 为选手登记生命值
    self.ATK = ATK    # 为选手登记攻击力

kakarot = Player()
kakarot.sign_up('卡卡罗特', 100 ,25)

当 Python 执行到最后一行时,它依然会把 kakarot 传递给参数列表中第一个参数,也就是 name 参数,接着把 '卡卡罗特' 传递给 HP 参数……一切都乱套啦!

所以我们在定义类方法时,一定要把 self 参数放在参数列表第一项 哦。

明白如何编写方法后,我们来尝试将上一关编写的 hit() 函数改写成 Player 类的 hit() 方法吧。

通过之前分析我们知道,攻击 行为是由一个 Player 类对象发出的,会打在另一个 Player 对象身上。因此 hit() 方法需要携带两个参数,第一个参数自然是表示自身的 self;第二个是他攻击的对象 target

# 选手类
class Player:
  # 帮选手登记信息
  def sign_up(self, name, HP, ATK):
    pass

  # 发动攻击
  def hit(self, target):
    pass

这一行为具体是怎么一回事呢?这中间有两个环节,一是攻击者放狠话,二是被攻击的对象调用自己的 防御 能力,抵御受到的伤害。我们将 hit() 方法补充完成,再把 防御 行为抽象成 defend() 方法,此时的类结构是这样的: 

# 选手类
class Player:
  # 帮选手登记信息
  def sign_up(self, name, HP, ATK):
    pass

  # 发动攻击
  def hit(self, target):
    print('>> 【{}】向【{}】发动攻击,\n'.format(self.name, target.name))
    target.defend(self.ATK)
  
  # 防御攻击
  def defend(self, damage):
    pass

接下来,我们只要再补充完成 defend() 方法,就能完成整个 Player 类的设计了。这一步就交给你来完成吧。

编程练习

请按照注释提示,将 defend() 函数改写为 Player 类的方法,并在程序最后一行让卡卡罗特向比克大魔王发动一次攻击。

代码提示

  1. 类方法归属类管辖,需要添加一层缩进;
  2. defend() 方法除 self 外还应接受一个参数,表示受到了多少伤害。

注意事项

访问、修改实例对象的属性时,需要用 . 运算符。

操作提示:在代码编辑区域选中若干行,按下键盘上的 tab 键,即可快速为这些行添加一层缩进;若选中若干行后按下 shift + tab 键,则能为这些行减少一层缩进。不妨自己试试吧~

在此代码中进行优化:

from random import randint

# 选手类
class Player:
  # 帮选手登记信息
  def sign_up(self, name, HP, ATK):
    self.name = name  # 为选手登记姓名
    self.HP = HP      # 为选手登记生命值
    self.ATK = ATK    # 为选手登记攻击力
    print('已成功登记信息')
    print('+ {}\tHP: {}\tATK: {}\n'.format(name, HP, ATK))

  # 发动攻击
  def hit(self, target):
    print('>> 【{}】向【{}】发动攻击,\n'.format(self.name, target.name))
    target.defend(self.ATK)

# 请将下面 defend() 函数改写为 Player 类的方法
def defend(defender, damage):
  # 若生成的随机数小于等于 20,则闪避成功
  if randint(0, 100) <= 20:
    print('>> 【{}】完美躲避了攻击!\n'.format(defender['name']))
  # 否则扣除对应生命值
  else:
    defender['HP'] = defender['HP'] - damage
    print('>> 【{}】受到 {} 点伤害...\n'.format(defender['name'], damage))

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot
kakarot = Player()
# 为卡卡罗特登记信息
kakarot.sign_up('卡卡罗特', 100 ,25)

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 piccolo
piccolo = Player()
# 为比克大魔王登记信息
piccolo.sign_up('比克大魔王', 150, 15)

# 让卡卡罗特向比克大魔王发起攻击

优化后的代码为:

from random import randint

# 选手类
class Player:
  # 帮选手登记信息
  def sign_up(self, name, HP, ATK):
    self.name = name  # 为选手登记姓名
    self.HP = HP      # 为选手登记生命值
    self.ATK = ATK    # 为选手登记攻击力
    print('已成功登记信息')
    print('+ {}\tHP: {}\tATK: {}\n'.format(name, HP, ATK))

  # 发动攻击
  def hit(self, target):
    print('>> 【{}】向【{}】发动攻击,\n'.format(self.name, target.name))
    target.defend(self.ATK)

# 请将下面 defend() 函数改写为 Player 类的方法
  def defend(defender, damage):
    # 若生成的随机数小于等于 20,则闪避成功
    if randint(0, 100) <= 20:
      print('>> 【{}】完美躲避了攻击!\n'.format(defender.name))
    # 否则扣除对应生命值
    else:
      print(defender.name)
      defender.HP = defender.HP - damage
      print('>> 【{}】受到 {} 点伤害...\n'.format(defender.name, damage))

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot
kakarot = Player()
# 为卡卡罗特登记信息
kakarot.sign_up('卡卡罗特', 100 ,25)

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 piccolo
piccolo = Player()
# 为比克大魔王登记信息
piccolo.sign_up('比克大魔王', 150, 15)

# 让卡卡罗特向比克大魔王发起攻击
Player.hit(kakarot, piccolo)

# 输出结果为:

# 已成功登记信息

# + 卡卡罗特 HP: 100 ATK: 25

# 已成功登记信息

# + 比克大魔王 HP: 150 ATK: 15

# >> 【卡卡罗特】向【比克大魔王】发动攻击,

# >> 【比克大魔王】完美躲避了攻击!

不知道你发现没有,虽然 sign_up() 和 hit()defend() 都是 Player 类的方法,但它们的使用频率和时机是不一样的:sign_up() 方法只有创建完实例对象后使用了一次,并且,如果不事先为选手登记信息,后续的攻击、防御过程都是没有意义的。🤔️ 那我们可不可以把这种 一实例化就必须要做的行为 与类实例化过程绑定在一起呢?

当然可以啦~我们用 __init__() 方法实现这一点。

__init__() 方法

__init__() 方法和程序入口 __main__ 一样,都以两个下划线 __ 开头、两个下划线结束。这意味着 __init__ 也是 Python 中内置的一个名称,有着特殊的功能。

init 的全称是 initialize(初始化),会在我们实例化新的对象时 自动调用

比如说我们希望在创建 Player 类对象时,自动地为这名新选手登记信息,那么可以把原来 sign_up() 方法中的内容挪到 __init__() 方法中:

# 选手类
class Player:
  # 每当创建新对象,都自动为他登记信息
  def __init__(self, name, HP, ATK):
    self.name = name  # 为选手登记姓名
    self.HP = HP      # 为选手登记生命值
    self.ATK = ATK    # 为选手登记攻击力
    print('已成功登记信息')
    print('+ {}\tHP: {}\tATK: {}\n'.format(name, HP, ATK))

此时我们再创建 Player 类的对象,需要在 Player() 的圆括号中传入姓名、生命值、攻击力三样信息,就像这样:

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot
kakarot = Player('卡卡罗特', 100, 25)
# 输出:
# 已成功登记信息
# + 卡卡罗特    HP: 100 ATK: 25

Python 执行到 kakarot = Player('卡卡罗特', 100, 25) 时,实际上完成了两个步骤:

  1. 实例化 Player 类,生成一个新的实例,将其赋值给 kakarot
  2. kakarot 自动地调用 __init__() 方法,为自己的 name 等属性赋值。

注意,一旦我们在类中编写了 __init__() 方法,那么在创建实例时 必须要传入对应的参数。假如我们没有传入对应参数,那么 Python 会报错喔:

# 未传入参数
kakarot = Player()
# 输出:
# TypeError: __init__() missing 3 required arguments: 'name', 'HP', 'ATK'
# 类型错误:__init__() 需要 3 个参数:'name','HP' 和 'ATK'

单选题我们将 sign_up() 方法改为 __init__() 方法后,以下哪项可以为 克林 选手(kuririn 对象)登记信息呢?

克林信息:生命值 85,攻击力 12。

A:kuririn = Player.__init__(kuririn, '克林', 85, 12)

B:kuririn = Player('克林', 85, 12)

对于 A 选项来说,Python 会先执行 Player.__init__(kuririn, '克林', 85, 12),而此时我们还没有定义 kuririn 变量,自然无法将它作为参数传递给 __init__() 方法,所以 A 选项错误。

对于 B 选项来说,Python 会先执行 Player('克林', 85, 12)。这其中发生了两个步骤,第一个步骤是实例化 Player 类,生成实例对象,将其赋值给 kuririn;第二步是 kuririn 自动地调用 __init__() 方法,为 name 等属性赋值。所以 B 选项是正确的。

如果还不清楚的话,可以再反复读几遍,或者自己编写代码做做实验哦。

咳咳,编写了 __init__() 方法后我们再实例化 Player 类时,看起来很像在调用一个名为 Player 的函数。为避免引发歧义,凸显出类是一个整体性的概念,Python 官方建议为类取名时 首字母大写,以便和函数名区分开来。如果所要描述的类不能用单个单词表达,则每个单词的首字母都需要大写,比如 FilmSelector。同学们在自己编写类时也要注意这一点哦。

好了,本章学习就到这里啦,让我们用一张图总结一下类与对象的语法知识吧:

课后练习:卡卡罗特VS比克大魔王

请你根据注释中提示信息,结合本关所学知识,用 面向对象编程 方法编写完成 Player 类,并让卡卡罗特和比克大魔王展开决斗吧~

卡卡罗特(kakarot)

  • 姓名(name):卡卡罗特
  • 生命值(HP):100
  • 攻击力(ATK,attack):25

比克大魔王(piccolo)

  • 姓名(name):比克大魔王
  • 生命值(HP):150
  • 攻击力(ATK,attack):20

决斗说明

  1. 决斗采取 回合制,由卡卡罗特先发动攻击;
  2. 防御时有 20% 几率防御成功,完全闪避攻击,免受伤害。

请在下方代码中,实现对应功能:

from random import randint

# 选手类
class Player:
  # 每当创建新对象,都自动为他登记信息
  def __init__(self, name, HP, ATK):
    self.name = name  # 为选手登记姓名
    self.HP = HP      # 为选手登记生命值
    self.ATK = ATK    # 为选手登记攻击力
    print('已成功登记信息')
    self.show_info()
  
  # 展示信息
  def show_info(self):
    print('+ {}\tHP: {}\tATK: {}\n'.format(self.name, self.HP, self.ATK))
  
  # 发动攻击


  # 防御攻击


# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 piccolo

# 展开决斗
while True:
  # 每回合开始,由卡卡罗特先发动攻击
  
  # 判断此时决斗是否分出胜负
  
  # 若未分出胜负,则攻防交换,由比克大魔王发动攻击
  
  # 判断此时决斗是否分出胜负
  
  # 每回合结束,打印出两人当前信息

实现后的代码为:

from random import randint

# 选手类
class Player:
  # 每当创建新对象,都自动为他登记信息
  def __init__(self, name, HP, ATK):
    self.name = name  # 为选手登记姓名
    self.HP = HP      # 为选手登记生命值
    self.ATK = ATK    # 为选手登记攻击力
    print('已成功登记信息')
    self.show_info()
  
  # 展示信息
  def show_info(self):
    print('+ {}\tHP: {}\tATK: {}\n'.format(self.name, self.HP, self.ATK))

  # 发动攻击
  def hit(attack, defender):
    print('-' * 6,attack.name,'发起攻击','-' * 6)
    defender.dfAttack(attack.ATK)

  # 防御攻击
  def dfAttack(defender, atk):
    if randint(1,100) <= 20:
      # 防御成功
      print(defender.name,'防御成功')
    else:
      # 防御失败
      defender.HP -= atk
      print(defender.name,'防御失败,气血-',atk)
    Player.show_info(defender)
# 实例化 Player 类,生成一个新的实例对象,赋值给变量 kakarot
kakarot = Player('卡卡罗特', 100, 25)

# 实例化 Player 类,生成一个新的实例对象,赋值给变量 piccolo
piccolo = Player('比克大魔王', 150, 20)

# 展开决斗
while True:
  # 每回合开始,由卡卡罗特先发动攻击
  Player.hit(kakarot, piccolo)
  # 判断此时决斗是否分出胜负
  if piccolo.HP <= 0:
    print(kakarot.name,'胜出')
    break
  # 若未分出胜负,则攻防交换,由比克大魔王发动攻击
  else:
    Player.hit(piccolo, kakarot)
    if kakarot.HP <= 0:
      # 判断此时决斗是否分出胜负
      print(piccolo.name,'胜出')
      break
  
  # 每回合结束,打印出两人当前信息
  # Player.show_info(kakarot)
  # Player.show_info(piccolo)
  print('-' * 32 + '\n')

课后练习:空调说明书

空调说明书

夏天怎么能没有空调呢!小贝舒舒服服地吹着凉风,她可太喜欢家里的空调了。

下面是小贝家卧室空调的说明书:

品牌:四季

额定电压:220V

额定制冷功率:1000W

额定制热功率:1350W

AC 是空调 (air conditioner) 的英文缩写,请你补全代码中标有 ??? 的地方,为 AC 类添加一个初始化方法,让 intro 方法执行时,能够打印出上面的空调说明书。

# brand 品牌,voltage 电压,cold 制冷,hot 制热
class AC:
  def __init__(???):
    ???

  def intro(self):
    print('''品牌:{}
额定电压:{}
额定制冷功率:{}
额定制热功率:{}'''.format(???))

air_bedroom = AC(???)
air_bedroom.intro()

优化后可得:

# brand 品牌,voltage 电压,cold 制冷,hot 制热
class AC:
  def __init__(self,brand, voltage, cold, hot):
    self.brand = brand
    self.voltage = voltage
    self.cold = cold
    self.hot = hot

  def intro(self):
    print('''品牌:{}
额定电压:{}
额定制冷功率:{}
额定制热功率:{}'''.format(self.brand, self.voltage, self.cold, self.hot))

air_bedroom = AC('四季','220V','1000W','1350W')
air_bedroom.intro()

# 输出结果为:
# 品牌:四季
# 额定电压:220V
# 额定制冷功率:1000W
# 额定制热功率:1350W

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

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

相关文章

基于STM32开发的智能水族箱管理系统

目录 引言环境准备智能水族箱管理系统基础代码实现&#xff1a;实现智能水族箱管理系统 4.1 温度传感器数据读取4.2 水泵与加热器控制4.3 水位传感器数据读取4.4 用户界面与显示应用场景&#xff1a;水族箱管理与优化问题解决方案与优化收尾与总结 1. 引言 智能水族箱管理系…

从0到1实现一个自己的大模型,实践中了解模型流程细节

前言 最近看了很多大模型&#xff0c;也使用了很多大模型。对于大模型理论似乎很了解&#xff0c;但是好像又缺点什么&#xff0c;思来想去决定自己动手实现一个 toy 级别的模型&#xff0c;在实践中加深对大语言模型的理解。 在这个系列的文章中&#xff0c;我将通过亲手实践…

【OPENMV】学习记录 (持续更新)

一、基础知识 1 设置彩色&#xff0f;黑白&#xff1a; sensor.set_pixformat() 设置像素模式。 sensor.GRAYSCALE: 灰度&#xff0c;每个像素8bit。sensor.RGB565: 彩色&#xff0c;每个像素16bit。 2 设置图像大小&#xff1a; sensor.set_framesize() 设置图像的大小 sen…

day51 动态规划 121. 买卖股票的最佳时机 122.买卖股票的最佳时机II

121. 买卖股票的最佳时机 动态规划 1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义 dp[i][0] 表示第i天持有股票所得最多现金 &#xff0c;这里可能有同学疑惑&#xff0c;本题中只能买卖一次&#xff0c;持有股票之后哪还有现金呢&#xff1f; 其实一开始现…

解密智慧校园解决方案:赋能数字化教育的未来

在当今数字化时代&#xff0c;智慧校园解决方案正以惊人的速度改变着教育界的面貌。随着科技的快速发展&#xff0c;数字化教育已经逐渐成为现代教育的核心。智慧校园解决方案作为一个集技术、教育和创新于一体的综合性项目&#xff0c;为学校提供了许多机遇和挑战。本文将揭示…

期望18K,4年前端Cvte 视源股份一面挂

一面 1、自我介绍&#xff1f;毕业的时候一直在 xx 公司&#xff0c;你基本都在做什么项目&#xff1f; 2、你讲一下你主要负责哪一块的&#xff1f;balabala 3、你们的 json 是怎么定义组件间的联动的&#xff1f; 4、怎么确定区分两个 input&#xff1f; 5、你们是怎么触…

Vue3入门 - vue3相比于vue2的优点,及如何创建Vue3项目

目录 一、认识Vue3 1. Vue2 选项式 API vs Vue3 组合式API 2. Vue3的优势 二、使用create-vue搭建Vue3项目 1. 认识create-vue 2. 使用create-vue创建项目 3.熟悉项目和关键文件 一、认识Vue3 1. Vue2 选项式 API vs Vue3 组合式API <script>export default …

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《考虑发用电相似性的海上风电中长期双边协商交易优化决策模型》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

【CV算法工程师必看】作为一个图像算法工程师,需要会什么,要学哪些技术栈?

作为一个图像算法工程师,除了基本的编程技能和理论知识,还需要掌握一系列的技术栈。以下是详细的技能和技术栈分类: 编程语言 Python: 主要用于快速开发和原型设计。常用库:OpenCV、Pillow、NumPy、SciPy、Scikit-image、TensorFlow、PyTorch。C++: 高性能要求的项目中广…

使用Ollama+OpenWebUI部署和使用Phi-3微软AI大模型完整指南

&#x1f3e1;作者主页&#xff1a; 点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月6日23点50分 &#x1f004;️文章质量&#xff1a;96分 欢迎来到Phi-3模型的奇妙世界&#xff01;Phi-3是由微软…

零售数据分析围绕人、货、场分析了什么?

零售数据分析围绕人、货、场分析了什么&#xff1f;通过全面分析零售业务中的人、货、场三个核心要素&#xff0c;为零售商提供深入的市场洞察和业务优化建议。通过对客流量、商品销售、场地布局等数据的收集和数据可视化分析&#xff0c;帮助企业更好地了解消费者行为、商品销…

一起学大模型 - 一起动笔练习prompt的用法

文章目录 前言一、代码演示二、代码解析1. 导入所需的库和模块&#xff1a;2. 设置日志记录和初始化模型&#xff1a;3. 定义一个函数用于清理GPU内存&#xff1a;4. 定义一个继承自LLM基类的QianWenChatLLM类&#xff0c;并实现对话生成的逻辑&#xff1a;5. 示例代码的主体部…

【Linux】ip命令详解

Linux网络排查 目录 一、ip命令介绍 1.1 ip命令简介 1.2 ip命令的由来 二、ip命令使用帮助 2.1 ip命令的help帮助信息 2.2 ip命令对象介绍 2.3 ip命令选项介绍 三、查看网络信息 3.1 显示当前网络接口信息 3.2 显示网络设备运行状态 3.3 显示详细设备信息 3.4 查看…

【多模态】37、TextSquare | 借助 Gemini-Pro 通过四个步骤来生成高质量的文本问答数据

文章目录 一、背景二、方法2.1 Square-10M2.2 模型结构2.3 使用 Square-10M 进行有监督微调 三、效果3.1 实验设置3.2 Benchmark 测评 论文&#xff1a;TextSquare: Scaling up Text-Centric Visual Instruction Tuning 代码&#xff1a;暂无 出处&#xff1a;字节 | 华中科技…

大学生必备搜题神器app?分享3个软件和公众号,来对比看看吧 #媒体#其他#笔记

以下分享的软件提供了各种实用的功能&#xff0c;如数学公式计算、语文阅读辅助等&#xff0c;让大学生们在学习过程中更加高效和便利。 1.同声传译王 譬如我们在搜题或是浏览一些资料文档到时候&#xff0c;经常会访问到一些外文网页或文档&#xff0c;所以一款趁手的翻译工…

2024年四川省国家大学科技园申报条件对象和支持政策

2024年四川省国家大学科技园申报对象 国家大学科技园是以具有较强科研实力的大学为依托&#xff0c;将大学的综合智力资源优势与其它社会优势资源相结合&#xff0c;为高等学校&#xff08;以下简称为高校&#xff09;科技成果转化、高新技术企业孵化、创新创业人才培养、产学…

白酒:茅台镇白酒的丰富历史与文化传承

茅台镇&#xff0c;位于中国贵州省仁怀市&#xff0c;是中国著名的白酒产区&#xff0c;以其得天独厚的自然环境和与众不同的酿造工艺而闻名于世。作为茅台镇的白酒品牌之一&#xff0c;云仓酒庄豪迈白酒承载着丰富的历史与文化传承。 茅台镇的历史可以追溯到汉代&#xff0c;当…

@vue-office/excel 解决移动端预览excel文件触发软键盘

先直接上代码 不耽误大家时间 标明下插件库 非常感谢作者提供预览插件 vue-office/excel 只需要控制CSS :deep(.x-spreadsheet-overlayer) {.x-spreadsheet-selectors {display: none !important;} } :deep(.x-spreadsheet-bottombar) {li.active {user-select: none !import…

visual studio打包qt算子时,只生成dll没有生成lib等文件

问题&#xff1a;在visual studio配置了qt项目&#xff0c;并打包成dll&#xff0c;原则上会生成一堆文件&#xff0c;包括dll,lib等文件。 解决办法&#xff1a; 挨个右击源代码的所有头文件-》属性-》项类型。改成qt头文件形式&#xff0c;如下。

4秒惊艳!Stable Cascade AI绘画神器,设计师和普通用户的无限创意新选择

近日&#xff0c;一款AI绘画模型Stable Cascade发布。 只需输入一段描述文字&#xff0c;即可在4秒钟内获得令人惊艳的图像。 无论你是设计师、艺术家&#xff0c;还是普通用户&#xff0c;都能轻松上手&#xff0c;释放无限创意。 Stable Cascade不仅在使用上极具便捷性&am…