Skip to content

实例对象 ​

上一节我们已经知道:

  • 类是模板
  • 对象是根据类创建出来的具体实例

如果要在游戏中分别创建黄忠和刘备,就不能只有一个“英雄模板”,还要根据模板创建两个保存不同数据的英雄对象。如何让每个对象拥有自己的数据,并让方法明确知道当前正在操作哪个对象?

这一节继续学习实例对象,重点解决三个问题:

  1. 对象如何通过类完成实例化
  2. __init__() 如何初始化实例属性
  3. self 如何绑定当前实例

实例化过程 ​

类与实例关系

Python 自带的数字、字符串、列表这些类型,适合表示简单数据。

如果要表示更复杂的事物,比如一个人、一只动物或一个游戏英雄,就更适合自己定义类。

比如动物:

  • 有名字
  • 有年龄
  • 还能执行某些行为

这时就可以定义一个 Animal 类。

要注意:

类只是模板,不是具体对象。

类规定对象“应该有哪些属性和方法”,但它本身不是某一个具体事物。

先进一步明确类和对象的关系:

  • 类:抽象模板
  • 对象:具体实例

类里描述的是一类事物共同拥有的特征和行为。

  • 特征,通常叫属性
  • 行为,通常叫方法

可以直接这样记:

  1. 先有类
  2. 再根据类创建对象
  3. 一个类可以创建很多个对象
  4. 不同对象的数据可以不同

最小实例化示例

实例对象,就是通过类创建出来的具体对象。通过类创建对象的过程叫作实例化。

先看一个最简单的例子:

python
class Hero:
    pass


hero1 = Hero()
hero2 = Hero()

print(hero1)
print(hero2)
print(hero1 is hero2)

hero1 和 hero2 都是通过 Hero 类创建的实例对象。它们来自同一个类,但在内存中是两个不同的对象,所以最后输出 False。

最小实例化与内存对象分配

初始化实例属性 ​

__init__() 方法

当我们使用类名加小括号创建对象时,Python 会自动调用 __init__()。

它的主要作用是初始化新对象。

python
class Person:
    def __init__(self):
        print('正在初始化对象')


zx = Person()

执行 zx = Person() 时,Python 会自动调用 __init__(),并把新对象传给参数 self。

初始数据传入

对象通常不只是创建出来就行,还要在创建时带上自己的数据。

这时候就要在 __init__() 里初始化属性。

python
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 中。

创建对象的基本格式是:

text
对象变量 = 类名(参数)

self 由 Python 自动传入,不需要手动填写。除了 self,__init__() 定义了哪些必需参数,实例化时就要依次传入对应的值。

实例化与 init 初始化流程

Person 实例属性与方法 ​

下面用“人”这个例子,把对象创建过程串起来。

Person 类结构

python
"""
定义一个人(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()

运行结果:

text
正心
18
19
男
正心 正在吃饭
正心 正在喝水
正心 正在睡觉

这个例子里要重点看四件事:

  1. 对象创建时就拥有 name 和 age 属性
  2. 类外可以通过 对象名.属性名 访问或修改属性
  3. Python 允许在类外给对象新增属性,但关键属性一般应在 __init__() 中统一定义
  4. eat()、drink()、sleep() 是实例方法,表示对象可以执行的具体行为

实例属性的统一初始化 ​

理论上,不在 __init__() 里赋值,也能先创建对象,再从外部补充属性。

例如:

python
class Person:
    def hello(self):
        print(f'我的名字是:{self.name},已经:{self.age} 岁了')

然后这样使用:

python
>>> tom = Person()
>>> tom.name = '汤姆'
>>> tom.age = 18
>>> tom.hello()
我的名字是:汤姆,已经:18 岁了

这种写法虽然可以运行,但一般不推荐。

因为无法从类的定义中直接看出对象需要哪些属性,调用者也容易遗漏。一般应在 __init__() 中统一定义对象需要的关键属性,这也更符合“显式优于隐式”的思路。

规范初始化与类外随意赋值对比

案例:英雄对象建模 ​

需求:创建两个拥有独立属性的英雄对象,并让每个英雄能够执行攻击操作。

功能分析:先定义英雄的姓名、武器、装备和血量,再创建黄忠与刘备两个实例,观察实例属性互不影响。

text
创建一个游戏英雄类(Hero)

英雄包含以下属性
    名字(name),武器(weapon),装备(equipment),血量(blood)

每个英雄都有以下技能(行为)
    攻击(attack)

创建两个英雄
    '黄忠', '弓箭', ['头盔', '靴子'], 100
    '刘备', '剑', ['头盔', '盔甲'], 100
参考代码
python
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 指的就是谁。

python
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() 返回的就是 hero1
  • hero2.return_self() 返回的就是 hero2

所以:

  • 类外部用 对象名. 访问属性和方法
  • 类内部用 self. 访问当前对象的属性和方法

self 与当前调用实例的绑定关系

实例方法参数传递 ​

定义实例方法时,第一个参数通常写成 self。通过对象调用方法时,Python 会自动把当前对象传给这个参数。

除此之外,其他参数和普通函数一样正常定义、传递。

text
创建一个游戏英雄类

英雄包含以下属性
    名字(name),武器(weapon),装备(equipment),血量(blood)

每个英雄都有以下技能
    攻击(attack)与逃跑(run)

创建两个英雄
    '黄忠', '弓箭', ['头盔', '靴子'], 100
    '刘备', '剑', ['头盔', '盔甲'], 100

黄忠攻击刘备,刘备血量减少 10
刘备攻击黄忠,黄忠血量减少 10
参考代码
python
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 修改被攻击者的血量。

小结 ​

本节重点记住四件事:

  1. 实例对象是根据类创建出来的具体对象
  2. __init__() 会在创建对象时自动执行
  3. self 指当前调用方法的对象
  4. 定义实例方法时,第一个参数通常写成 self

练习 ​

  1. 写一个 Dog 类,在 __init__() 中初始化名字和年龄
  2. 给 Dog 类编写 bark() 方法,输出狗的名字和叫声
  3. 创建两个保存不同数据的对象,分别调用它们的 bark() 方法