首页 / 知识库 / 0基础入门-阅读资料 / 0基础-python入门到精通

第 11 章 面向对象编程基础

11.1 类与实例

class Dog:
    # 类属性 — 所有实例共享
    species = "Canis familiaris"
    
    # 初始化方法(构造方法)
    def __init__(self, name, age):
        # 实例属性 — 每个实例独有
        self.name = name
        self.age = age
    
    # 实例方法
    def bark(self):
        return f"{self.name} says: Woof!"
    
    def info(self):
        return f"{self.name}, {self.age} years old"

# 创建实例
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)

print(dog1.bark())           # Buddy says: Woof!
print(dog2.info())           # Max, 5 years old
print(dog1.species)          # Canis familiaris
print(Dog.species)           # Canis familiaris
print(type(dog1))            # <class '__main__.Dog'>
print(isinstance(dog1, Dog)) # True

self 的本质

self 就是实例本身,不是关键字,只是约定俗成的名字:

class MyClass:
    def method(self):
        print(f"self is {self}")

obj = MyClass()
obj.method()       # self is <__main__.MyClass object at 0x...>

# obj.method() 等价于 MyClass.method(obj)
MyClass.method(obj)  # 完全等价

11.2 __init__ 构造方法

__init__ 在实例创建后被调用,用于初始化属性:

class Circle:
    def __init__(self, radius):
        if radius < 0:
            raise ValueError("半径不能为负")
        self.radius = radius
        self.area = 3.14159 * radius ** 2  # 初始化时计算

c = Circle(5)
print(c.radius)  # 5
print(c.area)    # 78.53975

注意__init__ 不是真正的构造方法。真正创建对象的是 __new____init__ 只是初始化:

class Singleton:
    _instance = None
    
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self, value):
        self.value = value

a = Singleton(1)
b = Singleton(2)
print(a is b)       # True — 同一个实例
print(a.value)      # 2 — 被第二次 __init__ 覆盖

11.3 实例属性与类属性

class Student:
    # 类属性 — 定义在类体中
    school = "Python University"
    count = 0
    
    def __init__(self, name):
        # 实例属性 — 定义在 __init__ 中
        self.name = name
        Student.count += 1   # 通过类名访问类属性

s1 = Student("Alice")
s2 = Student("Bob")

# 类属性通过类或实例都能访问
print(Student.school)   # Python University
print(s1.school)        # Python University
print(Student.count)    # 2

# 通过实例"修改"类属性实际上是创建了同名的实例属性
s1.school = "MIT"
print(s1.school)        # MIT — 实例属性(遮蔽了类属性)
print(s2.school)        # Python University — 类属性不变
print(Student.school)   # Python University — 类属性不变

可变类属性的陷阱

class BadExample:
    shared_list = []   # 所有实例共享同一个列表!
    
    def add(self, item):
        self.shared_list.append(item)

a = BadExample()
b = BadExample()
a.add(1)
print(b.shared_list)  # [1] — b 也受影响!

class GoodExample:
    def __init__(self):
        self.my_list = []  # 每个实例独有

11.4 实例方法、类方法、静态方法

class Pizza:
    def __init__(self, radius, ingredients):
        self.radius = radius
        self.ingredients = ingredients
    
    # 实例方法 — 通过 self 访问实例
    def area(self):
        return 3.14159 * self.radius ** 2
    
    # 类方法 — 通过 cls 访问类,常用作替代构造方法
    @classmethod
    def margherita(cls):
        return cls(30, ["mozzarella", "tomatoes"])
    
    @classmethod
    def prosciutto(cls):
        return cls(30, ["mozzarella", "tomatoes", "ham"])
    
    # 静态方法 — 不访问实例也不访问类
    @staticmethod
    def validate_radius(radius):
        return radius > 0

# 实例方法
p = Pizza(25, ["cheese"])
print(p.area())

# 类方法作为工厂方法
m = Pizza.margherita()
print(m.ingredients)   # ['mozzarella', 'tomatoes']

# 静态方法
print(Pizza.validate_radius(10))  # True
print(Pizza.validate_radius(-1))  # False

三者对比

实例方法类方法静态方法
装饰器@classmethod@staticmethod
第一个参数self(实例)cls(类)
访问实例属性可以不可以不可以
访问类属性可以可以不可以(除非硬编码类名)
常见用途操作实例工厂方法工具函数
class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
    
    def __repr__(self):
        return f"Date({self.year}, {self.month}, {self.day})"
    
    # 类方法:从字符串创建(工厂方法)
    @classmethod
    def from_string(cls, date_str):
        year, month, day = map(int, date_str.split("-"))
        return cls(year, month, day)
    
    @classmethod
    def today(cls):
        import datetime
        t = datetime.date.today()
        return cls(t.year, t.month, t.day)
    
    # 静态方法:验证日期是否合法
    @staticmethod
    def is_valid(year, month, day):
        return 1 <= month <= 12 and 1 <= day <= 31

d1 = Date(2026, 3, 31)
d2 = Date.from_string("2026-03-31")
d3 = Date.today()
print(Date.is_valid(2026, 13, 1))  # False

11.5 属性访问控制:公有、保护、私有

Python 没有真正的访问控制,靠命名约定

class Account:
    def __init__(self, owner, balance):
        self.owner = owner        # 公有 — 任何人都能访问
        self._balance = balance   # 保护 — 约定"请勿直接访问"
        self.__secret = "123"     # 私有 — 名称改写(name mangling)
    
    def get_balance(self):
        return self._balance

acc = Account("Alice", 1000)

# 公有 — 直接访问
print(acc.owner)          # Alice

# 保护 — 可以访问,但约定不应该
print(acc._balance)       # 1000(能访问,但 IDE 可能警告)

# 私有 — 名称改写
# print(acc.__secret)     # AttributeError!
print(acc._Account__secret)  # "123" — 通过改写后的名字仍然能访问

名称改写(Name Mangling)的目的不是安全,而是避免子类意外覆盖:

class Parent:
    def __init__(self):
        self.__x = 10  # 改写为 _Parent__x

class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__x = 20  # 改写为 _Child__x — 不会冲突!

c = Child()
print(c._Parent__x)  # 10
print(c._Child__x)   # 20

最佳实践

  • 默认用公有属性,Python 社区信任开发者
  • 用单下划线 _ 表示内部实现细节
  • 只有在需要避免子类命名冲突时才用双下划线 __
  • 提供公开接口时用 @property

11.6 @property 描述符快捷方式

@property 让方法看起来像属性,实现受控访问:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius   # 存储内部值
    
    @property
    def celsius(self):
        """获取摄氏温度"""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        """设置摄氏温度(带验证)"""
        if value < -273.15:
            raise ValueError("温度不能低于绝对零度")
        self._celsius = value
    
    @celsius.deleter
    def celsius(self):
        """删除温度值"""
        print("删除温度")
        del self._celsius
    
    @property
    def fahrenheit(self):
        """自动计算华氏温度(只读属性)"""
        return self._celsius * 9 / 5 + 32

t = Temperature(25)
print(t.celsius)        # 25 — 像访问属性一样
print(t.fahrenheit)     # 77.0 — 自动计算

t.celsius = 30          # 像赋值一样,但会执行 setter
# t.celsius = -300      # ValueError: 温度不能低于绝对零度
# t.fahrenheit = 100    # AttributeError: 没有 setter,只读

del t.celsius           # 删除温度

用 property 实现渐进式重构

# 阶段 1:简单属性
class User:
    def __init__(self, name):
        self.name = name

# 阶段 2:需要加验证了,但不想改调用方式
class User:
    def __init__(self, name):
        self.name = name  # 这里会触发 setter
    
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, value):
        if not value.strip():
            raise ValueError("名字不能为空")
        self._name = value.strip()

# 调用方式完全不变
user = User("Alice")
print(user.name)        # Alice
user.name = "Bob"       # 有验证
# user.name = "   "     # ValueError

缓存属性

class DataProcessor:
    def __init__(self, data):
        self._data = data
        self._result = None
    
    @property
    def result(self):
        if self._result is None:
            print("计算中...(耗时操作)")
            self._result = sum(x ** 2 for x in self._data)
        return self._result

dp = DataProcessor(range(1000))
print(dp.result)  # 计算中... 332833500
print(dp.result)  # 332833500(直接返回缓存)

# Python 3.8+ 可以用 functools.cached_property
from functools import cached_property

class DataProcessor:
    def __init__(self, data):
        self.data = data
    
    @cached_property
    def result(self):
        print("计算中...")
        return sum(x ** 2 for x in self.data)

本章小结:Python 的 OOP 简洁而实用。核心要点:理解 self 的本质、类属性与实例属性的区别、三种方法类型的适用场景、名称改写的真正用途、以及 @property 实现受控访问。Python 的 OOP 不追求严格封装,而是建立在信任和约定之上。