本文来源公众号“python”,仅用于学术分享,侵权删,干货满满。
原文链接:Python性能优化:使用__slots__减少内存占用
在Python的面向对象编程中,内存使用效率和属性访问控制始终是开发者关注的重要话题。Python提供了一个强大而特殊的机制——__slots__
,它能够显著优化类实例的内存使用,同时提供精确的属性访问控制。这一机制在处理大量对象实例、构建高性能应用程序以及需要严格控制对象属性的场景中发挥着至关重要的作用。
核心原理
1、Python对象的默认存储机制
通常情况下,Python对象的属性存储在一个名为__dict__
的字典中,这个字典为每个实例提供了动态的属性存储空间。虽然这种机制提供了极大的灵活性,允许在运行时动态添加和删除属性,但也带来了额外的内存开销。
每个Python对象实例不仅需要存储实际的属性值,还需要维护一个字典结构来管理这些属性。字典本身具有一定的内存开销,包括哈希表的存储空间和相关的管理信息。当应用程序需要创建大量对象实例时,这些开销会累积成为显著的内存消耗。
import sys
# 演示普通类的内存使用情况
class RegularPerson:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
# 创建实例并检查内存使用
regular_person = RegularPerson("张三", 30, "zhangsan@example.com")
# 查看对象的__dict__属性
print("普通类实例的__dict__:")
print(regular_person.__dict__)
# 测量对象大小
print(f"普通类实例大小: {sys.getsizeof(regular_person)} 字节")
print(f"__dict__字典大小: {sys.getsizeof(regular_person.__dict__)} 字节")
# 动态添加属性演示
regular_person.phone = "12345678901"
print(f"添加属性后的__dict__: {regular_person.__dict__}")
2、__slots__的工作原理与优势
__slots__
机制通过预先声明类实例可以拥有的属性名称,从根本上改变了Python对象的存储方式。当类定义了__slots__
时,Python解释器不再为每个实例创建__dict__
字典,而是使用更加紧凑的存储结构来直接存储属性值。
# 使用__slots__优化的类定义
class SlottedPerson:
__slots__ = ['name', 'age', 'email']
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def get_info(self):
returnf"姓名: {self.name}, 年龄: {self.age}, 邮箱: {self.email}"
# 创建使用__slots__的实例
slotted_person = SlottedPerson("李四", 25, "lisi@example.com")
# 验证__slots__的效果
print("使用__slots__的类实例:")
print(f"实例大小: {sys.getsizeof(slotted_person)} 字节")
# 检查是否存在__dict__
try:
print(slotted_person.__dict__)
except AttributeError as e:
print(f"__dict__不存在: {e}")
# 尝试动态添加属性
try:
slotted_person.phone = "12345678901"
except AttributeError as e:
print(f"无法动态添加属性: {e}")
print(f"正常属性访问: {slotted_person.get_info()}")
内存优化的实现
1、内存使用量对比分析
在实际项目中,当需要处理成千上万甚至更多的对象实例时,内存优化的效果会变得非常明显。
import tracemalloc
import gc
# 启动内存跟踪
tracemalloc.start()
# 定义测试用的普通类
class RegularPoint:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
# 定义使用__slots__的类
class SlottedPoint:
__slots__ = ['x', 'y', 'z']
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
# 测试函数:创建大量对象并测量内存使用
def memory_test(point_class, count=10000):
gc.collect() # 强制垃圾回收
snapshot_before = tracemalloc.take_snapshot()
# 创建大量对象
points = []
for i in range(count):
point = point_class(i, i*2, i*3)
points.append(point)
snapshot_after = tracemalloc.take_snapshot()
# 计算内存使用差异
top_stats = snapshot_after.compare_to(snapshot_before, 'lineno')
total_memory = sum(stat.size for stat in top_stats)
return total_memory, points
# 执行内存测试
print("开始内存使用对比测试...")
regular_memory, regular_points = memory_test(RegularPoint)
print(f"普通类创建10000个对象的内存使用: {regular_memory / 1024:.2f} KB")
slotted_memory, slotted_points = memory_test(SlottedPoint)
print(f"__slots__类创建10000个对象的内存使用: {slotted_memory / 1024:.2f} KB")
if regular_memory > 0:
savings = (1 - slotted_memory / regular_memory) * 100
print(f"内存节省比例: {savings:.1f}%")
# 单个对象大小对比
single_regular = RegularPoint(1, 2, 3)
single_slotted = SlottedPoint(1, 2, 3)
print(f"\n单个普通对象大小: {sys.getsizeof(single_regular)} 字节")
print(f"单个__slots__对象大小: {sys.getsizeof(single_slotted)} 字节")
2、属性访问速度优化
除了内存使用的优化,__slots__
还能够提升属性访问的速度。由于属性值直接存储在固定位置而非通过字典查找,属性的读取和写入操作都能获得性能提升。
import time
# 创建性能测试用的类
class RegularEmployee:
def __init__(self, emp_id, name, department, salary):
self.emp_id = emp_id
self.name = name
self.department = department
self.salary = salary
class SlottedEmployee:
__slots__ = ['emp_id', 'name', 'department', 'salary']
def __init__(self, emp_id, name, department, salary):
self.emp_id = emp_id
self.name = name
self.department = department
self.salary = salary
# 性能测试函数
def attribute_access_benchmark(employee_class, iterations=1000000):
employee = employee_class(1001, "王五", "技术部", 50000)
start_time = time.time()
# 执行大量属性访问操作
for _ in range(iterations):
name = employee.name
dept = employee.department
salary = employee.salary
employee.salary = salary + 1
end_time = time.time()
return end_time - start_time
# 执行性能基准测试
print("属性访问速度对比测试:")
regular_time = attribute_access_benchmark(RegularEmployee)
print(f"普通类属性访问时间: {regular_time:.4f} 秒")
slotted_time = attribute_access_benchmark(SlottedEmployee)
print(f"__slots__类属性访问时间: {slotted_time:.4f} 秒")
if regular_time > slotted_time:
speedup = (regular_time - slotted_time) / regular_time * 100
print(f"速度提升: {speedup:.1f}%")
属性访问控制的高级应用
1、严格的属性定义与验证
__slots__
不仅提供内存优化,还能实现严格的属性访问控制。通过预先定义允许的属性名称,可以防止程序运行时意外添加不必要的属性,这有助于维护代码的一致性和可靠性。
# 演示__slots__的属性控制功能
class RestrictedConfig:
__slots__ = ['host', 'port', 'username', 'password', 'timeout']
def __init__(self, host='localhost', port=8080, username='admin',
password='', timeout=30):
self.host = host
self.port = port
self.username = username
self.password = password
self.timeout = timeout
def validate(self):
"""验证配置的有效性"""
ifnot isinstance(self.host, str) ornot self.host:
raise ValueError("主机地址必须是非空字符串")
ifnot isinstance(self.port, int) ornot (1 <= self.port <= 65535):
raise ValueError("端口号必须是1-65535之间的整数")
ifnot isinstance(self.timeout, (int, float)) or self.timeout <= 0:
raise ValueError("超时时间必须是正数")
returnTrue
# 创建配置实例并测试
config = RestrictedConfig(host="192.168.1.100", port=3306,
username="database_user", password="secret123",
timeout=60)
print("配置对象创建成功:")
print(f"主机: {config.host}")
print(f"端口: {config.port}")
print(f"用户名: {config.username}")
print(f"超时: {config.timeout}")
# 验证配置
try:
config.validate()
print("配置验证通过")
except ValueError as e:
print(f"配置验证失败: {e}")
# 尝试添加未定义的属性
try:
config.debug_mode = True
except AttributeError as e:
print(f"属性控制生效: {e}")
# 演示属性修改
config.timeout = 120
print(f"修改后的超时时间: {config.timeout}")
2、继承中的__slots__处理
在类继承体系中使用__slots__
需要特别注意一些规则和最佳实践。子类如果也要使用__slots__
,需要明确声明自己的槽位,而父类的槽位会自动继承。
# 基类定义
class Vehicle:
__slots__ = ['brand', 'model', 'year']
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
def get_basic_info(self):
returnf"{self.year} {self.brand} {self.model}"
# 子类继承并扩展__slots__
class Car(Vehicle):
__slots__ = ['doors', 'fuel_type'] # 只需要声明新增的槽位
def __init__(self, brand, model, year, doors, fuel_type):
super().__init__(brand, model, year)
self.doors = doors
self.fuel_type = fuel_type
def get_full_info(self):
basic = self.get_basic_info()
returnf"{basic} - {self.doors}门 {self.fuel_type}"
# 另一个子类
class Motorcycle(Vehicle):
__slots__ = ['engine_size', 'has_sidecar']
def __init__(self, brand, model, year, engine_size, has_sidecar=False):
super().__init__(brand, model, year)
self.engine_size = engine_size
self.has_sidecar = has_sidecar
def get_full_info(self):
basic = self.get_basic_info()
sidecar = "带边车"if self.has_sidecar else"无边车"
returnf"{basic} - {self.engine_size}cc {sidecar}"
# 测试继承体系
car = Car("丰田", "凯美瑞", 2023, 4, "汽油")
motorcycle = Motorcycle("雅马哈", "YZF-R1", 2023, 998, False)
print("汽车信息:")
print(car.get_full_info())
print(f"汽车对象大小: {sys.getsizeof(car)} 字节")
print("\n摩托车信息:")
print(motorcycle.get_full_info())
print(f"摩托车对象大小: {sys.getsizeof(motorcycle)} 字节")
# 验证属性访问控制
try:
car.license_plate = "京A12345"
except AttributeError as e:
print(f"\n汽车类属性控制: {e}")
try:
motorcycle.color = "红色"
except AttributeError as e:
print(f"摩托车类属性控制: {e}")
总结
Python的__slots__
机制为开发者提供了一个强大的内存优化和属性访问控制工具。通过预先声明类属性,__slots__
能够显著减少内存使用量,提高属性访问速度,并提供严格的属性控制机制。在处理大量对象实例的应用场景中,合理使用__slots__
能够带来显著的性能提升。使用__slots__
也需要权衡灵活性与性能的关系,确保在获得优化收益的同时不影响代码的可维护性和扩展性。
THE END !
文章结束,感谢阅读。您的点赞,收藏,评论是我继续更新的动力。大家有推荐的公众号可以评论区留言,共同学习,一起进步。