实例对象
上一节我们已经知道:
- 类是模板
- 对象是根据类创建出来的具体实例
如果要在游戏中分别创建黄忠和刘备,就不能只有一个“英雄模板”,还要根据模板创建两个保存不同数据的英雄对象。如何让每个对象拥有自己的数据,并让方法明确知道当前正在操作哪个对象?
这一节继续学习实例对象,重点解决三个问题:
- 对象如何通过类完成实例化
__init__()如何初始化实例属性self如何绑定当前实例
实例化过程
类与实例关系
Python 自带的数字、字符串、列表这些类型,适合表示简单数据。
如果要表示更复杂的事物,比如一个人、一只动物或一个游戏英雄,就更适合自己定义类。
比如动物:
- 有名字
- 有年龄
- 还能执行某些行为
这时就可以定义一个 Animal 类。
要注意:
类只是模板,不是具体对象。
类规定对象“应该有哪些属性和方法”,但它本身不是某一个具体事物。
先进一步明确类和对象的关系:
- 类:抽象模板
- 对象:具体实例
类里描述的是一类事物共同拥有的特征和行为。
- 特征,通常叫属性
- 行为,通常叫方法
可以直接这样记:
- 先有类
- 再根据类创建对象
- 一个类可以创建很多个对象
- 不同对象的数据可以不同
最小实例化示例
实例对象,就是通过类创建出来的具体对象。通过类创建对象的过程叫作实例化。
先看一个最简单的例子:
class Hero:
pass
hero1 = Hero()
hero2 = Hero()
print(hero1)
print(hero2)
print(hero1 is hero2)hero1 和 hero2 都是通过 Hero 类创建的实例对象。它们来自同一个类,但在内存中是两个不同的对象,所以最后输出 False。
初始化实例属性
__init__() 方法
当我们使用类名加小括号创建对象时,Python 会自动调用 __init__()。
它的主要作用是初始化新对象。
class Person:
def __init__(self):
print('正在初始化对象')
zx = Person()执行 zx = Person() 时,Python 会自动调用 __init__(),并把新对象传给参数 self。
初始数据传入
对象通常不只是创建出来就行,还要在创建时带上自己的数据。
这时候就要在 __init__() 里初始化属性。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
zx = Person('正心', 18)这段代码的意思是:
调用 Person('正心', 18) 时,'正心' 会传给 name,18 会传给 age。方法中的 self.name 和 self.age 是实例属性,分别保存到新对象 zx 中。
创建对象的基本格式是:
对象变量 = 类名(参数)self 由 Python 自动传入,不需要手动填写。除了 self,__init__() 定义了哪些必需参数,实例化时就要依次传入对应的值。
Person 实例属性与方法
下面用“人”这个例子,把对象创建过程串起来。
"""
定义一个人(Person)类
人在出生时就会有名字、年龄等特征
人还会有吃饭、喝水、睡觉等行为
"""
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print(f'{self.name} 正在吃饭')
def drink(self):
print(f'{self.name} 正在喝水')
def sleep(self):
print(f'{self.name} 正在睡觉')
zx = Person('正心', 18)
# 获取属性
print(zx.name)
print(zx.age)
# 在外部修改属性
zx.age = 19
print(zx.age)
# 在外部新增属性
zx.gender = '男'
print(zx.gender)
zx.eat()
zx.drink()
zx.sleep()运行结果:
正心
18
19
男
正心 正在吃饭
正心 正在喝水
正心 正在睡觉这个例子里要重点看四件事:
- 对象创建时就拥有
name和age属性 - 类外可以通过
对象名.属性名访问或修改属性 - Python 允许在类外给对象新增属性,但关键属性一般应在
__init__()中统一定义 eat()、drink()、sleep()是实例方法,表示对象可以执行的具体行为
实例属性的统一初始化
理论上,不在 __init__() 里赋值,也能先创建对象,再从外部补充属性。
例如:
class Person:
def hello(self):
print(f'我的名字是:{self.name},已经:{self.age} 岁了')然后这样使用:
>>> tom = Person()
>>> tom.name = '汤姆'
>>> tom.age = 18
>>> tom.hello()
我的名字是:汤姆,已经:18 岁了这种写法虽然可以运行,但一般不推荐。
因为无法从类的定义中直接看出对象需要哪些属性,调用者也容易遗漏。一般应在 __init__() 中统一定义对象需要的关键属性,这也更符合“显式优于隐式”的思路。
案例:英雄对象建模
需求:创建两个拥有独立属性的英雄对象,并让每个英雄能够执行攻击操作。
功能分析:先定义英雄的姓名、武器、装备和血量,再创建黄忠与刘备两个实例,观察实例属性互不影响。
创建一个游戏英雄类(Hero)
英雄包含以下属性
名字(name),武器(weapon),装备(equipment),血量(blood)
每个英雄都有以下技能(行为)
攻击(attack)
创建两个英雄
'黄忠', '弓箭', ['头盔', '靴子'], 100
'刘备', '剑', ['头盔', '盔甲'], 100参考代码
class Hero:
def __init__(self, name, weapon, equipment, blood):
self.name = name
self.weapon = weapon
self.equipment = equipment
self.blood = blood
def attack(self):
return f'{self.name} 发动了攻击'
hero1 = Hero('黄忠', '弓箭', ['头盔', '靴子'], 100)
hero2 = Hero('刘备', '剑', ['头盔', '盔甲'], 100)
print(hero1.name, hero1.weapon, hero1.equipment, hero1.attack())
print(hero1.equipment)
print(hero1.equipment[0])
print(hero1.equipment[1])
print(hero2.name, hero2.weapon, hero2.equipment, hero2.attack())
hero1.blood -= 20
print(hero1.blood)
print(hero2.blood)hero1 和 hero2 都拥有 name、weapon、equipment 和 blood 属性,但各自保存自己的数据。修改 hero1.blood 后,hero2.blood 仍然是 100。
实例方法与 self
self 与当前实例绑定
很多同学第一次学类时,最容易卡住的就是 self。
先记一句最关键的话:
self 表示当前调用这个方法的实例对象。参数名 self 是 Python 的编程惯例,不是关键字,但不要随意改成其他名字。
是谁调用方法,self 指的就是谁。
class Hero:
def __init__(self, name, weapon, equipment, blood):
self.name = name
self.weapon = weapon
self.equipment = equipment
self.blood = blood
def return_self(self):
return self
hero1 = Hero('黄忠', '弓箭', ['头盔', '靴子'], 100)
attr = hero1.return_self()
print(hero1 is attr)
hero2 = Hero('刘备', '剑', ['头盔', '盔甲'], 100)
attr2 = hero2.return_self()
print(hero2 is attr2)两次比较都会输出 True,说明:
hero1.return_self()返回的就是hero1hero2.return_self()返回的就是hero2
所以:
- 类外部用
对象名.访问属性和方法 - 类内部用
self.访问当前对象的属性和方法
实例方法参数传递
定义实例方法时,第一个参数通常写成 self。通过对象调用方法时,Python 会自动把当前对象传给这个参数。
除此之外,其他参数和普通函数一样正常定义、传递。
创建一个游戏英雄类
英雄包含以下属性
名字(name),武器(weapon),装备(equipment),血量(blood)
每个英雄都有以下技能
攻击(attack)与逃跑(run)
创建两个英雄
'黄忠', '弓箭', ['头盔', '靴子'], 100
'刘备', '剑', ['头盔', '盔甲'], 100
黄忠攻击刘备,刘备血量减少 10
刘备攻击黄忠,黄忠血量减少 10参考代码
class Hero:
def __init__(self, name, weapon, equipment, blood):
self.name = name
self.weapon = weapon
self.equipment = equipment
self.blood = blood
def attack(self, target=None):
if target is not None:
print(self.name, '攻击了', target.name)
target.blood -= 10
print(target.name, '血量还剩下', target.blood)
else:
print(self.name, '正在发动攻击')
def run(self):
print(self.name, '逃跑了')
def return_data(self):
return self.name, self.weapon, self.equipment, self.blood
hz = Hero('黄忠', '弓箭', ['头盔', '靴子'], 100)
lb = Hero('刘备', '剑', ['头盔', '盔甲'], 100)
print(lb.blood)
hz.attack(lb)
lb.run()
lb.attack(hz)
hz.run()
print('刘备血量:', lb.blood)
print('黄忠血量:', hz.blood)
print('黄忠信息:', hz.return_data())执行 hz.attack(lb) 时,Python 自动把 hz 传给 self,我们传入的 lb 则交给参数 target。因此,方法内部可以通过 self 读取攻击者,也可以通过 target 修改被攻击者的血量。
小结
本节重点记住四件事:
- 实例对象是根据类创建出来的具体对象
__init__()会在创建对象时自动执行self指当前调用方法的对象- 定义实例方法时,第一个参数通常写成
self
练习
- 写一个
Dog类,在__init__()中初始化名字和年龄 - 给
Dog类编写bark()方法,输出狗的名字和叫声 - 创建两个保存不同数据的对象,分别调用它们的
bark()方法