🐸 青蛙编程网
首页 / Python / 面向对象 / 2. 属性与方法

属性与方法

这是什么:属性 = 挂在对象身上的数据,方法 = 挂在对象身上的能力。

为什么用:上一章的狗只会"汪汪",连名字都没有。没有属性,造出来的狗全都一个样;没有方法,行为只能写成外面的函数,谁是谁的说不清。

如何用:本章先分清实例属性和类属性,再用 __init__ 在造狗的一瞬间把名字、年龄装进去。

两种属性

  • 实例属性:每只狗各存一份,如名字、年龄
  • 类属性:全体狗共享一份,写在类里、方法外,如"犬科"这个分类

init:出生时自动执行

__init__ 前后各两条下划线,是初始化方法:Dog() 造狗的一瞬间自动执行,造狗时传的参数就交给它。

class Dog:
    kind = "犬科"                 # 类属性:直接写在类里

    def __init__(self, name, age):
        self.name = name          # 实例属性:挂在 self 身上
        self.age = age

    def introduce(self):          # 实例方法
        print(f"我叫{self.name},{self.age}岁,是{self.kind}")


d1 = Dog("旺财", 3)   # 不用手动调 __init__,直接传参
d2 = Dog("小黑", 1)

d1.introduce()
d2.introduce()
print("kind 是共享的:", d1.kind is d2.kind)
我叫旺财,3岁,是犬科
我叫小黑,1岁,是犬科
kind 是共享的: True
代码 作用
__init__(self, name, age) 初始化方法,实例化时自动执行,负责收参数
self 代表"当前这只狗",必须是方法的第一个参数
self.name = name 把传入的名字挂到这只狗身上,成为实例属性
kind = "犬科" 类属性,所有实例读到的都是同一份
self.kind 方法里读类属性,就像读自己的属性一样

self 的直觉

d1.introduce() 读作"让 d1 自己执行 introduce"——self 就是 d1。于是方法里的 self.name 就是"这只狗的名字"。谁调用方法,self 就是谁。

记忆口诀:self 是"我自己";挂 self 的是私产,直接写类里的是公摊;init 出生自动跑。

❓ 常见问题

Q1:报 TypeError: Dog.__init__() missing 2 required positional arguments: 'name' and 'age'?

原因:__init__ 声明了要收 name 和 age 两件行李,实例化时却写了 Dog() 空着手来。Python 按清单收货,缺件就拒收。 修复三步:① 看报错里 missing 的参数名有哪些;② 找到 Dog() 实例化那一行;③ 按顺序补齐参数,如 Dog("旺财", 3)。 举一反三:以后所有 missing N required positional arguments 都这样修——对照方法参数清单,一个不落地补。

Q2:报 AttributeError: 'Dog' object has no attribute 'name'?

原因:__init__ 里忘了写 self.name = name(或写成了 name = name)。名字从没挂到狗身上,后面 introduce 取 self.name 时自然扑空。 修复三步:① 打开 __init__,逐行检查传入的每个参数;② 确认每个都写成了 self.属性名 = 参数名;③ 保存重跑。 举一反三:取属性时报 AttributeError,回头查存属性那一步——先存后取,顺序不能少。

Q3:方法里写 print(name) 报 NameError: name 'name' is not defined?

原因:方法里想用"这只狗的名字",必须写 self.name。光写 name,Python 把它当成一个凭空冒出的普通变量去找,方法里没定义过,直接迷路。 修复三步:① 定位报错行;② 给属性统统加上 self. 前缀;③ 重跑确认。 举一反三:方法里访问"对象的数据"一律 self.xxx——把 self 读成"我的",self.name 就是"我的名字",就不容易漏。

Q4:类属性和实例属性,到底什么时候用哪个?

原因:判断标准一句话:"这份数据人人一样,还是各存各的"。犬科、腿的数量、学校名——全员一致,用类属性;名字、年龄、分数——人各不同,用实例属性。 修复三步:① 列出对象有哪些数据;② 全员一致的抽出来,写在类里方法外(如 kind = "犬科");③ 因人而异的写进 __init__,挂到 self 上。 举一反三:以后写"全公司统一的税率"用类属性,"每个员工的工资"用实例属性——一处定义,处处生效。

Q5:给 d1 改名字,d1.name = "大旺" 会影响 d2 吗?

原因:不会。实例属性是 d1 的私产,挂在它自己身上;改它动不了 d2 分毫——两只狗的 name 是两个独立的盒子。 修复三步:① 确认改的是实例属性(在 __init__ 里挂 self 的那种);② 直接赋值 d1.name = "大旺";③ 打印 d2.name 验证纹丝不动。 举一反三:反过来,改类属性 Dog.kind = "猫科" 就是全体遭殃——公摊一改,家家生效,动手前想清楚。

⚠️ 新手翻车现场

翻车一:空手造狗

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age


d1 = Dog()          # __init__ 等着收两件行李
TypeError: Dog.__init__() missing 2 required positional arguments: 'name' and 'age'

逐行分析:__init__ 明确要求 name 和 age,错在第 7 行——Dog() 一个参数都没给。补成 d1 = Dog("旺财", 3) 即可通过。

翻车二:属性没挂上 self

class Dog:
    def __init__(self, name):
        name = name          # 左边忘了 self.


d1 = Dog("旺财")
print(d1.name)
AttributeError: 'Dog' object has no attribute 'name'

逐行分析:病根在第 3 行——name = name 只是把参数抄给一个普通变量,从没挂到狗身上(等号左边缺 self.);报错在第 7 行才暴露——出生登记没做,之后谁都查不到它。改成 self.name = name 就好。

翻车三:方法里裸用属性名

class Dog:
    kind = "犬科"

    def __init__(self, name):
        self.name = name

    def introduce(self):
        print(f"我是{name},属于{kind}")     # 两个都忘了 self.


d1 = Dog("旺财")
d1.introduce()
NameError: name 'name' is not defined

逐行分析:错在第 8 行——f-string 里的 name 和 kind 都光秃秃没加 self.。Python 把裸写的 name 当成凭空出现的变量,方法里没定义、外面也没有,当场迷路。改成 self.name 和 self.kind 即可。

📦 完整小示例

小青蛙小学的成绩单:类属性管"全校共享"的部分,实例属性管"每人各一份"的部分。

class Student:
    school = "青蛙小学"              # 类属性:全校共享一份

    def __init__(self, name, score):
        self.name = name             # 实例属性:每人各一份
        self.score = score

    def introduce(self):
        print(f"我是{self.school}的{self.name},考了{self.score}分")

    def add_bonus(self, bonus):
        self.score = self.score + bonus
        print(f"{self.name} 加了 {bonus} 分,现在 {self.score} 分")


s1 = Student("小呱", 88)
s2 = Student("小蹦", 59)

s1.introduce()
s2.introduce()
print("同一个学校吗:", s1.school == s2.school)

s2.add_bonus(5)
s2.introduce()

你会看到:

我是青蛙小学的小呱,考了88分
我是青蛙小学的小蹦,考了59分
同一个学校吗: True
小蹦 加了 5 分,现在 64 分
我是青蛙小学的小蹦,考了64分

逐段中文精讲

  • 第 1~10 行(图纸):school 是类属性(公摊),name、score 在 __init__ 里挂 self(私产);introduce 负责自我介绍,add_bonus 负责加分——数据和改数据的能力打包在一起。
  • 第 12~13 行(造两个学生):同一个 __init__ 跑了两遍,各自收到不同的参数,装进各自独立的盒子。
  • 第 15~17 行(介绍 + 验证公摊):两个学生介绍时都读到了"青蛙小学"——self.school 先找私产,没有,再找公摊;最后一行证明两人共享同一个学校名。
  • 第 19~20 行(只给小蹦加分):self.score + bonus 算出新分写回 self.score,动的是小蹦自己的盒子;最后一行小蹦报出 64 分,而小呱的 88 分毫发无损。

跑通后做个小实验:在 add_bonus 里加一行 print(f"{self.school}全体掌声鼓励"),体会方法里类属性、实例属性随手都能取;再把 s2.add_bonus(5) 改成 s2.add_bonus(-70),看看负数它也照单全收——方法不会替你把关,想拦住奇怪的数,得自己用基础篇的条件判断加检查。

🎯 什么时候用它

  • 银行账户:balance 是实例属性(各存各的),deposit、withdraw 是方法(能力)——数据和操作焊死在同一个对象里,不会张冠李戴。
  • 游戏角色:血量、攻击力挂 self,受击、回血写成方法;打一场 Boss,血量变化全记录在对象身上,谁打谁掉血清清楚楚。
  • 购物车:商品列表是实例属性,加购、清空是方法;每个用户一个购物车对象,谁的车谁做主,互不串货。

📝 本章小结

  • __init__ 在 类名(参数) 的瞬间自动执行,负责收参数、把属性挂到 self 上
  • 谁调用方法,self 就是谁:d1.introduce() 里的 self 就是 d1
  • self.属性 是实例属性(私产,各存一份);类里方法外直接写的是类属性(公摊,全员共享)
  • 方法里访问数据必须带 self.,漏写就会 NameError / AttributeError
  • 记忆锚点:实例属性是自家沙发,类属性是小区花园——沙发各搬各的,花园人人共享。

动手改

  1. 给 Dog 加 have_birthday 方法:让 self.age 加 1 并打印新年龄,给旺财过个生日
  2. 新增类属性 legs = 4,在 introduce 里顺便报出来
  3. 把 self.name = name 这行删掉再运行——看懂这个报错,你就真正理解"实例属性"了

下一章:继承与多态,让猫和狗共用一套骨架 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。