你可以实现这个功能,但所有选项都很混乱,充满了附加条件,几乎没有用处,因此,首先考虑一下你是否真的想这样做。
将字符串存入池中并不能延长其生命周期。你不必担心存储在池中的字典会无限增长,充满了你不需要的字符串。因此,字符串池化不太可能成为一个实际的内存问题,并且了解有多少字符串被池化可能是相当无用的。
如果你仍然想这样做,让我们来看看你的选择。
正确的方法可能是使用自己的字符串缓存实现... 但是 Python 的弱引用支持不够好,无法创建字符串的弱引用。这意味着如果您尝试此方法,则要么传递自己的可弱引用字符串包装器,要么永远保持缓存的字符串处于活动状态。这两个选项都很糟糕。
实际上有一个函数可以打印您所询问的信息...但它也会取消所有的内部引用。它的存在是一项实现细节,只能通过C API访问,因此我们需要使用ctypes.pythonapi
来获取它。
import ctypes
_Py_ReleaseInternedStrings = ctypes.pythonapi._Py_ReleaseInternedStrings
_Py_ReleaseInternedStrings.argtypes = ()
_Py_ReleaseInternedStrings.restype = None
_Py_ReleaseInternedStrings()
输出:
releasing 3461 interned strings
total size of all interned strings: 33685/0 mortal/immortal
总大小是字符串长度的总和,因此不包括对象头或空终止符。
你可能不太满意每次想要检查有多少个字符串被interned时都需要释放所有interned strings。不幸的是,Python甚至没有通过C API或GC hooks暴露interned dict。那么还有什么可尝试的呢?好吧,进一步尝试更疯狂的选择,有调试器。
ecatmur发布了一个疯狂的hack,启动一个未经处理的GDB进程,并使用条件断点来访问errnomap
,这是一个非常类似于interned
dict的字典,您希望访问的是interned
dict。这可以改为访问interned
dict,但它将非常不可移植并且极难维护。
启动调试器也是个不好的选择。你还可以尝试什么呢?嗯,你可以构建自己定制的 Python 构建版本。从 python.org 下载源代码,添加
PyObject *
AwfulHackToGetTheInternedDict(void)
{
if (interned == NULL) {
Py_RETURN_NONE;
}
Py_INCREF(interned);
return interned;
}
前往 Objects/stringobject.c
,进行编译和安装。您可能希望使用虚拟环境将其与普通的Python解释器分开。完成这个糟糕的 hack 后,您可以执行以下操作:
import ctypes
AwfulHackToGetTheInternedDict = ctypes.pythonapi.AwfulHackToGetTheInternedDict
AwfulHackToGetTheInternedDict.argtypes = ()
AwfulHackToGetTheInternedDict.restype = ctypes.py_object
interned = AwfulHackToGetTheInternedDict()
获取所有已内部化字符串的字典。
那么,这些是你的选择,或者至少是我想到的选择。我还尝试过强制GC跟踪一个字符串,然后将其合并以使interned dict通过GC可见,但在字符串上调用PyObject_GC_Track
会导致致命错误,因此这种方法行不通。
interned
字典本身的大小;从其他地方没有引用的内部字符串的数量(和大小);仅从一个地方引用的内部字符串的数量(和大小)。这些一起有助于回答问题:我们是否通过不必要地将字符串放入池中而浪费了大量内存。 - jchl