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 中实现属性访问控制的底层机制。理解描述符有助于深入理解 property、classmethod、staticmethod 等的实现原理。
什么是描述符?
描述符是一个实现了 __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__
属性访问控制
getattr 和 getattribute
这两个方法用于控制属性访问,但触发时机不同:
__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)
setattr 和 delattr
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 秒
小结
本章我们学习了:
- 类和对象的概念
- 类的定义和对象的创建
- 初始化方法
__init__ - 属性(实例属性、类属性、property)
- 方法类型(实例方法、类方法、静态方法)
- 访问权限(公有、私有、保护)
- 继承和多继承
- 多态
- 特殊方法(魔术方法)
- 抽象类
- 数据类(dataclass)
- 描述符(Descriptor)
- slots 优化
- 属性访问控制(getattr、getattribute)
- 元类(Metaclass)
- 上下文管理器
练习
- 创建一个学生类,包含姓名、学号、成绩属性
- 创建一个银行账户类,支持存款、取款、查询余额
- 实现一个图形类层次结构(圆形、矩形、三角形)
- 实现一个栈数据结构(压入、弹出、查看栈顶)
- 使用 dataclass 重写学生类
- 实现一个类型检查描述符
- 使用元类实现一个简单的 ORM 框架