跳到主要内容

Python 面向对象编程

面向对象编程(OOP)是一种程序设计思想。本章将介绍类和对象的核心概念。

类和对象基础

什么是类?

类是一种抽象的数据类型,它定义了一类事物的共同属性和行为。

什么是对象?

对象是类的实例,是具体的存在。

定义类

class Person:
"""人类"""

# 类属性(所有实例共享)
species = "智人"

# 初始化方法
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age

# 实例方法
def say_hello(self):
print(f"你好,我叫{self.name},今年{self.age}岁")

# 私有方法(以双下划线开头)
def __private_method(self):
print("这是私有方法")

# 魔术方法
def __str__(self):
return f"Person: {self.name}, {self.age}"

创建对象

person = Person("张三", 20)
print(person.name) # 张三
print(person.age) # 20
person.say_hello() # 你好,我叫张三,今年20岁
print(person) # Person: 张三, 20

self 参数

self 指向当前对象本身,类似于其他语言中的 this

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

def bark(self):
# self 指向调用此方法的对象
print(f"{self.name} 叫了一声")

初始化方法 init

__init__ 在创建对象时自动调用,用于初始化对象的属性。

class Car:
def __init__(self, brand, color):
self.brand = brand
self.color = color
self.speed = 0

def drive(self):
print(f"{self.color}{self.brand}汽车正在行驶")

car = Car("比亚迪", "红色")
car.drive() # 红色的比亚迪汽车正在行驶

属性

实例属性

每个对象独有的属性:

class Person:
def __init__(self, name):
self.name = name # 实例属性

person1 = Person("张三")
person2 = Person("李四")
print(person1.name) # 张三
print(person2.name) # 李四

类属性

所有对象共享的属性:

class Person:
species = "智人" # 类属性

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

print(Person.species) # 智人
person = Person("张三")
print(person.species) # 智人(通过实例访问类属性)

属性访问优先级

实例属性 > 类属性

class Person:
name = "人类" # 类属性

def __init__(self, name):
self.name = name # 实例属性

person = Person("张三")
print(person.name) # 张三(优先访问实例属性)
print(Person.name) # 人类(访问类属性)

使用 property 装饰器

class Circle:
def __init__(self, radius):
self._radius = radius

@property
def radius(self):
return self._radius

@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负数")
self._radius = value

@property
def area(self):
return 3.14 * self._radius ** 2

circle = Circle(5)
print(circle.radius) # 5
print(circle.area) # 78.5
circle.radius = 10
print(circle.area) # 314.0

方法类型

实例方法

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

def say_hello(self): # 实例方法
print(f"你好,我叫{self.name}")

类方法

使用 @classmethod 装饰器,第一个参数是类本身:

class Person:
count = 0 # 类属性

def __init__(self, name):
self.name = name
Person.count += 1

@classmethod
def get_count(cls):
return cls.count

print(Person.get_count()) # 0
person = Person("张三")
print(Person.get_count()) # 1

静态方法

使用 @staticmethod 装饰器,不需要 self 或 cls 参数:

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

result = Math.add(3, 5) # 8
print(Math.add(1, 2)) # 3

访问权限

公有属性和方法

class Person:
def __init__(self, name):
self.name = name # 公有属性

def public_method(self): # 公有方法
print("公有方法")

私有属性和方法

使用双下划线 __ 前缀:

class Person:
def __init__(self, name):
self.__name = name # 私有属性

def __private_method(self): # 私有方法
print("私有方法")

def public_method(self):
# 可以在类内部访问私有成员
print(f"姓名:{self.__name}")
self.__private_method()

person = Person("张三")
person.public_method() # 姓名:张三 私有方法
# person.__name # 错误:无法直接访问
# person.__private_method() # 错误:无法直接访问

访问私有属性(不推荐)

# 使用 name mangling
person._Person__name # 张三
person._Person__private_method() # 私有方法

保护属性

使用单下划线 _ 前缀,表示protected(受保护):

class Person:
def __init__(self, name):
self._name = name # 保护属性

person = Person("张三")
print(person._name) # 可以访问,但不推荐

继承

基本语法

# 父类
class Animal:
def __init__(self, name):
self.name = name

def eat(self):
print(f"{self.name}在吃饭")

def sleep(self):
print(f"{self.name}在睡觉")

# 子类
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类的初始化方法
self.breed = breed # 子类新增属性

def bark(self): # 子类新增方法
print(f"{self.name}叫了一声")

def eat(self): # 重写父类方法
print(f"{self.name}正在吃狗粮")

dog = Dog("旺财", "金毛")
dog.eat() # 旺财正在吃狗粮(重写的方法)
dog.sleep() # 旺财在睡觉(继承的方法)
dog.bark() # 旺财叫了一声(子类新增的方法)

多继承

class Flyable:
def fly(self):
print("可以飞")

class Swimmable:
def swim(self):
print("可以游泳")

class Duck(Animal, Flyable, Swimmable):
pass

duck = Duck("鸭子")
duck.eat()
duck.fly()
duck.swim()

方法解析顺序(MRO)

class A:
pass

class B(A):
pass

class C(A):
pass

class D(B, C):
pass

print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

多态

同一接口,不同实现:

class Animal:
def speak(self):
pass

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

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

class Cow(Animal):
def speak(self):
print("哞哞")

# 多态调用
animals = [Dog(), Cat(), Cow()]
for animal in animals:
animal.speak()

# 输出:
# 汪汪
# 喵喵
# 哞哞

特殊方法(魔术方法)

方法描述示例
__init__初始化方法对象创建时调用
__str__字符串表示print(obj) 时调用
__repr__调试字符串repr(obj) 时调用
__len__长度len(obj) 时调用
__add__加法obj1 + obj2 时调用
__eq__相等obj1 == obj2 时调用
__lt__小于obj1 < obj2 时调用

示例

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __str__(self):
return f"Vector({self.x}, {self.y})"

def __repr__(self):
return f"Vector({self.x}, {self.y})"

def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)

def __eq__(self, other):
return self.x == other.x and self.y == other.y

def __len__(self):
return 2

def __getitem__(self, index):
if index == 0:
return self.x
elif index == 1:
return self.y
else:
raise IndexError("索引超出范围")

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1) # Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(v1 == v2) # False
print(len(v1)) # 2
print(v1[0]) # 1

抽象类和接口

使用 abc 模块创建抽象类:

from abc import ABC, abstractmethod

class Shape(ABC):
@abstractmethod
def area(self):
pass

@abstractmethod
def perimeter(self):
pass

class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height

def area(self):
return self.width * self.height

def perimeter(self):
return 2 * (self.width + self.height)

rect = Rectangle(5, 3)
print(rect.area()) # 15
print(rect.perimeter()) # 16

# 抽象类不能直接实例化
# shape = Shape() # 错误

数据类(dataclass)

Python 3.7 引入了 dataclasses 模块,可以自动生成 __init____repr____eq__ 等特殊方法,大大简化了数据类的定义。

基本用法

from dataclasses import dataclass

@dataclass
class Person:
name: str
age: int
city: str = "未知"

# 自动生成了 __init__ 方法
person = Person("张三", 25)
print(person) # Person(name='张三', age=25, city='未知')

# 自动生成了 __eq__ 方法
person2 = Person("张三", 25)
print(person == person2) # True

对比传统写法,可以看到 dataclass 的简洁性:

# 传统写法:需要手动编写多个方法
class PersonTraditional:
def __init__(self, name: str, age: int, city: str = "未知"):
self.name = name
self.age = age
self.city = city

def __repr__(self):
return f"PersonTraditional(name={self.name!r}, age={self.age!r}, city={self.city!r})"

def __eq__(self, other):
if not isinstance(other, PersonTraditional):
return False
return (self.name, self.age, self.city) == (other.name, other.age, other.city)

field 函数详解

使用 field() 函数可以对字段进行更精细的控制:

from dataclasses import dataclass, field

@dataclass
class Student:
name: str
age: int
# 默认值
grade: str = "大一"
# 使用 default_factory 创建可变默认值
scores: list = field(default_factory=list)
# 排除某些字段
_internal_id: int = field(default=0, repr=False)
# 不参与初始化
full_name: str = field(init=False)
# 不参与比较
email: str = field(compare=False, default="")

def __post_init__(self):
# 初始化后处理
self.full_name = f"学生:{self.name}"

student = Student("张三", 20)
print(student) # Student(name='张三', age=20, grade='大一', scores=[], full_name='学生:张三', email='')

为什么需要 default_factory?

Python 中可变对象作为默认值会导致所有实例共享同一个对象:

# 错误示例
class Wrong:
items = [] # 所有实例共享同一个列表

w1, w2 = Wrong(), Wrong()
w1.items.append(1)
print(w2.items) # [1] - 被污染了!

# 正确示例:使用 default_factory
@dataclass
class Correct:
items: list = field(default_factory=list)

c1, c2 = Correct(), Correct()
c1.items.append(1)
print(c2.items) # [] - 独立的列表

dataclass 参数

@dataclass(
init=True, # 生成 __init__ 方法
repr=True, # 生成 __repr__ 方法
eq=True, # 生成 __eq__ 方法
order=False, # 生成 __lt__、__le__、__gt__、__ge__ 方法
frozen=False, # 创建不可变实例
slots=False, # 使用 __slots__ 优化内存
)
class Config:
host: str
port: int = 8080

# frozen=True 创建不可变实例
@dataclass(frozen=True)
class Point:
x: float
y: float

p = Point(1.0, 2.0)
# p.x = 3.0 # FrozenInstanceError: cannot assign to field 'x'

# order=True 支持排序
@dataclass(order=True)
class Version:
major: int
minor: int

v1 = Version(1, 0)
v2 = Version(2, 0)
print(v1 < v2) # True

继承

dataclass 支持继承,子类会继承父类的所有字段:

@dataclass
class Animal:
name: str
age: int

@dataclass
class Dog(Animal):
breed: str

dog = Dog("旺财", 3, "金毛")
print(dog) # Dog(name='旺财', age=3, breed='金毛')

实用函数

from dataclasses import dataclass, asdict, astuple, fields, replace

@dataclass
class Person:
name: str
age: int

person = Person("张三", 25)

# 转换为字典
print(asdict(person)) # {'name': '张三', 'age': 25}

# 转换为元组
print(astuple(person)) # ('张三', 25)

# 获取字段信息
for f in fields(person):
print(f.name, f.type) # name <class 'str'> age <class 'int'>

# 创建修改后的副本
new_person = replace(person, age=26)
print(new_person) # Person(name='张三', age=26)

描述符(Descriptor)

描述符是 Python 中实现属性访问控制的底层机制。理解描述符有助于深入理解 propertyclassmethodstaticmethod 等的实现原理。

什么是描述符?

描述符是一个实现了 __get____set____delete__ 方法的类。当描述符作为另一个类的类属性时,对这些属性的访问会被描述符拦截。

class Descriptor:
"""简单的描述符示例"""

def __get__(self, obj, owner):
print(f"获取值,obj={obj}, owner={owner}")
return obj._value if obj else None

def __set__(self, obj, value):
print(f"设置值:{value}")
obj._value = value

def __delete__(self, obj):
print("删除值")
del obj._value

class MyClass:
attr = Descriptor() # 描述符作为类属性

obj = MyClass()
obj.attr = 10 # 调用 __set__
print(obj.attr) # 调用 __get__
del obj.attr # 调用 __delete__

数据描述符与非数据描述符

根据实现的方法不同,描述符分为两类:

数据描述符:同时实现 __get____set__,优先级最高。

非数据描述符:只实现 __get__,优先级低于实例属性。

# 数据描述符
class DataDescriptor:
def __get__(self, obj, owner):
return "数据描述符的值"

def __set__(self, obj, value):
print(f"数据描述符设置值:{value}")

# 非数据描述符
class NonDataDescriptor:
def __get__(self, obj, owner):
return "非数据描述符的值"

class Example:
data_desc = DataDescriptor()
non_data_desc = NonDataDescriptor()

e = Example()

# 数据描述符优先级高于实例属性
e.__dict__['data_desc'] = "实例属性"
print(e.data_desc) # 数据描述符的值(数据描述符优先)

# 非数据描述符优先级低于实例属性
e.__dict__['non_data_desc'] = "实例属性"
print(e.non_data_desc) # 实例属性(实例属性优先)

实际应用:类型检查

class Typed:
"""类型检查描述符"""

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

def __get__(self, obj, owner):
if obj is None:
return self
return obj.__dict__.get(self.name)

def __set__(self, obj, value):
if not isinstance(value, self.expected_type):
raise TypeError(f"{self.name} 应该是 {self.expected_type} 类型")
obj.__dict__[self.name] = value

class Person:
name = Typed("name", str)
age = Typed("age", int)

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

person = Person("张三", 25)
# person.age = "二十五" # TypeError: age 应该是 <class 'int'> 类型

实际应用:延迟计算属性

class LazyProperty:
"""延迟计算属性描述符"""

def __init__(self, func):
self.func = func
self.attr_name = func.__name__

def __get__(self, obj, owner):
if obj is None:
return self
# 计算并缓存结果
value = self.func(obj)
obj.__dict__[self.attr_name] = value
return value

class Circle:
def __init__(self, radius):
self.radius = radius

@LazyProperty
def area(self):
print("计算面积...")
return 3.14159 * self.radius ** 2

circle = Circle(5)
print(circle.area) # 计算面积... 78.53975
print(circle.area) # 78.53975(第二次直接从缓存获取)

property 的实现原理

property 本质上是一个描述符:

class MyProperty:
"""模拟 property 的实现"""

def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel

def __get__(self, obj, owner):
if obj is None:
return self
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("属性只读")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("属性不可删除")
self.fdel(obj)

def setter(self, fset):
self.fset = fset
return self

class Circle:
def __init__(self, radius):
self._radius = radius

@MyProperty
def radius(self):
return self._radius

@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负")
self._radius = value

c = Circle(5)
print(c.radius) # 5
c.radius = 10
print(c.radius) # 10

slots 优化

使用 __slots__ 可以限制类的属性,并减少内存占用。

基本用法

class Point:
__slots__ = ['x', 'y'] # 只允许 x 和 y 属性

def __init__(self, x, y):
self.x = x
self.y = y

p = Point(1, 2)
print(p.x, p.y) # 1 2

# p.z = 3 # AttributeError: 'Point' has no attribute 'z'

内存优化效果

import sys

# 普通类
class NormalPoint:
def __init__(self, x, y):
self.x = x
self.y = y

# 使用 __slots__ 的类
class SlotPoint:
__slots__ = ['x', 'y']

def __init__(self, x, y):
self.x = x
self.y = y

n = NormalPoint(1, 2)
s = SlotPoint(1, 2)

print(f"普通类实例大小: {sys.getsizeof(n)} 字节")
print(f"slots类实例大小: {sys.getsizeof(s)} 字节")

# 创建大量实例时的内存差异
normal_points = [NormalPoint(i, i) for i in range(100000)]
slot_points = [SlotPoint(i, i) for i in range(100000)]

slots 的限制

class Example:
__slots__ = ['x', 'y']

# 无法使用 __dict__
# print(__dict__) # 不存在

e = Example()
e.x = 1
# e.z = 3 # AttributeError

# 无法动态添加属性
# e.new_attr = 10 # AttributeError

继承时的注意事项

class Base:
__slots__ = ['x']

class Derived(Base):
__slots__ = ['y'] # 子类也需要定义 __slots__

d = Derived()
d.x = 1
d.y = 2

# 如果子类不定义 __slots__,会有 __dict__
class DerivedNoSlots(Base):
pass

d2 = DerivedNoSlots()
d2.x = 1
d2.z = 3 # 可以,因为有 __dict__

属性访问控制

getattrgetattribute

这两个方法用于控制属性访问,但触发时机不同:

  • __getattribute__:访问任何属性时都会触发
  • __getattr__:只有属性不存在时才触发
class Example:
def __init__(self):
self.exists = "存在的属性"

def __getattribute__(self, name):
print(f"__getattribute__ 被调用:{name}")
return super().__getattribute__(name)

def __getattr__(self, name):
print(f"__getattr__ 被调用:{name}")
return f"属性 {name} 不存在"

e = Example()
print(e.exists) # 先调用 __getattribute__,属性存在,返回值
print(e.missing) # 先调用 __getattribute__,再调用 __getattr__

动态属性代理

class Proxy:
"""代理模式:将属性访问转发给目标对象"""

def __init__(self, target):
self._target = target

def __getattr__(self, name):
# 当代理对象没有该属性时,转发给目标对象
return getattr(self._target, name)

def __setattr__(self, name, value):
if name == '_target':
super().__setattr__(name, value)
else:
setattr(self._target, name, value)

class Target:
def __init__(self):
self.value = 100

target = Target()
proxy = Proxy(target)

print(proxy.value) # 100(从 target 获取)
proxy.value = 200
print(target.value) # 200(修改了 target)

setattrdelattr

class Protected:
def __init__(self):
self._data = {}

def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value)
else:
self._data[name] = value
print(f"设置属性 {name} = {value}")

def __getattr__(self, name):
if name in self._data:
return self._data[name]
raise AttributeError(f"没有属性 {name}")

def __delattr__(self, name):
if name in self._data:
del self._data[name]
print(f"删除属性 {name}")
else:
super().__delattr__(name)

p = Protected()
p.x = 10 # 设置属性 x = 10
print(p.x) # 10
del p.x # 删除属性 x

元类(Metaclass)

元类是创建类的"类"。理解元类需要明白:在 Python 中,类也是对象,而元类就是创建这些类对象的类。

类的本质

class MyClass:
pass

obj = MyClass()

# 类是 type 的实例
print(type(MyClass)) # <class 'type'>
print(type(obj)) # <class '__main__.MyClass'>

# 类可以动态创建
DynamicClass = type('DynamicClass', (), {'x': 10})
print(DynamicClass.x) # 10

使用元类

class Meta(type):
"""自定义元类"""

def __new__(mcs, name, bases, namespace):
print(f"创建类: {name}")
print(f"基类: {bases}")
print(f"命名空间: {namespace}")

# 可以修改类的创建过程
namespace['added_by_meta'] = "元类添加的属性"

return super().__new__(mcs, name, bases, namespace)

def __init__(cls, name, bases, namespace):
print(f"初始化类: {name}")
super().__init__(name, bases, namespace)

class MyClass(metaclass=Meta):
x = 10

def method(self):
pass

# 输出:
# 创建类: MyClass
# 基类: ()
# 命名空间: {'x': 10, 'method': <function...>}
# 初始化类: MyClass

print(MyClass.added_by_meta) # 元类添加的属性

元类应用:单例模式

class SingletonMeta(type):
"""单例元类"""

_instances = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]

class Database(metaclass=SingletonMeta):
def __init__(self):
print("初始化数据库连接")

db1 = Database() # 初始化数据库连接
db2 = Database() # 不会再次初始化
print(db1 is db2) # True

元类应用:自动注册

class RegistryMeta(type):
"""自动注册元类"""

registry = {}

def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if name != 'BasePlugin': # 不注册基类
mcs.registry[name] = cls
return cls

class BasePlugin(metaclass=RegistryMeta):
"""插件基类"""

def run(self):
raise NotImplementedError

class PluginA(BasePlugin):
def run(self):
return "插件 A 运行"

class PluginB(BasePlugin):
def run(self):
return "插件 B 运行"

# 自动注册
print(RegistryMeta.registry)
# {'PluginA': <class 'PluginA'>, 'PluginB': <class 'PluginB'>}

# 通过名称获取插件
plugin = RegistryMeta.registry['PluginA']()
print(plugin.run()) # 插件 A 运行

元类应用:接口检查

class InterfaceMeta(type):
"""接口检查元类"""

def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)

if bases: # 不是接口本身
# 检查是否实现了所有抽象方法
interface = bases[0]
if hasattr(interface, '_abstract_methods'):
for method in interface._abstract_methods:
if method not in namespace:
raise TypeError(
f"{name} 必须实现抽象方法 {method}"
)
return cls

class Animal(metaclass=InterfaceMeta):
_abstract_methods = ['speak', 'move']

class Dog(Animal):
def speak(self):
return "汪汪"

def move(self):
return "跑"

# class Cat(Animal): # TypeError: Cat 必须实现抽象方法 speak
# def move(self):
# return "走"

dog = Dog()
print(dog.speak()) # 汪汪

上下文管理器

通过实现 __enter____exit__ 方法,可以让对象支持 with 语句。

基本实现

class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None

def __enter__(self):
print("打开文件")
self.file = open(self.filename, self.mode)
return self.file

def __exit__(self, exc_type, exc_val, exc_tb):
print("关闭文件")
if self.file:
self.file.close()
# 返回 False 会重新抛出异常
# 返回 True 会抑制异常
return False

with FileManager("test.txt", "w") as f:
f.write("Hello")
# 输出:
# 打开文件
# 关闭文件

异常处理

class SuppressError:
def __init__(self, error_type):
self.error_type = error_type

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is self.error_type:
print(f"抑制了异常: {exc_val}")
return True # 抑制异常
return False # 其他异常正常抛出

with SuppressError(ValueError):
raise ValueError("测试异常")
print("继续执行") # 会执行,异常被抑制

contextlib 模块

使用 contextlib 可以更简洁地创建上下文管理器:

from contextlib import contextmanager

@contextmanager
def timer():
import time
start = time.time()
yield # 在这里执行 with 块中的代码
end = time.time()
print(f"耗时: {end - start:.2f} 秒")

with timer():
sum(range(10000000))
# 耗时: 0.15 秒

小结

本章我们学习了:

  1. 类和对象的概念
  2. 类的定义和对象的创建
  3. 初始化方法 __init__
  4. 属性(实例属性、类属性、property)
  5. 方法类型(实例方法、类方法、静态方法)
  6. 访问权限(公有、私有、保护)
  7. 继承和多继承
  8. 多态
  9. 特殊方法(魔术方法)
  10. 抽象类
  11. 数据类(dataclass)
  12. 描述符(Descriptor)
  13. slots 优化
  14. 属性访问控制(getattrgetattribute
  15. 元类(Metaclass)
  16. 上下文管理器

练习

  1. 创建一个学生类,包含姓名、学号、成绩属性
  2. 创建一个银行账户类,支持存款、取款、查询余额
  3. 实现一个图形类层次结构(圆形、矩形、三角形)
  4. 实现一个栈数据结构(压入、弹出、查看栈顶)
  5. 使用 dataclass 重写学生类
  6. 实现一个类型检查描述符
  7. 使用元类实现一个简单的 ORM 框架