Python基础知识10

在 Python 编程领域,内存模型紧密围绕对象展开,一切数据,无论是数字、字符串,还是列表等复杂结构,均以对象的形式存在于内存之中。透彻理解 Python 对象的内存管理机制,对于编写高效、稳定且安全的代码而言,具有举足轻重的意义。接下来,让我们深入探讨 Python 对象内存模型的核心要点、具体示例以及拓展方向。

1. 对象的基本属性

Python 对象具备三个至关重要的核心属性:

  • 标识(Identity):这是用于唯一标识对象的内存地址,通过内置函数id(obj)便可以获取。值得注意的是,一旦对象被创建,其标识就不可改变,如同对象在内存世界中的独特 “身份证号”。
  • 类型(Type):它决定了对象所能支持的各类操作,包括可调用的方法以及适用的运算符等。借助type(obj)函数,我们能够轻松获取对象的类型信息,从而知晓该对象具备哪些 “能力”。
  • 值(Value):即对象实际存储的具体数据内容。对象值的可变性由其类型决定,例如,int类型的对象值不可变,而list类型的对象值则是可变的。

示例 1:对象标识与值的变化

python




a = 10

print(id(a))  # 输出如 140736425405344(不可变对象地址)

a += 1

print(id(a))  # 地址变化,新对象被创建(输出如 140736425405376)


lst = [1, 2]

print(id(lst))  # 输出如 2101498034944

lst.append(3)

print(id(lst))  # 地址不变(仍为 2101498034944)

要点

  • 对于不可变对象(如int),当修改其值时,实际上是在内存中创建了一个全新的对象,对象的标识也随之改变。
  • 可变对象(如list)在修改值时,其标识保持不变,因为修改操作是在原对象的基础上进行的,并未重新创建对象。
2. 可变对象与不可变对象的内存行为
  • 不可变对象(Immutable)
    • 类型:常见的不可变对象类型包括int、float、str、tuple、frozenset。
    • 行为:不可变对象的值一旦确定,就无法直接修改。当对其进行某些操作(如字符串拼接)时,会创建一个新的对象来存储操作结果。

示例 2:字符串驻留(Interning)

python

s1 = "hello"

s2 = "hello"

print(s1 is s2)  # 输出 True(驻留优化,共享内存)


s3 = "hello!"

s4 = "hello!"

print(s3 is s4)  # Python 3.7+ 输出 False(长字符串可能不驻留)

要点: Python 为了优化内存使用,会对短字符串和小整数(范围为 -5 到 256)进行驻留优化。这意味着相同值的短字符串和小整数在内存中只会存在一个实例,多个引用共享同一内存地址,从而减少了重复对象的创建,节省了内存空间。

  • 可变对象(Mutable)
    • 类型:常见的可变对象类型有list、dict、set以及自定义类实例。
    • 行为:可变对象支持原地修改操作,即在不改变对象标识的前提下,直接修改对象内部的值。

示例 3:多个引用共享可变对象

python

a = [1, 2]

b = a

b.append(3)

print(a)  # 输出 [1, 2, 3](a 和 b 指向同一对象)

要点: 在 Python 中,赋值操作本质上是传递对象的引用,而不是创建对象的副本。因此,当多个变量引用同一个可变对象时,对其中任何一个变量所指向对象的修改,都会影响到其他所有引用该对象的变量。

3. 内存管理机制
  • 引用计数(Reference Counting)
    • 原理:每个 Python 对象都会记录自身被引用的次数。当对象的引用计数归零时,意味着不再有任何变量引用该对象,此时对象所占用的内存将被回收,以释放资源。
    • 手动操作:借助sys.getrefcount(obj)函数,我们可以查看指定对象的当前引用计数。

示例 4:引用计数变化

python

import sys


a = [1, 2]

print(sys.getrefcount(a))  # 输出 2(a 本身 + getrefcount 参数传递)

b = a

print(sys.getrefcount(a))  # 输出 3

del b

print(sys.getrefcount(a))  # 输出 2

要点: 在 Python 中,函数传参以及赋值操作都会导致对象的引用计数增加。而当使用del语句删除对对象的引用时,对象的引用计数会相应减少。

  • 垃圾回收(Garbage Collection)
    • 循环引用处理:Python 采用分代回收(Generational GC)算法来检测并清理那些由于循环引用而导致无法被正常访问的对象。循环引用是指两个或多个对象相互引用,形成一个封闭的引用环,使得这些对象的引用计数永远不会归零,从而无法通过引用计数机制被回收。
    • 手动触发:如果需要,我们可以手动调用gc.collect()函数来触发垃圾回收机制,强制回收那些不再使用的对象所占用的内存。

示例 5:循环引用与垃圾回收

python


import gc


class Node:

    def __init__(self):

        self.next = None


a = Node()

b = Node()

a.next = b

b.next = a  # 形成循环引用


del a, b

gc.collect()  # 强制回收,释放内存

要点: 对于存在循环引用的对象,仅依靠引用计数机制无法将其回收,必须借助垃圾回收器的分代回收算法来检测和处理,从而确保内存的有效管理。

4. 对象复制与内存优化
  • 浅拷贝(Shallow Copy)
    • 方法:可以使用copy.copy()函数,或者某些对象自身提供的copy()方法(如list.copy())来实现浅拷贝。
    • 行为:浅拷贝会复制对象的顶层结构,但对于嵌套对象,仍然会共享引用,即嵌套对象在原对象和拷贝对象之间是共用的。
  • 深拷贝(Deep Copy)
    • 方法:使用copy.deepcopy()函数来实现深拷贝。
    • 行为:深拷贝会递归地复制所有嵌套对象,从而创建一个完全独立的副本,原对象和拷贝对象之间不存在任何共享的引用。

示例 6:浅拷贝与深拷贝对比

python

import copy


lst1 = [1, [2, 3]]

lst2 = copy.copy(lst1)      # 浅拷贝

lst3 = copy.deepcopy(lst1)  # 深拷贝


lst1[1].append(4)

print(lst2[1])  # 输出 [2, 3, 4](共享嵌套列表)

print(lst3[1])  # 输出 [2, 3](独立副本)

要点: 在进行对象复制操作时,需要根据实际需求谨慎选择浅拷贝或深拷贝。如果对象结构简单且不存在嵌套可变对象,浅拷贝通常能够满足需求,并且效率更高;而当对象包含复杂的嵌套可变对象,且需要确保拷贝对象与原对象完全独立时,则应使用深拷贝。

内存优化技巧

  • slots:在自定义类中使用__slots__属性,可以限制类实例所能拥有的属性,从而减少内存占用。这是因为__slots__会为类实例分配固定的内存空间,而不是使用动态的字典来存储属性。

python

class User:

    __slots__ = ('name', 'age')  # 禁止动态添加属性

    def __init__(self, name, age):

        self.name = name

        self.age = age

        生成器(Generator):生成器采用惰性计算的方式,即在需要时才生成值,而不是一次性将所有值加载到内存中。这使得生成器在处理大规模数据时,能够显著节省内存资源。

python

def large_data():

    for i in range(10**6):

        yield i  # 逐个生成值,不一次性加载到内存

5. 高级内存分析工具
  • sys.getsizeof():该函数用于查看对象在内存中实际占用的字节大小。通过它,我们可以直观地了解不同对象的内存占用情况,从而为优化代码提供参考。

python

import sys

print(sys.getsizeof([1,2,3]))  # 输出如 88(单位:字节)

  • tracemalloc:tracemalloc模块可以用于跟踪程序运行过程中的内存分配情况,帮助我们定位内存使用的热点区域和潜在的内存泄漏问题。

python

import tracemalloc

tracemalloc.start()

data = [1] * 1000

snapshot = tracemalloc.take_snapshot()

for stat in snapshot.statistics('lineno'):

    print(stat)

6. 拓展方向
  1. 内存泄漏调试:利用objgraph库可以可视化对象之间的引用关系,从而更方便地检测和调试内存泄漏问题。通过分析对象引用图,我们能够快速定位那些由于意外引用而导致无法被回收的对象。
  2. CPython 源码分析:深入研究 CPython 的源代码,有助于我们理解 Python 对象的底层结构,例如PyObject结构体的定义和实现细节。这对于优化代码性能、深入理解 Python 内存管理机制具有重要意义。
  3. 与 C 扩展交互:借助ctypes或Cython等工具,我们可以在 Python 代码中直接操作内存,实现与 C 语言的高效交互。这在处理一些对性能要求极高的任务时,能够显著提升程序的执行效率。
7. 知识点总结
  1. 对象基本属性:Python 对象有标识(内存地址,id () 获取,创建后不变)、类型(决定操作,type () 获取)、值(可变性由类型决定)。不可变对象修改值创建新对象,可变对象修改值标识不变。
  2. 可变与不可变对象内存行为:不可变对象包括 int、float 等,操作会创建新对象,Python 对短字符串和小整数有驻留优化;可变对象有 list、dict 等,支持原地修改,赋值传引用。
  3. 内存管理机制:引用计数记录对象被引用次数,引用计数为零回收内存,函数传参、赋值增加引用计数;垃圾回收通过分代回收处理循环引用,可手动触发 gc.collect () 。
  4. 对象复制与内存优化:浅拷贝复制顶层对象,嵌套对象共享引用,深拷贝递归复制所有嵌套对象;__slots__限制类实例属性减少内存占用,生成器惰性计算节省内存。
  5. 高级内存分析工具:sys.getsizeof () 查看对象内存大小,tracemalloc 跟踪内存分配情况。
  6. 拓展方向:用 objgraph 调试内存泄漏,分析 CPython 源码理解对象底层结构,通过 ctypes 或 Cython 与 C 扩展交互。

你可能感兴趣的:(Python基础,python,开发语言,爬虫)