Skip to main content
Septvean's Documents
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

一、什么是类

类可以理解为创建对象的模板。

例如,一只股票通常包含:

股票代码
股票名称
价格
行业

还可能具有一些行为:

显示信息
更新价格
判断是否属于主板
计算涨跌幅

如果只使用字典:

stock = {
    "code": "600519",
    "name": "贵州茅台",
    "price": 1500.0,
}

可以保存数据,但相关行为需要单独写函数。

使用类,可以把数据和行为组织在一起:

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

    def show_info(self) -> None:
        print(
            self.code,
            self.name,
            self.price,
        )

创建对象:

stock = Stock(
    code="600519",
    name="贵州茅台",
    price=1500.0,
)

调用方法:

stock.show_info()

类主要用于:

  • 描述现实中的对象
  • 把数据和行为组织在一起
  • 提高代码复用性
  • 管理复杂程序
  • 构建大型项目

二、类和对象

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

定义类:

class Person:
    pass

创建对象:

person = Person()

其中:

Person  → 类
person  → 对象

可以创建多个对象:

person1 = Person()
person2 = Person()
person3 = Person()

它们都属于 Person 类,但彼此是不同的对象。

查看类型:

print(type(person1))

判断对象是否属于某个类:

print(isinstance(person1, Person))

结果:

True

三、定义类

类使用 class 关键字定义。

基本格式:

class 类名:
    类的内容

例如:

class Dog:
    pass

Python 类名通常使用大驼峰命名法:

class Stock:
    pass


class StockAnalyzer:
    pass


class DatabaseConnection:
    pass

不推荐:

class stock_analyzer:
    pass

四、创建对象

定义类:

class Dog:
    pass

创建对象:

dog = Dog()

可以创建多个:

dog1 = Dog()
dog2 = Dog()

判断是否是同一个对象:

print(dog1 is dog2)

结果:

False

因为它们是两个独立对象。


五、初始化方法 init

__init__() 用于对象创建后的初始化。

class Person:
    def __init__(
        self,
        name: str,
        age: int,
    ) -> None:
        self.name = name
        self.age = age

创建对象:

person = Person(
    name="张三",
    age=25,
)

访问属性:

print(person.name)
print(person.age)

结果:

张三
25

1. init 的作用

创建对象时:

person = Person("张三", 25)

Python 会自动调用:

Person.__init__(
    person,
    "张三",
    25,
)

通常不需要手动调用 __init__()


2. self 是什么

self 表示当前对象本身。

例如:

self.name = name

表示:

把参数 name 保存到当前对象的 name 属性中

不同对象拥有自己的属性:

person1 = Person("张三", 25)
person2 = Person("李四", 30)

print(person1.name)
print(person2.name)

结果:

张三
李四

3. self 必须写吗

实例方法的第一个参数必须接收当前对象,通常命名为 self

class Person:
    def show_name(self) -> None:
        print(self.name)

理论上可以使用其他名称:

class Person:
    def show_name(current_object) -> None:
        print(current_object.name)

但不推荐。

约定统一使用:

self

六、实例属性

实例属性属于具体对象。

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

创建对象:

stock1 = Stock(
    "600519",
    "贵州茅台",
    1500.0,
)

stock2 = Stock(
    "000001",
    "平安银行",
    11.5,
)

每个对象的数据独立:

print(stock1.name)
print(stock2.name)

修改一个对象:

stock1.price = 1520.0

不会影响另一个对象。


1. 动态添加属性

Python 可以在对象创建后添加新属性:

stock1.industry = "白酒"

访问:

print(stock1.industry)

stock2 没有这个属性:

print(stock2.industry)

会出现:

AttributeError

实际项目中,推荐在 __init__() 中统一声明实例属性。


七、实例方法

实例方法是属于对象的方法。

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

    def show_info(self) -> None:
        print(
            f"{self.code} "
            f"{self.name} "
            f"{self.price}"
        )

调用:

stock = Stock(
    "600519",
    "贵州茅台",
    1500.0,
)

stock.show_info()

Python 实际会执行类似:

Stock.show_info(stock)

因此实例方法的第一个参数需要是 self


1. 方法修改属性

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

    def update_price(
        self,
        new_price: float,
    ) -> None:
        self.price = new_price

调用:

stock = Stock(
    "600519",
    "贵州茅台",
    1500.0,
)

stock.update_price(1520.0)

print(stock.price)

结果:

1520.0

2. 方法返回结果

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

    def is_main_board(self) -> bool:
        return self.code.startswith(("00", "60"))

调用:

stock = Stock(
    "600519",
    "贵州茅台",
    1500.0,
)

print(stock.is_main_board())

结果:

True

八、类属性

类属性属于类本身,所有对象共享。

class Stock:
    market = "A股"

    def __init__(
        self,
        code: str,
        name: str,
    ) -> None:
        self.code = code
        self.name = name

访问类属性:

print(Stock.market)

也可以通过对象访问:

stock = Stock(
    "600519",
    "贵州茅台",
)

print(stock.market)

1. 类属性共享

class Stock:
    count = 0

    def __init__(
        self,
        code: str,
        name: str,
    ) -> None:
        self.code = code
        self.name = name

        Stock.count += 1

创建对象:

stock1 = Stock(
    "600519",
    "贵州茅台",
)

stock2 = Stock(
    "000001",
    "平安银行",
)

查看对象数量:

print(Stock.count)

结果:

2

2. 类属性和实例属性同名

class Person:
    category = "人类"

创建对象:

person = Person()

访问:

print(person.category)

如果给对象赋值:

person.category = "程序员"

此时创建了实例属性,覆盖对象访问时看到的类属性。

print(person.category)
print(Person.category)

结果:

程序员
人类

类属性本身没有被修改。


九、类方法

类方法使用 @classmethod

第一个参数通常命名为 cls,表示当前类。

class Stock:
    market = "A股"

    @classmethod
    def show_market(cls) -> None:
        print(cls.market)

调用:

Stock.show_market()

也可以通过对象调用:

stock = Stock()
stock.show_market()

但通常通过类调用更清晰。


1. 类方法访问类属性

class Stock:
    count = 0

    def __init__(self) -> None:
        type(self).count += 1

    @classmethod
    def get_count(cls) -> int:
        return cls.count

使用:

Stock()
Stock()

print(Stock.get_count())

结果:

2

2. 类方法作为替代构造方法

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

    @classmethod
    def from_text(
        cls,
        text: str,
    ) -> "Stock":
        code, name, price_text = text.split(",")

        return cls(
            code=code.strip(),
            name=name.strip(),
            price=float(price_text),
        )

调用:

stock = Stock.from_text(
    "600519,贵州茅台,1500.0"
)

print(stock.code)
print(stock.name)
print(stock.price)

类方法常用于:

  • 从字符串创建对象
  • 从字典创建对象
  • 从 JSON 创建对象
  • 从数据库记录创建对象

3. 从字典创建对象

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

    @classmethod
    def from_dict(
        cls,
        data: dict[str, object],
    ) -> "Stock":
        return cls(
            code=str(data["code"]),
            name=str(data["name"]),
            price=float(data["price"]),
        )

调用:

data = {
    "code": "600519",
    "name": "贵州茅台",
    "price": 1500.0,
}

stock = Stock.from_dict(data)

十、静态方法

静态方法使用 @staticmethod

静态方法既不接收 self,也不接收 cls

class Stock:
    @staticmethod
    def is_valid_code(code: str) -> bool:
        return (
            len(code) == 6
            and code.isdigit()
        )

调用:

print(
    Stock.is_valid_code("600519")
)

结果:

True

静态方法适合:

  • 与类相关的辅助函数
  • 不需要访问对象属性
  • 不需要访问类属性

1. 三种方法区别

实例方法:

def method(self):
    pass

适合访问:

实例属性
类属性

类方法:

@classmethod
def method(cls):
    pass

适合访问:

类属性
创建替代构造方法

静态方法:

@staticmethod
def method():
    pass

适合:

与类有关,但不依赖对象和类状态的工具逻辑

十一、封装

封装是把数据和操作数据的方法放在类内部,并限制外部随意修改。

Python 没有像某些语言那样严格的私有属性机制,但有命名约定。


1. 公开属性

class Stock:
    def __init__(
        self,
        price: float,
    ) -> None:
        self.price = price

外部可以直接访问和修改:

stock.price = -100

这可能导致无效数据。


2. 单下划线属性

class Stock:
    def __init__(
        self,
        price: float,
    ) -> None:
        self._price = price

单下划线表示:

这是内部属性,外部不应随意访问。

但技术上仍然可以访问:

print(stock._price)

这是一种开发者约定,不是强制限制。


3. 双下划线属性

class Stock:
    def __init__(
        self,
        price: float,
    ) -> None:
        self.__price = price

外部直接访问:

print(stock.__price)

会出现:

AttributeError

Python 会进行名称改写。

实际属性名称类似:

_Stock__price

仍然可以访问:

print(stock._Stock__price)

但不应该这样做。

双下划线主要用于避免子类属性冲突,而不是绝对安全。


十二、property 属性

property 可以让方法像属性一样访问。

class Stock:
    def __init__(
        self,
        price: float,
    ) -> None:
        self._price = price

    @property
    def price(self) -> float:
        return self._price

访问:

stock = Stock(1500.0)

print(stock.price)

调用时不写括号:

stock.price

不是:

stock.price()

1. property 设置器

class Stock:
    def __init__(
        self,
        price: float,
    ) -> None:
        self.price = price

    @property
    def price(self) -> float:
        return self._price

    @price.setter
    def price(
        self,
        value: float,
    ) -> None:
        if value < 0:
            raise ValueError(
                "价格不能小于零"
            )

        self._price = value

创建对象:

stock = Stock(1500.0)

修改:

stock.price = 1520.0

非法修改:

stock.price = -100

会抛出:

ValueError

外部看起来仍然像普通属性:

stock.price

但内部已经执行了校验逻辑。


2. 只读属性

只定义 getter,不定义 setter:

class Rectangle:
    def __init__(
        self,
        width: float,
        height: float,
    ) -> None:
        self.width = width
        self.height = height

    @property
    def area(self) -> float:
        return self.width * self.height

访问:

rectangle = Rectangle(10, 5)

print(rectangle.area)

不能直接赋值:

rectangle.area = 100

会报错。


十三、特殊方法

以双下划线开头和结尾的方法,称为特殊方法或魔术方法。

例如:

__init__
__str__
__repr__
__len__
__eq__

它们让自定义对象支持 Python 内置行为。


十四、str

__str__() 定义对象面向用户的字符串表示。

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
    ) -> None:
        self.code = code
        self.name = name

    def __str__(self) -> str:
        return f"{self.code} {self.name}"

使用:

stock = Stock(
    "600519",
    "贵州茅台",
)

print(stock)

结果:

600519 贵州茅台

没有定义 __str__() 时,输出通常类似:

<__main__.Stock object at 0x...>

十五、repr

__repr__() 定义对象面向开发者的字符串表示。

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

    def __repr__(self) -> str:
        return (
            "Stock("
            f"code={self.code!r}, "
            f"name={self.name!r}, "
            f"price={self.price!r}"
            ")"
        )

使用:

stock = Stock(
    "600519",
    "贵州茅台",
    1500.0,
)

print(repr(stock))

结果:

Stock(code='600519', name='贵州茅台', price=1500.0)

通常建议:

__str__  → 给普通用户看
__repr__ → 给开发者调试看

十六、eq

__eq__() 定义两个对象如何判断相等。

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
    ) -> None:
        self.code = code
        self.name = name

    def __eq__(
        self,
        other: object,
    ) -> bool:
        if not isinstance(other, Stock):
            return NotImplemented

        return self.code == other.code

创建对象:

stock1 = Stock(
    "600519",
    "贵州茅台",
)

stock2 = Stock(
    "600519",
    "贵州茅台",
)

比较:

print(stock1 == stock2)

结果:

True

如果没有定义 __eq__(),默认比较是否为同一个对象。


十七、len

让对象支持 len()

class Portfolio:
    def __init__(
        self,
        stocks: list[str],
    ) -> None:
        self.stocks = stocks

    def __len__(self) -> int:
        return len(self.stocks)

使用:

portfolio = Portfolio([
    "600519",
    "000001",
    "300750",
])

print(len(portfolio))

结果:

3

十八、contains

让对象支持 in

class Portfolio:
    def __init__(
        self,
        codes: list[str],
    ) -> None:
        self.codes = codes

    def __contains__(
        self,
        code: object,
    ) -> bool:
        return code in self.codes

使用:

portfolio = Portfolio([
    "600519",
    "000001",
])

print("600519" in portfolio)

结果:

True

十九、iter

让对象可以被遍历。

class Portfolio:
    def __init__(
        self,
        codes: list[str],
    ) -> None:
        self.codes = codes

    def __iter__(self):
        return iter(self.codes)

使用:

portfolio = Portfolio([
    "600519",
    "000001",
    "300750",
])

for code in portfolio:
    print(code)

二十、继承

继承允许一个类复用另一个类的属性和方法。

父类:

class Animal:
    def speak(self) -> None:
        print("动物发出声音")

子类:

class Dog(Animal):
    pass

使用:

dog = Dog()
dog.speak()

结果:

动物发出声音

Dog 继承了 Animal 的方法。


1. 重写方法

子类可以重新定义父类方法。

class Animal:
    def speak(self) -> None:
        print("动物发出声音")


class Dog(Animal):
    def speak(self) -> None:
        print("汪汪")


class Cat(Animal):
    def speak(self) -> None:
        print("喵喵")

调用:

dog = Dog()
cat = Cat()

dog.speak()
cat.speak()

结果:

汪汪
喵喵

2. super()

super() 用于调用父类的方法。

class Person:
    def __init__(
        self,
        name: str,
    ) -> None:
        self.name = name

子类:

class Employee(Person):
    def __init__(
        self,
        name: str,
        salary: float,
    ) -> None:
        super().__init__(name)
        self.salary = salary

创建对象:

employee = Employee(
    "张三",
    10000.0,
)

print(employee.name)
print(employee.salary)

如果不调用:

super().__init__(name)

父类初始化逻辑不会自动执行。


3. 重写并保留父类行为

class Animal:
    def speak(self) -> None:
        print("动物准备发声")


class Dog(Animal):
    def speak(self) -> None:
        super().speak()
        print("汪汪")

调用:

dog = Dog()
dog.speak()

结果:

动物准备发声
汪汪

二十一、多态

多态表示不同对象可以使用相同的方法接口,但表现不同。

class Dog:
    def speak(self) -> None:
        print("汪汪")


class Cat:
    def speak(self) -> None:
        print("喵喵")

定义函数:

def make_sound(animal) -> None:
    animal.speak()

调用:

make_sound(Dog())
make_sound(Cat())

结果:

汪汪
喵喵

函数不关心对象具体是什么类,只关心它是否有 speak() 方法。

这体现了 Python 的鸭子类型:

如果一个对象走起来像鸭子,叫起来也像鸭子,就可以把它当作鸭子。


1. 股票示例

class MarketOrder:
    def execute(self) -> None:
        print("按市场价格成交")


class LimitOrder:
    def execute(self) -> None:
        print("达到指定价格后成交")

统一执行:

def execute_order(order) -> None:
    order.execute()

调用:

execute_order(MarketOrder())
execute_order(LimitOrder())

二十二、抽象基类

抽象基类用于定义子类必须实现的方法。

from abc import ABC, abstractmethod

定义抽象类:

class Order(ABC):
    @abstractmethod
    def execute(self) -> None:
        pass

子类:

class MarketOrder(Order):
    def execute(self) -> None:
        print("执行市价单")

不能直接创建抽象类对象:

order = Order()

会报错。

子类如果没有实现抽象方法,也不能创建对象。

class LimitOrder(Order):
    pass
order = LimitOrder()

会报错。

抽象类适合大型项目中统一接口。


二十三、多重继承

Python 支持一个类继承多个父类。

class Flyable:
    def fly(self) -> None:
        print("飞行")


class Swimmable:
    def swim(self) -> None:
        print("游泳")


class Duck(Flyable, Swimmable):
    pass

使用:

duck = Duck()

duck.fly()
duck.swim()

多重继承可以使用,但不应过度复杂。


1. MRO 方法解析顺序

查看类的方法查找顺序:

print(Duck.mro())

或者:

print(Duck.__mro__)

Python 会按照 MRO 顺序查找方法。


二十四、组合

组合是一个对象内部包含另一个对象。

class Engine:
    def start(self) -> None:
        print("发动机启动")


class Car:
    def __init__(self) -> None:
        self.engine = Engine()

    def start(self) -> None:
        self.engine.start()
        print("汽车启动")

使用:

car = Car()
car.start()

结果:

发动机启动
汽车启动

组合表示:

Car 拥有一个 Engine

继承表示:

Dog 是一种 Animal

选择原则:

是一个 → 继承
有一个 → 组合

实际项目中,通常优先考虑组合,而不是过度使用继承。


二十五、数据类 dataclass

如果一个类主要用于保存数据,可以使用 dataclass 简化代码。

from dataclasses import dataclass

普通类:

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

数据类:

@dataclass
class Stock:
    code: str
    name: str
    price: float

创建对象:

stock = Stock(
    code="600519",
    name="贵州茅台",
    price=1500.0,
)

自动生成:

  • __init__
  • __repr__
  • __eq__

输出:

print(stock)

结果类似:

Stock(code='600519', name='贵州茅台', price=1500.0)

1. 默认值

@dataclass
class Stock:
    code: str
    name: str
    price: float = 0.0
    industry: str = "未知"

创建:

stock = Stock(
    code="600519",
    name="贵州茅台",
)

2. 字段顺序

无默认值字段必须放在有默认值字段前面。

正确:

@dataclass
class Stock:
    code: str
    name: str
    price: float = 0.0

错误:

@dataclass
class Stock:
    price: float = 0.0
    code: str

3. default_factory

列表、字典、集合等可变默认值,不能直接写:

@dataclass
class Portfolio:
    codes: list[str] = []

这会报错。

正确:

from dataclasses import dataclass, field


@dataclass
class Portfolio:
    codes: list[str] = field(
        default_factory=list
    )

字典:

@dataclass
class Portfolio:
    stocks: dict[str, str] = field(
        default_factory=dict
    )

4. post_init

初始化完成后执行额外校验:

@dataclass
class Stock:
    code: str
    name: str
    price: float

    def __post_init__(self) -> None:
        self.code = self.code.strip()

        if len(self.code) != 6:
            raise ValueError(
                "股票代码必须为 6 位"
            )

        if self.price < 0:
            raise ValueError(
                "价格不能小于零"
            )

创建对象时会自动执行:

stock = Stock(
    " 600519 ",
    "贵州茅台",
    1500.0,
)

5. 冻结数据类

@dataclass(frozen=True)
class Point:
    x: float
    y: float

创建:

point = Point(10, 20)

不能修改:

point.x = 100

适合不可变数据对象。


6. slots

Python 3.10 以上可以使用:

@dataclass(slots=True)
class Stock:
    code: str
    name: str
    price: float

优点:

  • 减少内存占用
  • 防止随意添加新属性
  • 属性访问通常更快

例如:

stock.industry = "白酒"

如果未定义 industry,会报错。


二十六、slots

普通类也可以使用 __slots__

class Stock:
    __slots__ = (
        "code",
        "name",
        "price",
    )

    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

对象不能动态添加未声明属性:

stock.industry = "白酒"

会报错。

适合大量对象、字段固定的场景。


二十七、类中的类型提示

class Stock:
    market: str = "A股"

    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code: str = code
        self.name: str = name
        self.price: float = price

类型提示帮助编辑器理解属性类型。


二十八、对象转字典

普通类可以手动转换:

class Stock:
    def __init__(
        self,
        code: str,
        name: str,
        price: float,
    ) -> None:
        self.code = code
        self.name = name
        self.price = price

    def to_dict(
        self,
    ) -> dict[str, object]:
        return {
            "code": self.code,
            "name": self.name,
            "price": self.price,
        }

调用:

stock = Stock(
    "600519",
    "贵州茅台",
    1500.0,
)

data = stock.to_dict()

1. 数据类转字典

from dataclasses import asdict, dataclass


@dataclass
class Stock:
    code: str
    name: str
    price: float

转换:

stock = Stock(
    "600519",
    "贵州茅台",
    1500.0,
)

data = asdict(stock)

结果:

{
    "code": "600519",
    "name": "贵州茅台",
    "price": 1500.0,
}

二十九、类和字典的区别

使用字典:

stock = {
    "code": "600519",
    "name": "贵州茅台",
    "price": 1500.0,
}

优点:

  • 简单
  • 灵活
  • 适合 JSON
  • 适合临时数据

缺点:

  • 键容易拼错
  • 缺少行为
  • 类型不明确
  • 大型项目难维护

使用类:

stock = Stock(
    code="600519",
    name="贵州茅台",
    price=1500.0,
)

优点:

  • 结构明确
  • 属性和行为统一
  • 编辑器提示更好
  • 容易校验数据
  • 适合复杂项目

简单数据处理可以用字典。

当数据具有明确结构和行为时,可以考虑类。


三十、什么时候应该使用类

适合使用类:

  • 数据具有固定字段
  • 数据具有相关行为
  • 需要创建多个同类型对象
  • 需要维护对象状态
  • 需要继承或多态
  • 项目逻辑越来越复杂

例如:

股票
订单
用户
数据库连接
交易策略
文件解析器
HTTP 客户端
配置对象

不一定需要类:

简单数学计算
一次性数据清洗
只有几个步骤的小脚本
无状态工具函数

不应该为了面向对象而强行使用类。


三十一、类设计原则

1. 一个类负责一个主要职责

不推荐:

class StockSystem:
    # 读取 Excel
    # 请求网络
    # 分析股票
    # 保存数据库
    # 发送邮件
    pass

推荐拆分:

class StockLoader:
    pass


class StockAnalyzer:
    pass


class StockRepository:
    pass


class NotificationService:
    pass

2. 属性保存状态,方法处理行为

class Stock:
    def __init__(
        self,
        code: str,
        price: float,
    ) -> None:
        self.code = code
        self.price = price

    def update_price(
        self,
        new_price: float,
    ) -> None:
        self.price = new_price

3. 避免上帝类

上帝类指一个类承担几乎所有功能。

例如:

class Application:
    # 上千行代码
    # 所有业务逻辑
    pass

这种类难以测试和维护。


4. 优先组合,谨慎继承

不推荐为了复用几行代码而建立复杂继承层级。

优先考虑:

class Analyzer:
    def __init__(
        self,
        loader: StockLoader,
    ) -> None:
        self.loader = loader

5. 不要让属性失控

如果属性有规则,使用:

  • 初始化校验
  • property
  • 数据类 __post_init__
  • 专门更新方法

例如:

def update_price(
    self,
    price: float,
) -> None:
    if price < 0:
        raise ValueError(
            "价格不能小于零"
        )

    self.price = price

三十二、综合案例一:股票类

from dataclasses import dataclass

定义:

@dataclass(slots=True)
class Stock:
    code: str
    name: str
    price: float
    previous_close: float
    industry: str = "未知"

    def __post_init__(self) -> None:
        self.code = self.code.strip()
        self.name = self.name.strip()

        if len(self.code) != 6:
            raise ValueError(
                "股票代码必须为 6 位"
            )

        if not self.code.isdigit():
            raise ValueError(
                "股票代码必须全部是数字"
            )

        if self.price < 0:
            raise ValueError(
                "股票价格不能小于零"
            )

        if self.previous_close <= 0:
            raise ValueError(
                "昨收价格必须大于零"
            )

    @property
    def change_amount(self) -> float:
        return self.price - self.previous_close

    @property
    def change_percent(self) -> float:
        return (
            self.change_amount
            / self.previous_close
            * 100
        )

    def is_main_board(self) -> bool:
        return self.code.startswith(
            ("00", "60")
        )

    def update_price(
        self,
        new_price: float,
    ) -> None:
        if new_price < 0:
            raise ValueError(
                "价格不能小于零"
            )

        self.price = new_price

    def to_dict(
        self,
    ) -> dict[str, object]:
        return {
            "code": self.code,
            "name": self.name,
            "price": self.price,
            "previous_close": (
                self.previous_close
            ),
            "industry": self.industry,
            "change_amount": (
                self.change_amount
            ),
            "change_percent": (
                self.change_percent
            ),
        }

    def __str__(self) -> str:
        return (
            f"{self.code} {self.name} "
            f"{self.price:.2f} "
            f"{self.change_percent:+.2f}%"
        )

使用:

stock = Stock(
    code="600519",
    name="贵州茅台",
    price=1520.0,
    previous_close=1500.0,
    industry="白酒",
)

print(stock)
print(stock.is_main_board())
print(stock.change_amount)
print(stock.change_percent)

三十三、综合案例二:股票组合类

class Portfolio:
    def __init__(
        self,
        name: str,
    ) -> None:
        self.name = name
        self._stocks: dict[
            str,
            Stock,
        ] = {}

添加股票:

    def add_stock(
        self,
        stock: Stock,
    ) -> None:
        self._stocks[stock.code] = stock

删除股票:

    def remove_stock(
        self,
        code: str,
    ) -> Stock | None:
        return self._stocks.pop(
            code,
            None,
        )

查找股票:

    def get_stock(
        self,
        code: str,
    ) -> Stock | None:
        return self._stocks.get(code)

获取主板股票:

    def get_main_board_stocks(
        self,
    ) -> list[Stock]:
        return [
            stock
            for stock in self._stocks.values()
            if stock.is_main_board()
        ]

按涨幅排序:

    def sort_by_change_percent(
        self,
        *,
        reverse: bool = True,
    ) -> list[Stock]:
        return sorted(
            self._stocks.values(),
            key=lambda stock: (
                stock.change_percent
            ),
            reverse=reverse,
        )

统计数量:

    def __len__(self) -> int:
        return len(self._stocks)

支持成员判断:

    def __contains__(
        self,
        code: object,
    ) -> bool:
        return code in self._stocks

支持遍历:

    def __iter__(self):
        return iter(
            self._stocks.values()
        )

完整使用:

portfolio = Portfolio(
    "自选股"
)

portfolio.add_stock(
    Stock(
        "600519",
        "贵州茅台",
        1520.0,
        1500.0,
        "白酒",
    )
)

portfolio.add_stock(
    Stock(
        "000001",
        "平安银行",
        11.8,
        11.5,
        "银行",
    )
)

portfolio.add_stock(
    Stock(
        "300750",
        "宁德时代",
        295.0,
        300.0,
        "电池",
    )
)

查看数量:

print(len(portfolio))

判断:

print(
    "600519" in portfolio
)

遍历:

for stock in portfolio:
    print(stock)

排序:

for stock in (
    portfolio.sort_by_change_percent()
):
    print(stock)

三十四、综合案例三:交易订单类

定义抽象父类:

from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class Order(ABC):
    code: str
    quantity: int

    def __post_init__(self) -> None:
        if self.quantity <= 0:
            raise ValueError(
                "交易数量必须大于零"
            )

    @abstractmethod
    def execute(self) -> None:
        pass

市价单:

@dataclass
class MarketOrder(Order):
    def execute(self) -> None:
        print(
            f"按市价交易 "
            f"{self.code} "
            f"{self.quantity} 股"
        )

限价单:

@dataclass
class LimitOrder(Order):
    limit_price: float

    def __post_init__(self) -> None:
        super().__post_init__()

        if self.limit_price <= 0:
            raise ValueError(
                "限价必须大于零"
            )

    def execute(self) -> None:
        print(
            f"以 {self.limit_price:.2f} "
            f"限价交易 "
            f"{self.code} "
            f"{self.quantity} 股"
        )

统一执行:

def execute_orders(
    orders: list[Order],
) -> None:
    for order in orders:
        order.execute()

使用:

orders: list[Order] = [
    MarketOrder(
        code="600519",
        quantity=100,
    ),
    LimitOrder(
        code="000001",
        quantity=500,
        limit_price=11.5,
    ),
]

execute_orders(orders)

这体现了:

  • 抽象类
  • 继承
  • 方法重写
  • 多态

三十五、常见错误

1. 忘记写 self

错误:

class Person:
    def show_name():
        print("张三")

调用:

person = Person()
person.show_name()

会出现参数错误。

正确:

class Person:
    def show_name(self) -> None:
        print("张三")

2. 忘记使用 self 保存属性

错误:

class Person:
    def __init__(
        self,
        name: str,
    ) -> None:
        name = name

对象没有 name 属性。

正确:

self.name = name

3. 类属性误用可变对象

不推荐:

class Portfolio:
    stocks = []

所有对象共享同一个列表。

portfolio1 = Portfolio()
portfolio2 = Portfolio()

portfolio1.stocks.append(
    "600519"
)

print(portfolio2.stocks)

也会看到:

["600519"]

正确:

class Portfolio:
    def __init__(self) -> None:
        self.stocks = []

每个对象拥有独立列表。


4. 直接修改内部状态

不推荐:

stock._price = -100

应该通过公开接口:

stock.price = 100

或:

stock.update_price(100)

5. 继承层级过深

例如:

A
└── B
    └── C
        └── D
            └── E

方法来源难以判断,维护成本高。

优先使用较浅继承和组合。


6. 类只包含一个静态函数

例如:

class MathHelper:
    @staticmethod
    def add(a, b):
        return a + b

这种情况下普通函数更简单:

def add(a, b):
    return a + b

7. 类职责过多

如果一个类同时:

  • 读取文件
  • 抓取网页
  • 分析数据
  • 写数据库
  • 发送邮件

应该拆分。


三十六、练习题

练习一

创建一个 Person 类,包含:

  • 姓名
  • 年龄
  • 自我介绍方法

参考答案:

class Person:
    def __init__(
        self,
        name: str,
        age: int,
    ) -> None:
        self.name = name
        self.age = age

    def introduce(self) -> None:
        print(
            f"我叫{self.name},"
            f"今年{self.age}岁"
        )

练习二

创建一个 Rectangle 类,包含:

  • 宽度
  • 高度
  • 面积属性
  • 周长属性

参考答案:

class Rectangle:
    def __init__(
        self,
        width: float,
        height: float,
    ) -> None:
        if width <= 0 or height <= 0:
            raise ValueError(
                "宽度和高度必须大于零"
            )

        self.width = width
        self.height = height

    @property
    def area(self) -> float:
        return (
            self.width
            * self.height
        )

    @property
    def perimeter(self) -> float:
        return 2 * (
            self.width
            + self.height
        )

练习三

创建一个银行账户类,包含:

  • 账户名称
  • 余额
  • 存款
  • 取款
  • 查询余额

参考答案:

class BankAccount:
    def __init__(
        self,
        owner: str,
        balance: float = 0,
    ) -> None:
        if balance < 0:
            raise ValueError(
                "初始余额不能小于零"
            )

        self.owner = owner
        self._balance = balance

    @property
    def balance(self) -> float:
        return self._balance

    def deposit(
        self,
        amount: float,
    ) -> None:
        if amount <= 0:
            raise ValueError(
                "存款金额必须大于零"
            )

        self._balance += amount

    def withdraw(
        self,
        amount: float,
    ) -> None:
        if amount <= 0:
            raise ValueError(
                "取款金额必须大于零"
            )

        if amount > self._balance:
            raise ValueError(
                "余额不足"
            )

        self._balance -= amount

练习四

使用数据类创建股票对象。

参考答案:

from dataclasses import dataclass


@dataclass
class Stock:
    code: str
    name: str
    price: float

    def is_main_board(self) -> bool:
        return self.code.startswith(
            ("00", "60")
        )

练习五

创建父类 Animal,子类 DogCat,分别重写 speak()

参考答案:

class Animal:
    def speak(self) -> None:
        raise NotImplementedError


class Dog(Animal):
    def speak(self) -> None:
        print("汪汪")


class Cat(Animal):
    def speak(self) -> None:
        print("喵喵")

练习六

创建一个 Student 类,支持按照成绩比较大小。

参考答案:

class Student:
    def __init__(
        self,
        name: str,
        score: float,
    ) -> None:
        self.name = name
        self.score = score

    def __lt__(
        self,
        other: "Student",
    ) -> bool:
        return self.score < other.score

    def __repr__(self) -> str:
        return (
            f"Student("
            f"name={self.name!r}, "
            f"score={self.score!r}"
            f")"
        )

使用:

students = [
    Student("张三", 85),
    Student("李四", 92),
    Student("王五", 78),
]

students.sort()

三十七、类的核心总结

定义类:

class Person:
    pass

创建对象:

person = Person()

初始化:

class Person:
    def __init__(
        self,
        name: str,
    ) -> None:
        self.name = name

实例方法:

def introduce(self) -> None:
    print(self.name)

类属性:

class Person:
    category = "人类"

类方法:

@classmethod
def create(cls):
    return cls()

静态方法:

@staticmethod
def validate(value):
    return True

属性控制:

@property
def price(self):
    return self._price

继承:

class Dog(Animal):
    pass

调用父类:

super().__init__()

数据类:

@dataclass
class Stock:
    code: str
    name: str

面向对象的四个核心概念:

封装
继承
多态
抽象

实际设计时可以先问:

这个对象有哪些数据?
这个对象有哪些行为?
哪些数据需要校验?
哪些行为属于对象本身?
是否真的需要继承?
是否可以使用组合?
这个类是否承担了太多职责?

类的本质是:

数据
+
操作数据的方法
+
对象之间的关系

当程序从简单脚本发展为复杂系统时,合理使用类可以显著提高代码的结构性、可读性和可维护性。