类装饰器单例中isinstance报错因返回函数而非类,应改用MyClass.__wrapped__;__new__误传参数需只传cls;多线程失效须用双重检查锁;模块级单例最轻量但不支持参数化。

类装饰器实现单例时 isinstance() 报错
常见错误是直接用 @singleton 装饰类后,再写 isinstance(obj, MyClass),会触发 TypeError: isinstance() arg 2 must be a type...。这是因为装饰器返回的是个函数(get_instance),不是原始类本身。
解决办法:用 MyClass.__wrapped__ 替代 MyClass 做类型检查 —— 这是 functools.wraps 保留的原始类引用。
- 不要写
isinstance(a1, MyClass) - 要写
isinstance(a1, MyClass.__wrapped__)或更稳妥地用type(a1) is MyClass.__wrapped__ - 如果必须暴露原类名,可在装饰器里显式设置
cls.__name__ = original.__name__,但不推荐绕过类型系统
__new__ 中误传参数导致 TypeError: object() takes no parameters
这个报错几乎都出在重写 __new__ 时,把 super().__new__(cls, *args, **kwargs) 写成了 super().__new__(cls, *args, **kwargs) —— 看似一样,实则 object.__new__ 不接受除 cls 外的任何参数。哪怕 __init__ 需要参数,__new__ 也必须只传 cls。
正确写法是:
立即学习“Python免费学习笔记(深入)”;
def __new__(cls, *args, **kwargs):
if not cls._instance:
# 注意:这里不能传 *args/**kwargs 给 super().__new__
cls._instance = super().__new__(cls)
return cls._instance- 所有初始化逻辑(含参数校验、属性赋值)必须留在
__init__中 - 如果
__init__被多次调用(比如没加守卫),就加个if not hasattr(self, '_initialized'):控制 - 别手滑把
__new__写成__int__或漏掉下划线
多线程环境下单例失效的根源和修复
裸用 if not _instance: _instance = cls() 在并发场景下会创建多个实例,因为判断和赋值不是原子操作。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
必须引入线程锁,且要用双重检查(double-checked locking)避免每次获取都加锁:
- 外层
if cls not in _instances:快速路径,无锁 - 内层
with _lock:块中再次检查,防止重复初始化 - 锁对象(
_lock)必须是模块级或类级的,不能在闭包里每次重建 - 推荐用
threading.Lock(),别用RLock—— 单例初始化不需要可重入
比装饰器和 __new__ 更轻量的方案:模块级单例
Python 模块天然单例,只要把实例放在模块顶层,导入即得唯一对象,完全规避线程安全问题和类型检查陷阱。
例如 db.py:
import sqlite3
_connection = sqlite3.connect("app.db")
class DB:
def query(self, sql): ...
db = DB()使用时:from db import db —— 所有地方拿到的 db 都是同一个对象。
- 没有装饰器、没有元类、不干扰类定义
- 无法支持带参数的初始化(如动态配置数据库 URL),此时才需转向线程安全的类方案
- 注意避免循环导入:模块级实例不能依赖其他尚未完成导入的模块
实际项目里,模块级单例覆盖 80% 场景;真需要参数化 + 线程安全时,优先选带双重检查锁的类装饰器,而非元类——后者调试困难、继承行为隐晦,且容易在 __init_subclass__ 中引发意外覆盖。

















