Python 类与面向对象
程序越来越大,光用函数会变得混乱。这时候需要"类"——把数据和操作数据的函数绑在一起,形成"对象"。这一章讲清 Python 面向对象的核心:类、对象、继承、魔术方法,以及现代 Python 的 dataclass。
1. 什么是类与对象?
类(class)是创建对象的模板(蓝图),对象(object)是按这个模板造出来的具体实例。比如"Dog 类"是狗的模板,而"旺财"是一只具体的狗对象。类把数据(属性)和行为(方法)封装在一起。
# 定义类:class 类名:
class Dog:
"""一只狗""" # 类的 docstring
# 类属性:所有实例共享
species = "Canis lupus familiaris"
# 构造方法:创建对象时自动调用
def __init__(self, name, age):
# self 代表"当前对象自己",类似 JavaScript 的 this
self.name = name # 实例属性
self.age = age
# 实例方法:第一个参数永远是 self
def bark(self):
return f"{self.name} 汪汪叫!"
def describe(self):
return f"{self.name}, {self.age} 岁"
# 创建对象(实例化)
d1 = Dog("旺财", 3)
d2 = Dog("小白", 5)
print(d1.bark()) # 旺财 汪汪叫!
print(d2.describe()) # 小白, 5 岁
# 访问属性
print(d1.name) # 旺财
print(d1.species) # Canis lupus familiaris (类属性,所有实例共享)几个要点:
__init__:构造方法,创建对象时自动调用,用来初始化属性。self:代表"当前对象自己",类似 JavaScript 的this。每个实例方法的第一个参数必须是 self。- 实例属性:每个对象独有的数据(旺财的名字 vs 小白的名字)。
- 类属性:所有对象共享的数据(
species)。
2. self 的本质
# self 是什么?
# self 就是"当前对象自己"的引用
# 调用 d.bark() 时,Python 自动把 d 作为 self 传进去
class Cat:
def __init__(self, name):
self.name = name
def meow(self):
# 这里的 self.name 就是当前对象的 name
return f"{self.name}: 喵~"
c = Cat("Tom")
# 下面两种调用等价:
print(c.meow()) # 自动传 self
print(Cat.meow(c)) # 手动传 self(很少这样写,理解原理用)
# ⚠ 忘记 self 是经典错误
class Bad:
def hello(): # ❌ 忘了 self
print("hi")
# Bad().hello() # TypeError: hello() takes 0 positional arguments but 1 was givend.bark() 等价于 Dog.bark(d)——Python 自动把对象 d 作为第一个参数(即 self)传进去。理解了这点,就理解了 Python 面向对象的底层。
3. 继承
继承让子类复用父类的代码,这是面向对象的核心价值之一。Python 支持单继承和多继承,这里先看单继承。
# 继承:子类继承父类的属性和方法
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} 在吃东西"
def sleep(self):
return f"{self.name} 在睡觉"
# 继承语法:class 子类(父类):
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类的 __init__
self.breed = breed
def bark(self):
return f"{self.name} 汪汪叫"
# 重写父类方法(覆盖)
def eat(self):
return f"{self.name} 在啃骨头"
class Cat(Animal):
def meow(self):
return f"{self.name} 喵喵叫"
# 子类有父类的所有方法
d = Dog("旺财", "金毛")
print(d.eat()) # 旺财 在啃骨头 (重写后的版本)
print(d.sleep()) # 旺财 在睡觉 (继承自 Animal)
print(d.bark()) # 旺财 汪汪叫
c = Cat("汤姆")
print(c.sleep()) # 汤姆 在睡觉 (继承自 Animal)关键点:
- 语法:
class 子类(父类): super():调用父类的方法(常用于__init__)。- 重写(override):子类定义同名方法,会覆盖父类的。
- 子类自动拥有父类的所有方法。
4. 魔术方法(dunder methods)
Python 不用 toString()、equals() 这种命名,而是用双下划线方法(__xxx__),叫"魔术方法"或"dunder"(double underscore)。它们定义了对象在特定场景的行为。
# 魔术方法(dunder methods):以双下划线开头和结尾
# 它们定义了对象在特定场景下的行为
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
# str:print(对象) 时显示什么
def __str__(self):
return f"《{self.title}》 by {self.author}"
# repr:开发者调试时看到的表示
def __repr__(self):
return f"Book({self.title!r}, {self.author!r})"
# len:支持 len(对象)
def __len__(self):
return self.pages
# eq:支持 ==
def __eq__(self, other):
return self.title == other.title and self.author == other.author
# lt:支持 < 和 sort
def __lt__(self, other):
return self.pages < other.pages
# add:支持 +
def __add__(self, other):
return Book(f"{self.title} & {other.title}", "合集", self.pages + other.pages)
b1 = Book("Python 入门", "Guido", 300)
b2 = Book("Python 入门", "Guido", 300)
b3 = Book("算法", "CLRS", 800)
print(b1) # 《Python 入门》 by Guido (__str__)
print(len(b1)) # 300 (__len__)
print(b1 == b2) # True (__eq__)
print(b1 < b3) # True (__lt__)
print(b1 + b3) # 《Python 入门 & 算法》 by 合集 (__add__)常用魔术方法:
__init__:构造__str__:print 时显示(给人看)__repr__:调试时显示(给程序员看)__len__:支持len(obj)__eq__/__lt__:支持==/</ sort__add__/__sub__:支持+/-__getitem__:支持obj[key]__iter__:支持 for 循环遍历__enter__/__exit__:支持with语句
5. 实例方法 / 类方法 / 静态方法
class MyClass:
count = 0 # 类属性
def __init__(self):
MyClass.count += 1
# 实例方法:第一个参数是 self,操作实例
def instance_method(self):
return f"实例方法,访问 {self}"
# 类方法:第一个参数是类本身(用 cls),操作类
@classmethod
def how_many(cls):
return f"已创建 {cls.count} 个实例"
# 静态方法:不接收 self 也不接收 cls,像普通函数
@staticmethod
def helper():
return "我是辅助函数,不需要实例或类"
# 创建几个实例
a = MyClass()
b = MyClass()
c = MyClass()
print(MyClass.how_many()) # 已创建 3 个实例
print(a.instance_method()) # 实例方法,访问 <__main__.MyClass object>
print(MyClass.helper()) # 我是辅助函数,不需要实例或类
# 类方法常用作"工厂方法"(替代构造函数)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, birth_year):
import datetime
age = datetime.datetime.now().year - birth_year
return cls(name, age) # 用 cls 创建实例
p = Person.from_birth_year("小明", 2000)
print(f"{p.name}, {p.age} 岁")6. 属性 @property
把方法变成"属性"访问(像字段一样),还能在赋值时校验。这是 Python 的优雅封装方式。
class Circle:
def __init__(self, radius):
self._radius = radius # _ 前缀表示"内部使用"(约定,不强制)
# @property:把方法变成"属性"访问
@property
def radius(self):
return self._radius
# @属性名.setter:允许赋值
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("半径必须大于 0")
self._radius = value
# 只读属性:只定义 @property,不定义 setter
@property
def area(self):
import math
return math.pi * self._radius ** 2
c = Circle(5)
print(c.radius) # 5 (像属性一样访问,不加括号)
print(c.area) # 78.54 (计算属性)
c.radius = 10 # 走 setter,会校验
print(c.area) # 314.16
# c.radius = -1 # ❌ ValueError: 半径必须大于 0
# c.area = 100 # ❌ AttributeError: 只读属性不能赋值7. dataclass(Python 3.7+):数据类神器
写"主要用来存数据"的类时,dataclass 能自动生成 __init__、__repr__、__eq__,省去一堆样板代码。
# dataclass(Python 3.7+):自动生成 __init__/__repr__/__eq__
# 写"数据类"的神器,省去一堆样板代码
from dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
score: float = 0.0 # 默认值
# 自动有了 __init__
s1 = Student("小明", 20, 85.5)
s2 = Student("小红", 22)
print(s1) # Student(name='小明', age=20, score=85.5) 自动 __repr__
print(s2.score) # 0.0 用默认值
# 自动 __eq__
s3 = Student("小明", 20, 85.5)
print(s1 == s3) # True
# 对比:不用 dataclass 要写一大堆
class StudentOld:
def __init__(self, name, age, score=0.0):
self.name = name
self.age = age
self.score = score
def __repr__(self):
return f"StudentOld({self.name}, ...)"
def __eq__(self, other):
return self.name == other.name and self.age == other.age and self.score == other.scoredataclass 是现代 Python 写数据类的首选,绝大多数场景都该用它替代手写样板。
小结
面向对象是组织大规模代码的核心。重点掌握:类与对象、__init__ 与 self、继承与 super、重写、常用魔术方法(__str__/__repr__/__eq__/__len__)、类方法/静态方法、@property、dataclass。下一篇看文件操作——读写本地数据。
← 上一篇 Python 模块与包
下一篇 Python 文件操作 →