Python 面向对象基础
类与实例
类(Class) 是对象的蓝图或模板,定义了一类事物共同拥有的属性和行为。
实例(Instance) 是根据类创建的具体对象,每个实例拥有独立的数据。
class Dog:
species = "Canis" # 类属性:所有实例共享
def __init__(self, name, age):
self.name = name # 实例属性:每个实例独立
self.age = age
def bark(self):
print(f"{self.name} 汪汪!")
dog1 = Dog("小黑", 3)
dog2 = Dog("小白", 2)
dog1.bark() # 小黑 汪汪!
dog2.bark() # 小白 汪汪!
self 是 Python 约定的第一个参数,代表当前实例本身。调用方法时 Python 自动将实例传入,无需手动传递。
类与实例的关系:
| 对比项 | 类 | 实例 |
|---|---|---|
| 是什么 | 模板 / 蓝图 | 具体对象 |
| 属性 | 类属性(共享) | 实例属性(独立) |
| 创建方式 | class Dog: ... | Dog("小黑", 3) |
| 内存 | 存储方法和类属性 | 存储各自的实例数据 |
检查类型与继承关系:
print(isinstance(dog1, Dog)) # True:dog1 是 Dog 的实例
print(isinstance(dog1, object)) # True:所有类都继承自 object
print(issubclass(Dog, object)) # True:检查类之间的继承关系
print(type(dog1)) # <class '__main__.Dog'>
print(type(dog1) is Dog) # True
属性
实例属性 vs 类属性
实例属性:在 __init__ 中通过 self 定义,每个实例独立持有,修改不影响其他实例。
类属性:定义在类体中(__init__ 之外),所有实例共享同一份数据。
class Counter:
count = 0 # 类属性:记录创建了多少个实例
def __init__(self, name):
Counter.count += 1 # 通过类名修改类属性
self.name = name # 实例属性
self.id = Counter.count
c1 = Counter("A")
c2 = Counter("B")
print(Counter.count) # 2
print(c1.id, c2.id) # 1 2
实例属性遮蔽类属性:当实例定义了与类属性同名的属性时,实例会优先使用自己的,但类属性不受影响。
class Dog:
species = "Canis"
d1 = Dog()
d1.species = "改了" # 在 d1 的 __dict__ 里新建了同名属性
print(d1.species) # 改了(访问的是实例属性)
print(Dog.species) # Canis(类属性不变)
d2 = Dog()
print(d2.species) # Canis(d2 没有同名实例属性,回退到类属性)
属性查找顺序
Python 查找属性的顺序(LEGB 的类版本):
实例 __dict__ → 类 __dict__ → 父类链(按 MRO 顺序)
更精确的顺序(涉及描述符时)见 元编程 → 「描述符」。
__dict__:属性的底层存储
实例和类的属性,本质上都存储在字典里:
class Person:
role = "human"
def __init__(self, name):
self.name = name
p = Person("Alice")
print(p.__dict__) # {'name': 'Alice'}
print(Person.__dict__) # {'role': 'human', '__init__': <function ...>, ...}
方法也是属性——它们是存储在类 __dict__ 里的函数对象。通过实例访问时,Python 会自动将其包装成绑定方法(bound method),把实例作为第一个参数传入。
方法
方法的本质区别在于绑定对象不同,决定了方法内部能访问什么。
实例方法
第一个参数是 self(当前实例),可以访问实例属性和类属性,是最常用的方法类型。
class Dog:
def __init__(self, name):
self.name = name
def bark(self): # 实例方法
print(f"{self.name} 汪汪")
类方法(@classmethod)
第一个参数是 cls(类本身),不能访问实例属性。最典型的用途是作为替代构造函数(工厂方法):
class Date:
def __init__(self, year, month, day):
self.year, self.month, self.day = year, month, day
@classmethod
def from_string(cls, s): # 从字符串构建
year, month, day = map(int, s.split("-"))
return cls(year, month, day) # cls 而非 Date,子类继承后仍正确
@classmethod
def today(cls): # 从系统时间构建
import datetime
d = datetime.date.today()
return cls(d.year, d.month, d.day)
d = Date.from_string("2024-03-15")
print(d.year, d.month, d.day) # 2024 3 15
静态方法(@staticmethod)
没有 self 也没有 cls,与类本身没有绑定关系,只是"逻辑上属于这个类"的工具函数。
class MathUtils:
@staticmethod
def clamp(value, min_val, max_val):
return max(min_val, min(max_val, value))
print(MathUtils.clamp(15, 0, 10)) # 10
三种方法对比
| 方法类型 | 装饰器 | 第一个参数 | 能访问实例属性 | 能访问类属性 | 典型用途 |
|---|---|---|---|---|---|
| 实例方法 | 无 | self | ✅ | ✅ | 操作实例数据 |
| 类方法 | @classmethod | cls | ❌ | ✅ | 替代构造函数、操作类级别数据 |
| 静态方法 | @staticmethod | 无 | ❌ | ❌(需显式类名) | 工具函数 |
封装
封装(Encapsulation):隐藏内部实现,只暴露必要接口。
访问控制约定
Python 没有语言级别的强制访问控制,而是用命名约定表达意图:
| 命名 | 含义 | 示例 |
|---|---|---|
name | 公开(public),随意访问 | self.name |
_name | 受保护(protected),约定不在类外直接访问 | self._age |
__name | 私有(private),触发名称重整 | self.__secret |
名称重整(Name Mangling)
__xxx(双下划线前缀)会被 Python 改写成 _ClassName__xxx,防止在子类中被意外覆盖:
class BankAccount:
def __init__(self, balance):
self.__balance = balance # 实际存储为 _BankAccount__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def get_balance(self):
return self.__balance
acc = BankAccount(1000)
# acc.__balance # ❌ AttributeError
# acc._BankAccount__balance # 可以访问,但强烈不推荐
print(acc.get_balance()) # 1000
核心理解:Python 的私有不是"绝对禁止",而是"意图声明 + 防止误用"。约定优于强制,责任在开发者。
用 property 控制访问(推荐方式)
相比裸的 getter/setter 方法,property 提供了更 Pythonic 的方式:
class Person:
def __init__(self, age):
self._age = age
@property
def age(self): # 对外表现得像普通属性
return self._age
@age.setter
def age(self, value): # 但赋值时可以做校验
if not isinstance(value, int) or value < 0:
raise ValueError("年龄必须是非负整数")
self._age = value
p = Person(20)
print(p.age) # 20,像访问普通属性
p.age = 25 # 触发 setter 校验
p.age = -1 # ❌ ValueError
详见 python-oop-advanced → 「property」
继承
继承(Inheritance) 让子类复用父类的属性和方法,建立 "is-a"(是一个)的关系。
基本继承
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} 在吃东西")
def speak(self):
print("...")
class Dog(Animal): # Dog 继承 Animal
def speak(self): # 方法重写(override)
print(f"{self.name} 汪汪!")
class Cat(Animal):
def speak(self):
print(f"{self.name} 喵喵~")
d = Dog("小黑")
d.eat() # 小黑 在吃东西(继承自 Animal)
d.speak() # 小黑 汪汪!(重写后的版本)
super() 调用父类
重写方法时,用 super() 调用父类版本,实现"扩展而不是替换":
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 必须调用,否则 self.name 不会被设置
self.breed = breed
def speak(self):
print(f"{self.name}({self.breed})汪汪!")
d = Dog("小黑", "拉布拉多")
d.speak() # 小黑(拉布拉多)汪汪!
super()不是"调用父类",而是"沿着 MRO 链调用下一个类",详见 python-oop-advanced → 「super()」。
继承与私有属性
父类的私有属性(__xxx)会进行名称重整,子类无法直接访问:
class Animal:
def __init__(self):
self.__secret = "动物的秘密"
class Dog(Animal):
def reveal(self):
# print(self.__secret) # ❌ AttributeError
print(self._Animal__secret) # ✅ 能访问,但不应这么做
多态
多态(Polymorphism):同一套调用接口,不同对象产生不同行为。
animals = [Dog("小黑", "拉布拉多"), Cat("小白"), Dog("旺财", "柴犬")]
for animal in animals:
animal.speak() # 调用方式完全相同
# 小黑(拉布拉多)汪汪!
# 小白 喵喵~
# 旺财(柴犬)汪汪!
多态的价值:
# ❌ 没有多态时,需要类型判断
for a in animals:
if isinstance(a, Dog):
a.speak() # 每新增一种动物就要改这里
elif isinstance(a, Cat):
a.speak()
# ✅ 有多态后,新增 Bird 类完全不需要修改遍历逻辑
class Bird(Animal):
def speak(self):
print(f"{self.name} 啾啾~")
多态让代码对扩展开放(新增类型)、对修改关闭(无需改动已有逻辑)。
鸭子类型的详细讨论见 python-oop-advanced → 「鸭子类型」
__new__ 与 __init__
创建一个实例实际分两步:
__new__(cls):向操作系统申请内存,创建并返回空白对象,这一步决定"返回哪个对象"__init__(self):接收__new__返回的对象,对其进行初始化,这一步决定"如何填充数据"
class Demo:
def __new__(cls, *args, **kwargs):
print("1. __new__:分配内存,创建对象")
instance = super().__new__(cls)
return instance # 必须返回实例,否则 __init__ 不会被调用
def __init__(self, value):
print("2. __init__:初始化对象")
self.value = value
d = Demo(42)
# 1. __new__:分配内存,创建对象
# 2. __init__:初始化对象
什么时候需要重写 __new__?
绝大多数情况下只需要重写 __init__。__new__ 主要用于:
- 单例模式:控制只创建一个实例(见 python-design-patterns → 「单例模式」)
- 继承不可变类型:如
int、str、tuple,它们的值在创建时确定,只能在__new__中修改
class PositiveInt(int):
def __new__(cls, value):
if value <= 0:
raise ValueError("必须是正整数")
return super().__new__(cls, value)
n = PositiveInt(5) # ✅
m = PositiveInt(-1) # ❌ ValueError
单例模式的
__new__实现见 python-design-patterns → 「单例模式」
进阶内容见 → python-oop-advanced