Python类与面向对象
类的创建和调用
类表示包含某些共同特点的一系列对象,Python 中的类需要我们自行创建。
类的属性的创建和变量的定义是一样的类的方法的创建和函数的定义也很类似。唯一不同的是:类的 方法中有个必须放在首位的参数 self,它表示创建的实例本身。下面创建一个类:
class Human:
arms = 2
legs = 2
hair = '各种颜色的头发'
def walk(self):
print('直立行走')
def speak(self):
print('说着各式各样的语言')
human = Human() 这一行代码非常关键,通过该类创建了一个类的实例对象,这个过程叫做 类的实例化。创建了一个可调用所属类的属性和方法的实例。
# 类的实例化
human = Human()
print(human.arms)
# 输出:2
print(human.legs)
# 输出:2
print(human.hair)
# 输出:各种颜色的头发
human.walk()
# 输出:直立行走
human.speak()
# 输出:说着各式各样的语言
当我们实例化后得到实例 human 后,实例 human 就拥有了 Human 类的所有属性和方法。然后我们通过实例名.属性名和实例名.方法名去调用它们。
self的作用
self 在英文中是“自己、自我”的意思在Python的类中self 表示创建的实例本身。
class Human:
arms = 2
legs = 2
hair = '各种颜色的头发'
def walk(self):
print('直立行走')
def speak(self):
print('说着各式各样的语言')
def intro(self):
# 类的方法里访问类的属性
print('人类有%d条胳膊%d条腿' % (arms, legs))
# 类的实例化
human = Human()
human.intro()
# 报错:NameError: name 'arms' is not defined on line 14
我们在类的方法里访问类的属性时发现出现了变量未定义的错误!说明我们在类的方法里直接访问类的属性是不行的,那要怎么办呢?访问类的属性要先实例化然后通过实例名.属性 进行访问。
但在类的方法内访问类的属性时,类的实例还没被创建。这时,self 就出现了,它的作用是代替实例本身,所以上面的例子正确的写法应该是这样:
class Human:
arms = 2
legs = 2
hair = '各种颜色的头发'
def walk(self):
print('直立行走')
def speak(self):
print('说着各式各样的语言')
def intro(self):
# 类的方法里访问类的变量
print('人类有%d条胳膊%d条腿' % (self.arms, self.legs))
# 类的实例化
human = Human()
human.intro()
# 输出:人类有2条胳膊2条腿
当执行 human.intro() 这行代码时Python 会将human作为第一个参数传递给 self,这样我们就不再需要传入第一个参数,并能通过 self访问类的属性和方法了。
创建类的方法,一定要把第一个参数留给 self。创建的时候需要写,调用的时候不需要!
初始化方法
在 Python 的类中,有一种特殊的方法——初始化方法。它的格式是 def init(self):
初始化方法的特殊之处是:每当进行类的实例化时,初始化方法会自动被执行。我们看个例子:
class Human:
def __init__(self):
print('Hi,我是 Human 类的初始化方法')
human = Human()
# 输出:Hi,我是 Human 类的初始化方法
我们可以看到,我们只是创建了实例,并没有调用 init 方法,它自己就自动执行了。利用这个特性,我们通常会在 初始化方法 里完成类属性初始值的设置。比如:
class Human:
def __init__(self):
# self.不能丢
self.arms = 2
self.legs = 2
self.hair = '各种颜色的头发'
def walk(self):
print('直立行走')
def speak(self):
print('说着各式各样的语言')
human = Human()
print(human.hair)
# 输出:各种颜色的头发
除了进行固定的初始值设置,初始化方法 可以接收其他参数,进行自定义的属性初始值设置。
class Human:
def __init__(self, name, arms, legs, hair):
# self.不能丢
self.name = name
self.arms = arms
self.legs = legs
self.hair = hair
xiaobei = Human('小贝', 2, 2, '粉红色的头发')
print(xiaobei.name)
# 输出:小贝
神奇方法
像 init 这样的方法在 Python 的类中被称为 神奇方法(或魔术方法),它们的特征是被 双下划线 所包裹。我们再介绍一个神奇方法——str()。
class Human:
arms = 2
legs = 2
hair = '各种颜色的头发'
def __str__(self):
return '人类有%d条胳膊%d条腿' % (self.arms, self.legs)
human = Human()
print(human)
# 输出:人类有2条胳膊2条腿
可以看到,有了 str() 方法,直接打印实例的结果为 str() 方法的返回值。因此,我们可以使用 str() 方法来描述一个类。
面向对象与面向过程
与 面向对象 相对应的是 面向过程,我们之前写代码都用的是 面向过程 的思维方式。也就是把一个问题拆分成一个个步骤,然后用函数实现各个步骤,依次调用解决问题。
而 面向对象 的思维方式是:把一个问题拆分成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为和特征(方法和属性)。
假设我们要处理学生的成绩,将学生的姓名和成绩打印出来,用 面向过程 的思维可以这样写:
# 1.用字典存储姓名和分数
student1 = {'name': '小贝', 'score': 98}
student2 = {'name': '闻闻', 'score': 93}
# 2.定义打印分数的函数
def print_score(student):
print('姓名:%s,分数:%d' % (student['name'], student['score']))
# 3.调用打印分数的函数
print_score(student1)
print_score(student2)
而用 面向对象 的思维(使用类)我们可以这样写:
# 1.定义学生类
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('姓名:%s,分数:%d' % (self.name, self.score))
# 2.实例化学生类
student1 = Student('小贝', 98)
student2 = Student('闻闻', 93)
# 3.调用实例的方法
student1.print_score()
student2.print_score()
我们可以看到,面向过程 是以动作(函数)为主体,对象(这个例子里是学生)作为参数传递给函数。而 面向对象 是以对象为主体,动作和特征分别是对象的方法和属性。用代码来描述就是,面向过程:动作(对象);面向对象:对象.动作()。
使用 面向对象 的思维方式的好处是:程序的可读性、可拓展性、可维护性高。但并不是说 面向过程 就一无是处了,二者相辅相成,并不是对立的,我们要根据实际情况选择合适的编程思维方式。
类的继承
继承指一个类获取父类里所有的属性和方法,举个例子:
class Human:
arms = 2
legs = 2
hair = '各种颜色的头发'
def walk(self):
print('直立行走')
def speak(self):
print('说着各式各样的语言')
class Chinese(Human):
pass
xiaobei = Chinese()
print(xiaobei.arms)
# 输出:2
xiaobei.walk()
# 输出:直立行走
我们可以看到,Chinese 类中什么都没有,实例化出来的 xiaobei 却拥有 Human 类的属性和方法。我们通过 class 子类名(父类名) 的方式实现了类的继承,让子类拥有了父类的所有属性和方法。
类的多层继承
继承不只是子类继承父类的属性和方法,当父类还有自己的父类时,子类也能继承父类的父类的属性和方法。我们来看个例子:
class Human:
arms = 2
legs = 2
hair = '各种颜色的头发'
def walk(self):
print('直立行走')
def speak(self):
print('说着各式各样的语言')
class Chinese(Human):
pass
class JiangSu(Chinese):
pass
xiaobei = JiangSu()
print(xiaobei.arms)
# 输出:2
xiaobei.walk()
# 输出:直立行走
我们可以看到,类的属性和方法会通过继承一直传递下去,减少了大量的重复代码,实现了代码的复用。
类的多重继承
除了继承一个类,我们还可以同时继承多个类,语法是 class 类 A(类 B, 类 C, 类 D):。我们以出生在美国的中国人(ABC,American Born Chinese)为例:
class Chinese:
hair = '黑头发'
skin = '黄皮肤'
def speak_chinese(self):
print('说汉语')
class American:
hair = '金头发'
skin = '白皮肤'
def speak_english(self):
print('说英语')
class ABC(Chinese, American):
pass
abc = ABC()
print(abc.hair)
# 输出:黑头发
print(abc.skin)
# 输出:黄皮肤
abc.speak_chinese()
# 输出:说汉语
abc.speak_english()
# 输出:说英语
我们可以看到,ABC 类继承了 Chinese 类和 American 类的所有属性和方法,但 hair 和 skin 这两个类都有的属性,ABC 类只继承了 Chinese 类的。
这是因为 class ABC(Chinese, American): 中 Chinese 靠前,调用实例的属性和方法时会优先在子类中查找,找不到再从最左侧的父类依次往右查找,直到找到为止,如果都没有找到则会报错。
类的定制
你可能会有疑问,既然子类继承父类的所有属性和方法,它俩是一样的,为什么不直接使用父类呢?因为类除了可以继承之外,还可以进行定制!我们可以在父类的基础上做以下两点定制:
- 创建新的属性和方法
- 修改继承自父类的属性和方法
我们来看个例子:
class Human:
arms = 2
legs = 2
hair = '各种颜色的头发'
def walk(self):
print('直立行走')
def speak(self):
print('说着各式各样的语言')
class Chinese(Human):
hair = '黑头发'
skin = '黄皮肤'
def speak(self):
print('说汉语')
def eat(self):
print('吃米饭')
xiaobei = Chinese()
print(xiaobei.arms)
# 输出:2
print(xiaobei.hair)
# 输出:黑头发
print(xiaobei.skin)
# 输出:黄皮肤
xiaobei.walk()
# 输出:直立行走
xiaobei.speak()
# 输出:说汉语
xiaobei.eat()
# 输出:吃米饭
我们可以看到,Chinese 类在 Human 类的基础上新增了 skin 属性和 eat() 方法,并且 Chinese 类自己的 hair 属性和 speak() 方法覆盖了 Human 类中对应的属性和方法。这就是类的定制的两个特点新增和重写。
新增指在继承父类的属性和方法的基础上添加自己独有的属性或方法。
重写指在继承父类的属性和方法的基础上重写父类的属性和方法。
编程练习
五年一届的“天下第一武道会”又开始了!接下来登场的选手是卡卡罗特和比克大魔王,他们的初始血量都为 100,轮番攻击,每次攻击打出 10-20 点伤害,对方有 20% 的几率防御成功不受伤害。
Player 类的主体已经完成,还差一个 defend()(防御) 方法,请你根据要求补全它并补全轮番攻击的代码。
要求:
- 当受到伤害时,将当前血量(health)减去伤害值(damage),并打印出 xxx 受到 xx 点伤害,血量剩余 xx;
- 有 20% 的几率防御成功不受伤害,血量不变并打印出 xxx 防御成功;
- 当受到伤害后,血量小于等于 0 时打印 xxx 被击败;
- 使用循环使两人轮番攻击,当其中一人血量小于等于 0 时,结束循环。
from random import randint, random
class Player:
def __init__(self, name):
self.name = name
self.health = 100
def attack(self, target):
print(self.name + '发起攻击')
damage = randint(10, 20)
target.defend(damage)
def defend(self, damage):
if random() > 0.2:
self.health -= damage
print('%s受到%d点伤害,血量剩余%d' % (self.name, damage, self.health))
if self.health <= 0:
print(self.name + '被击败')
else:
print(self.name + '防御成功')
print('----------------------')
kakarotto = Player('卡卡罗特')
piccolo = Player('比克大魔王')
while True:
if kakarotto.health > 0:
kakarotto.attack(piccolo)
else:
break
if piccolo.health > 0:
piccolo.attack(kakarotto)
else:
break
狼人之夜
朋友聚会都要做些什么?一起玩狼人杀是个不错的选择。按照最经典的角色配置,“狼人”每晚刀杀一个人,“人类”阵营白天投票选择一个人出局。“人类”阵营里,除了普通人类“村民”之外,还有预言家、女巫和猎人三种特殊角色,每个角色都有特殊技能。
下面我们就用代码来写一个简单的抽牌模拟器,与 Python 共度狼人之夜吧。
老师已经帮你把四种角色的卡牌图案和阵营图案存到 card.py 中了。请你完成下面这 4 步要求,打印出你抽到的卡牌。你能抽到哪张身份牌呢?
1.定义子类 Prophet(预言家)、Witch(女巫)、Hunter(猎人),让它们继承父类 Villager(村民)的 identity() 方法,identity() 方法可以打印出他们所在的阵营图案(card.py 中的 human_symbol);
2.为所有四个类分别定制 print_card() 方法,打印出各自的卡牌图案。在 card.py 中,预言家的图案是 prophet_symbol,猎人是 hunter_symbol,女巫是 witch_symbol,村民是 villager_symbol;
3.将四个类实例化,每个实例都代表着一张身份牌,将这些实例存到列表中;
4.借助 random 模块在列表中随机选择一个实例,并调用该实例的 identity() 方法和 print_card() 方法,打印出随机抽到的身份牌。
提示:你可以在第 3 步控制抽牌的概率,如 Prophet 类的实例是 p,Hunter 类的实例是 h,那在列表 [p, p, h, h, h] 中随机返回一个元素,取到 p 的概率就是 40%。
#card.py
human_symbol = '''
---------
人类阵营
---------'''
prophet_symbol = '''
------------------------------------
______ _ _
| ___ \ | | | |
| |_/ / __ ___ _ __ | |__ ___| |_
| __/ '__/ _ \| '_ \| '_ \ / _ \ __|
| | | | | (_) | |_) | | | | __/ |_
\_| |_| \___/| .__/|_| |_|\___|\__|
| |
|_|
--------------- 预言家 --------------
每晚可以查验一个人的身份
------------------------------------'''
witch_symbol = '''
--------------------------
_ _ _ _ _
| | | (_) | | |
| | | |_| |_ ___| |__
| |/\| | | __/ __| '_ \\
\ /\ / | || (__| | | |
\/ \/|_|\__\___|_| |_|
---------- 女巫 -----------
有一瓶毒药,有一瓶解药
--------------------------'''
hunter_symbol = '''
--------------------------------
_ _ _
| | | | | |
| |_| |_ _ _ __ | |_ ___ _ __
| _ | | | | '_ \| __/ _ \ '__|
| | | | |_| | | | | || __/ |
\_| |_/\__,_|_| |_|\__\___|_|
------------- 猎人 --------------
被票死或刀杀后可以开枪带走一个人
--------------------------------'''
villager_symbol = '''
-----------------------------------
_ _ _
(_) | |
__ ___| | | __ _ __ _ ___ _ __
\ \ / / | | |/ _` |/ _` |/ _ \ '__|
\ V /| | | | (_| | (_| | __/ |
\_/ |_|_|_|\__,_|\__, |\___|_|
__/ |
|___/
-------------- 村民 ----------------
白天投票选择一个人出局
#main.py
import random
import card
class Villager:
def identity(self):
print(card.human_symbol)
def print_card(self):
print(card.villager_symbol)
class Prophet(Villager):
def print_card(self):
print(card.prophet_symbol)
class Witch(Villager):
def print_card(self):
print(card.witch_symbol)
class Hunter(Villager):
def print_card(self):
print(card.hunter_symbol)
def choose_role(role):
role.identity()
role.print_card()
villager = Villager()
prophet = Prophet()
witch = Witch()
hunter = Hunter()
choose_role(random.choice([villager, villager, prophet, witch, hunter]))
# 我们还可以用 `random.choices()` 方法来控制抽牌概率,像下面这样:
# role = random.choices([villager, prophet, witch, hunter],weights=[2, 1, 1, 1],k=1)
# 其中,`weights` 代表权重,也就是在对应位置抽取元素的概率大小,数字大则概率高;
# `k` 代表要在这个随机位置抽几个元素,抽到的元素会存到一个列表中。
# 注意,`random.choices()` 方法返回的是一个列表,所以,调用 `choose_role()` 函数应该写为:
# choose_role(role[0])
全能的孙悟饭
悟饭是赛亚人卡卡罗特与地球人琪琪的儿子,既有赛亚人强大的战斗力,也有地球人聪明的头脑。
作为两个星球的人类的混血儿,他接受父亲的训练,可以变身为超级赛亚人,战斗力暴增 100 倍,击败强大的敌人;也不忘记妈妈的叮嘱,好好学习,最后成为了一名优秀的学者。
我们一起把悟饭的信息写到代码世界中吧。我们已经构造好了通用的 Human 类,代表所有人类,拥有一个初始化方法,设置人类的 姓名(name) 和 战斗力(power)。
要求:
1.定义 Saiyan(赛亚人)类,继承自 Human 类;
2.为 Saiyan 类创建新方法 super_saiyan()(变身超级赛亚人),作用是赋予实例一个新属性 super_power,值是 power 属性的 100 倍,并打印出 xx成为了超级赛亚人,战斗力从xx增长到xx。
3.定义 Earthling(地球人)类,继承自 Human 类;
4.为 Earthling 类创建新方法 study(),作用是打印出 xx经过努力学习,成为了一名优秀的学者;
5.定义 Hybrid(混血儿)类,多重继承自 Saiyan 类和 Earthling 类;
6.为 Hybrid 类创建实例 wufan(悟饭),姓名为 悟饭,战斗力是 100;
7.让悟饭调用 super_saiyan() 方法,变成超级赛亚人;
8.让悟饭调用 study() 方法,实现自己的学者梦。
class Human:
def __init__(self, name, power):
self.name = name
self.power = power
# 定义一个赛亚人类吧
class Saiyan(Human):
# 定义变身超级赛亚人的方法
def super_saiyan(self):
self.super_power = self.power * 100
print('{}成为了超级赛亚人,战斗力从{}增长到{}'.format(self.name, self.power, self.super_power))
# 定义一个地球人类吧
class Earthling(Human):
# 定义一个地球人好好学习的方法
def study(self):
print('{}经过努力学习,成为了一名优秀的学者'.format(self.name))
# 使用多重继承,定义地球人和赛亚人的混血儿类
class Hybrid(Saiyan, Earthling):
pass
# 实例化悟饭
wufan = Hybrid('悟饭', 100)
# 让悟饭变身超级赛亚人
wufan.super_saiyan()
# 让悟饭实现学者梦
wufan.study()