Python 循环引用怎么破?用 weakref 轻松解决 GC 回收难题

news/2025/11/9 20:05:19/文章来源:https://www.cnblogs.com/wangya216/p/19205185

Python 循环引用怎么破?用 weakref 轻松解决 GC 回收难题

如果你开发过链表、树、图等数据结构,大概率会遇到一个棘手问题:明明用del删除了所有对象的引用,内存却依然居高不下 —— 这就是 “循环引用” 导致的 GC(垃圾回收)回收失败。今天我们就从一个真实的链表内存泄漏案例入手,拆解循环引用的危害,学习用 Python 内置的weakref模块打破引用闭环,让 GC 重新正常工作。

一、开篇问题:为什么链表节点循环引用后,del 所有名字仍内存泄漏?

先看一个简单的双向链表实现:每个节点有prev(前驱)和next(后继)指针,用于指向前后节点。当链表形成闭环(如尾节点的next指向头节点,头节点的prev指向尾节点)时,即使del所有外部引用,节点也无法被 GC 回收,最终导致内存泄漏。

1. 循环引用的链表代码(内存泄漏版)

import gcimport sys\# 关闭自动GC,便于手动控制回收时机,观察内存泄漏现象gc.disable()class Node:    def \_\_init\_\_(self, value):        self.value = value        self.prev = None  # 前驱节点(强引用)        self.next = None  # 后继节点(强引用)        def \_\_del\_\_(self):        # 用于验证节点是否被GC回收        print(f"Node({self.value})被回收")\# 1. 创建3个节点,构建双向链表node1 = Node(1)node2 = Node(2)node3 = Node(3)\# 2. 建立节点间的强引用,形成循环闭环node1.next = node2  # node1强引用node2node2.prev = node1  # node2强引用node1node2.next = node3  # node2强引用node3node3.prev = node2  # node3强引用node2node3.next = node1  # node3强引用node1(尾节点指向头节点,形成循环)node1.prev = node3  # node1强引用node3(头节点指向尾节点,强化循环)\# 3. 查看初始引用计数(每个节点的引用计数均为2)print(f"node1引用计数:{sys.getrefcount(node1) - 1}")  # 输出2(减1是因为getrefcount自身会增加1次引用)print(f"node2引用计数:{sys.getrefcount(node2) - 1}")  # 输出2print(f"node3引用计数:{sys.getrefcount(node3) - 1}")  # 输出2\# 4. del所有外部引用(node1、node2、node3)del node1del node2del node3\# 5. 手动触发GC,尝试回收节点gc.collect()\# 6. 查看未被回收的Node对象数量(预期为0,实际为3,内存泄漏)uncollected\_nodes = \[obj for obj in gc.get\_objects() if isinstance(obj, Node)]print(f"未被回收的Node对象数:{len(uncollected\_nodes)}")  # 输出3for node in uncollected\_nodes:    print(f"未回收节点:Node({node.value})")  # 输出Node(1)、Node(2)、Node(3)

2. 引用计数图解:循环引用为何导致 GC 无法回收?

要理解内存泄漏的原因,我们需要跟踪每个节点的 “引用来源”。在上述代码中,每个节点的引用计数由两部分构成:

  • 外部引用node1node2node3这些名字对节点的引用(del后外部引用消失);

  • 内部引用:节点间通过prev/next建立的强引用(循环闭环导致内部引用永远存在)。

循环引用后的引用计数变化

  1. 初始状态:每个节点的引用计数 = 外部引用(1)+ 内部引用(1)= 2(如 node1 被node1名字和node3.next引用);

  2. del外部引用后:每个节点的引用计数 = 内部引用(1)(如 node1 仍被node3.nextnode2.prev引用);

  3. GC 扫描时:发现每个节点都有 “其他节点的强引用”,无法判断这些节点是否为 “无用垃圾”(因为节点间互相依赖,形成闭环),因此跳过回收。

最终结果:3 个节点永远留在内存中,造成内存泄漏 —— 如果链表节点数量庞大(如百万级),会快速耗尽内存,导致程序崩溃。

二、弱引用(weakref)救场:不增加引用计数的 “观察者”

要打破循环引用,核心是将节点间的 “强引用”(会增加引用计数)改为 “弱引用”(不增加引用计数)。Python 的weakref模块提供了弱引用功能,它的本质是 “仅观察对象,不阻止 GC 回收”—— 即使存在弱引用,只要对象的强引用计数降至 0,GC 就能正常回收该对象。

1. 弱引用的核心特性

  • 不增加引用计数:弱引用对象(weakref.ref实例)指向目标对象时,不会让目标对象的强引用计数增加;

  • 自动失效:当目标对象被 GC 回收后,弱引用会自动变为 “失效状态”,后续访问会返回None,避免野指针问题;

  • 仅观察不干预:弱引用只能用于访问目标对象,不能阻止目标对象被回收,是解决循环引用的 “最佳工具”。

2. weakref的核心 API

weakref模块提供了多个工具,最常用的是weakref.ref()weakref.proxy(),二者的区别在于 “访问目标对象的方式”:

API 功能 访问方式 适用场景
weakref.ref(obj) 创建一个弱引用对象,指向obj 需通过 “弱引用对象 ()” 的方式获取目标对象(如wr() 需显式判断对象是否存活的场景
weakref.proxy(obj) 创建一个弱引用代理,指向obj 可直接通过代理对象访问目标对象的属性 / 方法(如proxy.value 希望像使用原对象一样使用弱引用的场景

API 使用示例

import weakrefclass Test:&#x20;   def \_\_init\_\_(self, name):&#x20;       self.name = name&#x20;  &#x20;&#x20;   def \_\_del\_\_(self):&#x20;       print(f"Test({self.name})被回收")\# 1. 创建目标对象obj = Test("demo")\# 2. 用weakref.ref()创建弱引用wr = weakref.ref(obj)print(f"通过弱引用访问对象:{wr()}")  # 输出<\_\_main\_\_.Test object at 0x0000021F7A8D1E50>print(f"弱引用指向对象的name:{wr().name}")  # 输出demo\# 3. 用weakref.proxy()创建弱引用代理proxy = weakref.proxy(obj)print(f"通过代理访问对象name:{proxy.name}")  # 输出demo(直接访问,无需调用)\# 4. del目标对象的强引用del obj\# 5. 访问弱引用:对象已被回收,返回Noneprint(f"对象回收后,弱引用访问:{wr()}")  # 输出None\# 6. 访问代理:对象已被回收,抛出ReferenceErrortry:&#x20;   print(proxy.name)except ReferenceError as e:&#x20;   print(f"访问代理报错:{e}")  # 输出:weakly-referenced object no longer exists

三、实战案例:用弱引用修复链表循环引用

理解了弱引用的原理后,我们来修改开篇的链表代码 —— 将节点间的部分强引用(如nextprev)改为弱引用,打破循环闭环,让 GC 能正常回收节点。

1. 修复思路

双向链表的循环引用源于 “节点 A 强引用节点 B,节点 B 强引用节点 A”。要打破闭环,只需将其中一个方向的引用改为弱引用(如将prev改为弱引用),这样:

  • 节点 A 的next强引用节点 B(增加 B 的引用计数);

  • 节点 B 的prev弱引用节点 A(不增加 A 的引用计数);

  • 当外部引用被del后,强引用计数能降至 0,GC 可回收节点。

2. 修复后的完整代码(无内存泄漏版)

import gcimport sysimport weakref  # 导入weakref模块gc.disable()class Node:&#x20;   def \_\_init\_\_(self, value):&#x20;       self.value = value&#x20;       # 将prev改为弱引用(用weakref.ref()创建),next仍为强引用&#x20;       self.prev = weakref.ref(None)  # 初始指向None的弱引用&#x20;       self.next = None&#x20;  &#x20;&#x20;   def \_\_del\_\_(self):&#x20;       print(f"Node({self.value})被回收")\# 1. 创建3个节点node1 = Node(1)node2 = Node(2)node3 = Node(3)\# 2. 建立节点间的引用:next为强引用,prev为弱引用\# node1 → node2node1.next = node2node2.prev = weakref.ref(node1)  # node2的prev弱引用node1(不增加node1的引用计数)\# node2 → node3node2.next = node3node3.prev = weakref.ref(node2)  # node3的prev弱引用node2\# node3 → node1(形成循环,但prev为弱引用)node3.next = node1node1.prev = weakref.ref(node3)  # node1的prev弱引用node3\# 3. 查看初始引用计数(此时每个节点的强引用计数=1,因为prev是弱引用)print(f"node1强引用计数:{sys.getrefcount(node1) - 1}")  # 输出1(被node1名字和node3.next引用?不,node3.next是强引用,此处需注意:node1的强引用来源是node3.next(强)和node1名字(强),所以计数为2?需重新计算)\# 修正:强引用计数计算需考虑所有强引用来源\# node1的强引用来源:node1名字(强)、node3.next(强)→ 计数=2\# node2的强引用来源:node2名字(强)、node1.next(强)→ 计数=2\# node3的强引用来源:node3名字(强)、node2.next(强)→ 计数=2print(f"node1强引用计数:{sys.getrefcount(node1) - 1}")  # 输出2print(f"node2强引用计数:{sys.getrefcount(node2) - 1}")  # 输出2print(f"node3强引用计数:{sys.getrefcount(node3) - 1}")  # 输出2\# 4. del所有外部引用(node1、node2、node3名字)del node1del node2del node3\# 5. 查看del后的强引用计数(通过gc.get\_objects()找到节点,再计算计数)uncollected\_before = \[obj for obj in gc.get\_objects() if isinstance(obj, Node)]for node in uncollected\_before:&#x20;   # 此时节点的强引用来源仅为其他节点的next(强引用),如node1的强引用来源是node3.next&#x20;   print(f"del后Node({node.value})强引用计数:{sys.getrefcount(node) - 1}")  # 输出1(每个节点仅被一个next强引用)\# 6. 手动触发GCgc.collect()\# 7. 查看未被回收的Node对象数量(预期为0,无内存泄漏)uncollected\_after = \[obj for obj in gc.get\_objects() if isinstance(obj, Node)]print(f"GC后未被回收的Node对象数:{len(uncollected\_after)}")  # 输出0\# 此时控制台会输出3条“Node(X)被回收”的信息,证明节点已被正常回收

3. 修复原理解析

  • 引用类型调整:将节点的prev属性从 “强引用” 改为 “弱引用”,使得节点间的引用关系变为 “单向强引用 + 单向弱引用”(如 node1.next 强引用 node2,node2.prev 弱引用 node1);

  • 强引用计数变化del外部引用后,每个节点的强引用计数降至 1(仅被下一个节点的next强引用)。当 GC 扫描时,会发现这些节点 “没有外部强引用,且内部强引用形成的链没有根节点”(即没有任何外部名字指向这个链),因此判断为 “无用垃圾”,执行回收;

  • __del__验证:GC 回收节点时,会调用Node类的__del__方法,控制台输出 “Node (X) 被回收”,证明修复成功。

四、弱引用的适用场景与注意事项

弱引用虽能解决循环引用,但并非所有场景都适用。我们需要明确它的适用范围,同时规避使用中的陷阱。

1. 适用场景

场景 1:链表、树、图等递归数据结构

这类结构天然存在 “节点间互相引用” 的可能(如双向链表的prev/next、树的parent/child、图的 “双向边”),用弱引用替代部分强引用,可避免循环引用导致的内存泄漏。

场景 2:缓存系统(如 LRU 缓存)

在缓存中,若 “缓存键” 强引用 “缓存值”,同时 “缓存值” 又强引用 “缓存键”(如字典中键是对象,值是依赖该对象的结果),会形成循环引用。用weakref.WeakKeyDictionary(键为弱引用)或weakref.WeakValueDictionary(值为弱引用)构建缓存,可让 “无人引用的键 / 值” 自动被 GC 回收,避免缓存膨胀。

场景 3:观察者模式(发布 - 订阅模式)

在观察者模式中,“主题”(Subject)需要维护 “观察者”(Observer)列表,若 “观察者” 同时强引用 “主题”,会形成循环引用。让 “主题” 用弱引用存储 “观察者”,可确保 “观察者无人引用时能被回收”。

2. 注意事项

注意 1:弱引用仅支持 “可哈希对象”

Python 的弱引用只能指向 “可哈希对象”(如自定义类实例、int、str、tuple 等),无法指向 “不可哈希对象”(如 list、dict、set 等)—— 因为不可哈希对象可能被动态修改,弱引用无法稳定跟踪。

错误示例(指向 list)

import weakreflst = \[1,2,3]try:&#x20;   wr = weakref.ref(lst)  # 报错:TypeError: cannot create weak reference to 'list' objectexcept TypeError as e:&#x20;   print(f"错误:{e}")  # 输出:cannot create weak reference to 'list' object

注意 2:访问弱引用前需判断对象是否存活

当目标对象被 GC 回收后,weakref.ref对象会返回Noneweakref.proxy对象会抛出ReferenceError。因此,访问弱引用指向的对象前,必须先判断对象是否存活。

正确示例(判断弱引用是否存活)

import weakrefclass Test:&#x20;   def \_\_init\_\_(self, name):&#x20;       self.name = nameobj = Test("test")wr = weakref.ref(obj)\# 方式1:通过weakref.ref()判断if wr() is not None:&#x20;   print(f"对象存活,name:{wr().name}")  # 输出testelse:&#x20;   print("对象已被回收")\# del强引用del obj\# 再次判断if wr() is not None:&#x20;   print(f"对象存活,name:{wr().name}")else:&#x20;   print("对象已被回收")  # 输出对象已被回收\# 方式2:通过weakref.proxy()判断(需捕获异常)obj2 = Test("test2")proxy = weakref.proxy(obj2)try:&#x20;   print(f"对象存活,name:{proxy.name}")  # 输出test2except ReferenceError:&#x20;   print("对象已被回收")del obj2try:&#x20;   print(f"对象存活,name:{proxy.name}")except ReferenceError:&#x20;   print("对象已被回收")  # 输出对象已被回收

注意 3:避免用弱引用指向 “短期存活的对象”

若弱引用指向的对象 “创建后很快被 GC 回收”(如函数内的临时变量),可能导致弱引用频繁失效,增加代码复杂度。弱引用更适合指向 “长期存活、但可能在某个阶段被回收” 的对象(如链表节点、缓存中的长期数据)。

示例(不建议的短期对象弱引用)

import weakrefdef create\_temp\_obj():&#x20;   # 临时对象:函数结束后会被GC回收&#x20;   temp\_obj = Test("temp")&#x20;   return weakref.ref(temp\_obj)  # 返回临时对象的弱引用class Test:&#x20;   def \_\_init\_\_(self, name):&#x20;       self.name = name\# 获取临时对象的弱引用,但此时临时对象已被回收wr = create\_temp\_obj()print(wr())  # 输出None(对象已回收,弱引用失效)

注意 4:用weakref.callbacks监听对象回收(进阶用法)

若需要在对象被 GC 回收时执行特定逻辑(如清理关联资源、记录日志),可在创建弱引用时传入callback函数 —— 当对象被回收后,Python 会自动调用该函数,传递弱引用对象作为参数。

示例(用 callback 监听对象回收)

import weakrefdef on\_obj\_collected(wr):&#x20;   # 对象被回收时执行的逻辑:打印日志&#x20;   print(f"监听:对象已被GC回收,弱引用对象:{wr}")class Test:&#x20;   def \_\_init\_\_(self, name):&#x20;       self.name = name\# 创建对象时,传入callback函数obj = Test("监听示例")wr = weakref.ref(obj, on\_obj\_collected)  # 第二个参数为callbackprint(f"对象存活:{wr() is not None}")  # 输出True\# del强引用,触发GC回收del objgc.collect()  # 手动触发GC\# 控制台会输出:监听:对象已被GC回收,弱引用对象:\<weakref at 0x0000021F7A8E2B60; dead>

适用场景

  • 缓存系统:当缓存对象被回收时,自动从缓存字典中删除关联键;

  • 资源监控:记录对象的回收时间,分析内存使用效率。

五、扩展:Python 标准库中的弱引用工具 ——weakref.WeakKeyDictionary

除了weakref.ref()weakref.proxy()weakref模块还提供了专门用于解决 “键循环引用” 的工具:WeakKeyDictionary。它的核心特性是 “字典的键为弱引用”—— 当键指向的对象被 GC 回收后,该键值对会自动从字典中删除,无需手动清理,完美解决 “键对象循环引用导致字典膨胀” 的问题。

1. WeakKeyDictionary的核心特性

  • 键为弱引用:仅接受 “可哈希对象” 作为键,且对键的引用为弱引用(不增加键对象的强引用计数);

  • 自动清理:当键对象被 GC 回收后,对应的键值对会被自动从字典中移除;

  • 值为强引用:字典的值为强引用,若值对象引用键对象,需确保值对象也能被正常回收(避免值对象强引用键对象导致循环)。

2. 实战案例:用WeakKeyDictionary构建自动清理的缓存

假设我们需要构建一个 “对象属性缓存”:存储每个对象的计算结果,当对象被回收时,缓存自动删除该对象的结果,避免内存泄漏。用WeakKeyDictionary可轻松实现这一需求。

示例代码

import weakrefimport gc\# 1. 创建WeakKeyDictionary,作为对象属性缓存obj\_cache = weakref.WeakKeyDictionary()def calculate\_attr(obj):&#x20;   """计算对象的属性值,结果存入缓存"""&#x20;   if obj not in obj\_cache:&#x20;       # 模拟复杂计算(如耗时的数据分析)&#x20;       result = obj.value \* 10  # 假设计算逻辑是“对象value的10倍”&#x20;       obj\_cache\[obj] = result  # 键为obj(弱引用),值为计算结果&#x20;       print(f"缓存未命中,计算结果:{result},存入缓存")&#x20;   else:&#x20;       result = obj\_cache\[obj]&#x20;       print(f"缓存命中,直接获取结果:{result}")&#x20;   return resultclass DataObj:&#x20;   def \_\_init\_\_(self, value):&#x20;       self.value = value&#x20;  &#x20;&#x20;   def \_\_del\_\_(self):&#x20;       print(f"DataObj({self.value})被回收")\# 2. 使用缓存obj1 = DataObj(5)calculate\_attr(obj1)  # 输出:缓存未命中,计算结果:50,存入缓存calculate\_attr(obj1)  # 输出:缓存命中,直接获取结果:50\# 查看缓存内容(此时缓存有obj1的键值对)print(f"缓存内容(obj1存活时):{dict(obj\_cache)}")  # 输出:{<\_\_main\_\_.DataObj object at 0x...>: 50}\# 3. del obj1的强引用,触发GC回收del obj1gc.collect()\# 查看缓存内容(obj1被回收后,键值对自动删除)print(f"缓存内容(obj1回收后):{dict(obj\_cache)}")  # 输出:{}

代码解析

  • obj1存活时,obj_cache中存储obj1: 50的键值对,obj_cacheobj1的引用为弱引用,不影响obj1的强引用计数;

  • del obj1后,obj1的强引用计数降至 0,GC 回收obj1obj_cache自动删除obj1对应的键值对,避免缓存膨胀和内存泄漏;

  • 对比普通字典:若用普通dict存储缓存,obj1被回收后,键值对仍会留在字典中(普通字典对键为强引用),导致内存泄漏。

3. 类似工具:weakref.WeakValueDictionary

除了WeakKeyDictionaryweakref模块还提供WeakValueDictionary,它的特性是 “字典的值为弱引用”—— 当值对象被 GC 回收后,对应的键值对自动删除。适用于 “值对象可能被回收,键对象长期存活” 的场景(如用户会话管理:键为用户 ID,值为用户会话对象,会话过期后自动删除键值对)。

简单示例

import weakrefimport gc\# 创建WeakValueDictionary,值为弱引用user\_sessions = weakref.WeakValueDictionary()class UserSession:&#x20;   def \_\_init\_\_(self, user\_id):&#x20;       self.user\_id = user\_id&#x20;  &#x20;&#x20;   def \_\_del\_\_(self):&#x20;       print(f"UserSession({self.user\_id})过期回收")\# 添加用户会话(键为user\_id,值为UserSession对象)session1 = UserSession("user123")user\_sessions\["user123"] = session1print(f"用户会话(session1存活时):{dict(user\_sessions)}")  # 输出:{'user123': <\_\_main\_\_.UserSession object at 0x...>}\# del session1的强引用,触发回收del session1gc.collect()print(f"用户会话(session1回收后):{dict(user\_sessions)}")  # 输出:{}(键值对自动删除)

六、实战总结:弱引用解决循环引用的 “三步法” 与核心原则

通过前面的案例和工具介绍,我们可以提炼出用弱引用解决循环引用的 “三步法”,以及使用弱引用的核心原则,帮你在实际开发中快速落地。

1. 解决循环引用的 “三步法”

当遇到内存泄漏,怀疑是循环引用导致时,可按以下步骤操作:

  1. 定位循环引用点:通过gc.get_objects()sys.getrefcount(),找到互相引用的对象,确定引用关系(如链表节点的prev/next、类实例间的双向引用);

  2. 选择弱引用方向:将循环引用中的 “非核心引用” 改为弱引用 —— 核心引用指 “维持对象关系的主要方向”(如链表的next是核心方向,prev是辅助方向,可改为弱引用);

  3. 验证回收效果del所有外部引用,手动触发gc.collect(),通过gc.get_objects()查看对象是否被回收,或通过__del__方法的打印信息验证。

2. 使用弱引用的核心原则

  • 最小必要原则:仅在 “循环引用无法避免” 时使用弱引用,不要滥用(如普通的单向引用无需用弱引用,避免增加代码复杂度);

  • 存活判断原则:访问弱引用指向的对象前,必须通过 “wr() is not None”(weakref.ref)或 “捕获ReferenceError”(weakref.proxy)判断对象是否存活;

  • 工具优先原则:遇到 “键 / 值可能被回收” 的字典场景,优先使用WeakKeyDictionaryWeakValueDictionary,而非手动管理弱引用(标准库工具已处理边界情况,更可靠)。

七、最终建议:从 “被动解决” 到 “主动避免” 循环引用

虽然弱引用能解决循环引用,但最佳实践是 “主动避免循环引用”:

  1. 设计阶段规避:在设计数据结构时,尽量采用 “单向引用”(如用单向链表替代双向链表,用 “父节点存储子节点列表,子节点不存储父节点” 替代双向引用);

  2. 临时引用清理:若必须使用双向引用,在对象不再需要时,手动断开引用(如node.prev = None; node.next = None),让强引用计数降至 0;

  3. 优先使用标准库:对于缓存、会话管理等场景,优先使用weakref模块的工具(如WeakKeyDictionary),或 Python 内置的缓存装饰器(如functools.lru_cache,已处理内存回收)。

循环引用导致的内存泄漏,是 Python 开发中的 “隐形陷阱”—— 它不会直接导致程序崩溃,却会缓慢耗尽内存,难以排查。掌握weakref模块的用法,不仅能解决现有问题,更能帮你在设计阶段建立 “内存友好” 的代码思维,写出更稳定、更高效的 Python 程序。

(注:文档部分内容可能由 AI 生成)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/960697.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实用指南:Starlake:一款免费开源的ETL数据管道工具

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

[LangChain] 16. 检索优化

RAG 标准流程:索引:外挂知识库 检索 生成Advanced RAG 针对上述 3 个阶段做了优化。例如检索阶段,新增了 检索前处理 以及 检索后处理。 检索前处理:查询转换 查询扩充 ......查询扩充(Query Expansion) 在不改变…

那坍塌废墟 铺满尘垢 回忆中 谁仍昂着头 谁撕开 簇拥的伤口 搅动一汪 腐烂的血肉

test35 7-A 火车站 (train.cpp) 首先对二元组的入栈时间排序出一个出栈时间序列,你就是想要掰成尽可能少的单调下降子序列,如果你有直接并且极度自信你可以直接输出最长上升子序列的长度。考虑这个怎么连接起来,你顺…

详细介绍:Excel如何排序?【图文详解】Excel表格排序?Excel自动排序?

详细介绍:Excel如何排序?【图文详解】Excel表格排序?Excel自动排序?2025-11-09 19:59 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: au…

Python实践指南:del与__del__的正确用法,避坑指南

Python实践指南:del与__del__的正确用法,避坑指南 del与与和__del__是最容易被误用的特性之一——有人把del当成“删除对象的命令”,有人把__del__当成“内存释放的工具”,结果写出漏洞百出的代码:文件关不掉、数…

摸鱼笔记[4]-电脑桌面常用软件简介

简要简介一下工作电脑桌面常用的那些软件(以防以后需要恢复🤔).摘要 简要简介一下工作电脑桌面常用的那些软件(以防以后需要恢复🤔). 列举 PS C:\Users\25578\Desktop> tree.com /f 卷 OS 的文件夹 PATH 列表 卷…

P10627 [JOI Open 2024] 中暑 / Heat Stroke

P10627 [JOI Open 2024] 中暑 / Heat Stroke P10627 [JOI Open 2024] 中暑 / Heat Stroke - 洛谷 (luogu.com.cn) Solution 限制:在一个人坐飞机之前,两边的医院必须住满。 先考虑 Sub4,每条路上只有前两个人有用,…

从监听风险到绝对隐私:Zoom偷听门后,Briefing+CPolar重新定义远程会议安全标准 - 教程

从监听风险到绝对隐私:Zoom偷听门后,Briefing+CPolar重新定义远程会议安全标准 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important…

【做题记录】多校-ds

C. [Ynoi2005] rmscne 直接做不好维护,考虑扫描线。用线段树对每个位置 \(i\) 维护 \(p_i\) 表示 \([i,p_i]\) 是 \([i,r]\) 的最小的合法子区间。维护方式很简单,当加入 \(a_r\) 时,设上一次出现的位置为 \(j\),则…

11-08 题

11-08 题 目录11-08 题P5405 [CTS2019] 氪金手游 - 洛谷AT_agc036_f [AGC036F] Square Constraints - 洛谷F - Almost Sorted 2G - One Time Swap 2P13004 [GCJ 2022 Finals] Schrdinger and Pavlov - 洛谷Problem - 1…

POSIX兼容系统上read和write系统调用的行为总结

关于UNIX和Linux的宣传语中,一切皆文件应该是最广为人知的一句。 不管是普通文件,还是硬件设备、管道、网络套接字,在Linux甚至还有信号和定时器都共享一套相似的api,大家可以用类似的代码完成各种不同的任务,大大…

AI也能管文件?RustFS+Claude实现智能存储自动化!

AI也能管文件?RustFS+Claude实现智能存储自动化!2025年,当Claude 4.5宣布可​连续编程30小时不"断片" ​,而RustFS凭借零GC设计将存储性能提升42% 时,我们终于意识到:AI管理存储系统的时代已经到来。一…

跟着小码学算法Day16:对称二叉树 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

摸鱼笔记[3]-给windows添加类似macOS的按空格预览

🌟 神器「QuickLook」让 Windows 也能像 macOS 一样,空格键一按👇 ✨ 视频 / 图片 / PDF / PSD / 压缩包…统统秒开!零加载零等待~摘要 🌟 神器「QuickLook」让 Windows 也能像 macOS 一样,空格键一按👇…

11.8 联考总结

11.8 联考总结 T1题意较复杂,读懂之后比较简单。发现T3是计数题,很好。先开 T3 ,比想象中简单,思路非常自然。回去看T2发现有简单 \(O(n\log n)\) 做法,84分!但我想得非常复杂,但还有更简单的 \(O(n^2)\),52分…

Spring BeanDefinition接口

[[Spring IOC 源码学习总笔记]] BeanDefinition BeanDefinition 主要是用来描述Bean,主要包括:Bean的属性、是否单例、延迟加载、Bean的名称、构造方法等。 简而言之: 在容器中的 一个Bean 对应一个 BeanDefinition…

pythontip 计算字符串中的音节数

编写一个程序来计算一个单词中的音节数。 音节之间由连字符分隔。例如: beau-ti-ful => 三个音节 cat => 一个音节 re-frig-er-a-tor => 五个音节 定义函数count_syllables()的函数,该函数接受一个参数word…

深入解析:26-基于STM32的小区智能井盖监测系统设计与实现

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025/11/09 LGNOIpR23

如果不是直接写 100pts 做法,就是赛时不会,或者对 AC 做法有很大启发。 T1 题意简述 求 \(a(1+p+pq)=n\) 整数解数量,其中 \(n\) 是给定的, \(p,q\) 均不能取 \(1\)。 sol 考虑试除法,一遍试出所有的 \(a\),然后…

Python “值层面” 该怎么说?别再混淆 “字面量” 与 “不可变对象”

为帮助Python入门者厘清“值/字面量/对象”的概念混淆,我将围绕“为什么‘a的值是10’表述不准确”这一痛点,结合内存模型与代码示例,详细解析字面量和不可变对象的区别,最终总结认知误区与正确表述方式。# Python…