Bra*_*mon 5 python string python-3.x pandas
为什么长度sys.getsizeof()为 1 的 Pythonstr比长度为 2 的字符串大?(对于长度 > 2,该关系似乎按预期单调增加。)
例子:
>>> from string import ascii_lowercase
>>> import sys
>>> strings = [ascii_lowercase[:i] for i, _ in enumerate(ascii_lowercase, 1)]
>>> strings
['a',
'ab',
'abc',
'abcd',
'abcde',
'abcdef',
'abcdefg',
# ...
>>> sizes = dict(enumerate(map(sys.getsizeof, strings), 1))
>>> sizes
{1: 58, # <--- ??
2: 51,
3: 52,
4: 53,
5: 54,
6: 55,
7: 56,
8: 57,
9: 58,
10: 59,
11: 60,
12: 61,
13: 62,
14: 63,
15: 64,
16: 65,
# ...
Run Code Online (Sandbox Code Playgroud)
看起来这与 有关str.__sizeof__,但我对 C 的了解根本不够深入,无法深入了解这种情况下发生的情况。
编辑:
这似乎与 IPython 启动文件中的单个 Pandas 导入有关。
我也可以在普通的 Python 会话中重现该行为:
~$ python
Python 3.6.6 |Anaconda, Inc.| (default, Jun 28 2018, 11:07:29)
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from string import ascii_lowercase
>>> import sys
>>> strings = [ascii_lowercase[:i] for i, _ in enumerate(ascii_lowercase, 1)]
>>> sizes = dict(enumerate(map(sys.getsizeof, strings), 1))
>>> sizes
{1: 50, 2: 51, 3: 52, 4: 53, 5: 54, 6: 55, 7: 56, 8: 57, 9: 58, 10: 59, 11: 60, 12: 61, 13: 62, 14: 63, 15: 64, 16: 65, 17: 66, 18: 67, 19: 68, 20: 69, 21: 70, 22: 71, 23: 72, 24: 73, 25: 74, 26: 75}
>>> import pandas as pd
>>> sizes = dict(enumerate(map(sys.getsizeof, strings), 1))
>>> sizes
{1: 58, 2: 51, 3: 52, 4: 53, 5: 54, 6: 55, 7: 56, 8: 57, 9: 58, 10: 59, 11: 60, 12: 61, 13: 62, 14: 63, 15: 64, 16: 65, 17: 66, 18: 67, 19: 68, 20: 69, 21: 70, 22: 71, 23: 72, 24: 73, 25: 74, 26: 75}
>>> pd.__version__
'0.23.2'
Run Code Online (Sandbox Code Playgroud)
当您执行 时import pandas,它会执行大量 NumPy 操作,包括调用UNICODE_setitem所有单 ASCII 字母字符串,并且可能在其他地方对单 ASCII 数字字符串执行类似的操作。
NumPy 函数调用已弃用的 C APIPyUnicode_AsUnicode。
当您在 CPython 3.3+ 中调用它时,它将wchar_t *在其成员中缓存字符串内部结构上的表示形式,wstr作为两个 wchar_t 值w\'a\'和\'\\0\',在 32 位wchar_t构建的 Python 上占用 8 个字节。并str.__size__考虑到这一点。
因此,所有 ASCII 字母和数字的单字符驻留字符串\xe2\x80\x94(除此之外\xe2\x80\x94)都增加了 8 个字节。
\n\n首先,我们知道这显然是发生在import pandas(根据Brad Solomon 的回答)的事情。它可能发生在(miradulo 发布,但随后被删除,对ShadowRanger 的回答np.set_printoptions(precision=4, threshold=625, edgeitems=10)有影响的评论),但是绝对不上。import numpy
其次,我们知道它发生在\'a\',但是其他单字符字符串呢?
为了验证前者并测试后者,我运行了以下代码:
\n\nimport sys\n\nstrings = [chr(i) for i in (0, 10, 17, 32, 34, 47, 48, 57, 58, 64, 65, 90, 91, 96, 97, 102, 103, 122, 123, 130, 0x0222, 0x12345)]\n\nsizes = {c: sys.getsizeof(c) for c in strings}\nprint(sizes)\n\nimport numpy as np\nsizes = {c: sys.getsizeof(c) for c in strings}\nprint(sizes)\n\nnp.set_printoptions(precision=4, threshold=625, edgeitems=10)\nsizes = {c: sys.getsizeof(c) for c in strings}\nprint(sizes)\n\nimport pandas\nsizes = {c: sys.getsizeof(c) for c in strings}\nprint(sizes)\nRun Code Online (Sandbox Code Playgroud)\n\n在多个 CPython 安装(但 Linux 或 macOS 上的所有 64 位 CPython 3.4 或更高版本)上,我得到了相同的结果:
\n\n{\'\\x00\': 50, \'\\n\': 50, \'\\x11\': 50, \' \': 50, \'"\': 50, \'/\': 50, \'0\': 50, \'9\': 50, \':\': 50, \'@\': 50, \'A\': 50, \'Z\': 50, \'[\': 50, \'`\': 50, \'a\': 50, \'f\': 50, \'g\': 50, \'z\': 50, \'{\': 50, \'\\x82\': 74, \'\xc8\xa2\': 76, \'\': 80}\n{\'\\x00\': 50, \'\\n\': 50, \'\\x11\': 50, \' \': 50, \'"\': 50, \'/\': 50, \'0\': 50, \'9\': 50, \':\': 50, \'@\': 50, \'A\': 50, \'Z\': 50, \'[\': 50, \'`\': 50, \'a\': 50, \'f\': 50, \'g\': 50, \'z\': 50, \'{\': 50, \'\\x82\': 74, \'\xc8\xa2\': 76, \'\': 80}\n{\'\\x00\': 50, \'\\n\': 50, \'\\x11\': 50, \' \': 50, \'"\': 50, \'/\': 50, \'0\': 50, \'9\': 50, \':\': 50, \'@\': 50, \'A\': 50, \'Z\': 50, \'[\': 50, \'`\': 50, \'a\': 50, \'f\': 50, \'g\': 50, \'z\': 50, \'{\': 50, \'\\x82\': 74, \'\xc8\xa2\': 76, \'\': 80}\n{\'\\x00\': 50, \'\\n\': 50, \'\\x11\': 50, \' \': 50, \'"\': 50, \'/\': 50, \'0\': 58, \'9\': 58, \':\': 50, \'@\': 50, \'A\': 58, \'Z\': 58, \'[\': 50, \'`\': 50, \'a\': 58, \'f\': 58, \'g\': 58, \'z\': 58, \'{\': 50, \'\\x82\': 74, \'\xc8\xa2\': 76, \'\': 80}\nRun Code Online (Sandbox Code Playgroud)\n\n所以,import numpy什么都没有改变,也是如此set_printoptions(大概是为什么 miradulo 删除了评论\xe2\x80\xa6),但是import pandas确实如此。
它显然会影响 ASCII 数字和字母,但不会影响其他内容。
\n\n另外,如果将所有prints 更改为print(sizes.values()),那么字符串永远不会被编码以用于输出,您会得到相同的结果,这意味着它要么不是关于缓存 UTF-8,要么就是这样即使我们不强迫它,它总是会发生。
明显的可能性是,无论 Pandas 调用什么,都使用旧版PyUnicodeAPI之一来为所有 ASCII 数字和字母生成单字符字符串。所以这些字符串最终不是紧凑 ASCII 格式,而是传统格式,对吗?(有关这意味着什么的详细信息,请参阅源中的注释。)
没有。使用 my 中的代码superhackyinternals,我们可以看到它仍然是紧凑的 ASCII 格式:
import ctypes\nimport sys\nfrom internals import PyUnicodeObject\n\ns = \'a\'\nprint(sys.getsizeof(s))\nps = PyUnicodeObject.from_address(s)\nprint(ps, ps.kind, ps.length, ps.interned, ps.ascii, ps.compact, ps.ready)\naddr = id(s) + PyUnicodeObject.utf8_length.offset\nbuf = (ctypes.c_char * 2).from_address(addr)\nprint(addr, bytes(buf))\n\nimport pandas\nprint(sys.getsizeof(s))\ns = \'a\'\nps = PyUnicodeObject.from_address(s)\nprint(ps, ps.kind, ps.length, ps.interned, ps.ascii, ps.compact, ps.ready)\naddr = id(s) + PyUnicodeObject.utf8_length.offset\nbuf = (ctypes.c_char * 2).from_address(addr)\nprint(addr, bytes(buf))\nRun Code Online (Sandbox Code Playgroud)\n\n我们可以看到 Pandas 将大小从 50 更改为 58,但字段仍然是:
\n\n<__main__.PyUnicodeObject object at 0x101bbae18> 1 1 1 1 1 1\nRun Code Online (Sandbox Code Playgroud)\n\n\xe2\x80\xa6 换句话说,它是1BYTE_KIND,长度为 1,凡人实习,ASCII,紧凑,准备就绪。
但是,如果你看一下ps.wstr,在 Pandas 之前它是一个空指针,而在 Pandas 之后它是一个指向wchar_t字符串的指针w"a\\0"。并考虑到str.__sizeof__这个wstr尺寸。
所以,问题是,如何最终得到一个具有wstr值的 ASCII 紧凑字符串?
简单:您调用PyUnicode_AsUnicode它(或访问 3.2 样式本机内部存储的其他已弃用函数或宏之一wchar_t *。该本机内部存储实际上并不存在于 3.3+ 中。因此,为了向后兼容,将处理这些调用通过动态创建该存储,将其粘贴在wstr成员上,然后调用适当的PyUnicode_AsUCS[24]函数来解码该存储。(除非您正在处理一个紧凑的字符串,其类型恰好与宽度匹配wchar_t,在这种情况下wstr只是一个毕竟指向本机存储的指针。)
理想情况下,您希望str.__sizeof__包含额外的存储空间,并且从源代码中您可以看到它确实如此。
让我们验证一下:
\n\nimport ctypes\nimport sys\ns = \'a\'\nprint(sys.getsizeof(s))\nctypes.pythonapi.PyUnicode_AsUnicode.argtypes = [ctypes.py_object]\nctypes.pythonapi.PyUnicode_AsUnicode.restype = ctypes.c_wchar_p\nprint(ctypes.pythonapi.PyUnicode_AsUnicode(s))\nprint(sys.getsizeof(s))\nRun Code Online (Sandbox Code Playgroud)\n\n多田,我们的 50 变成了 58。
\n\n那么,你如何知道它在哪里被调用呢?
\n\n实际上,在 Pandas 和 Numpy 中,有大量对PyUnicode_AsUnicode、PyUnicode_AS_UNICODE宏以及调用它们的其他函数的调用。因此,我在 lldb 中运行 Python 并附加一个断点PyUnicode_AsUnicode,如果调用堆栈帧与上次相同,则脚本会跳过。
前几次调用涉及日期时间格式。然后是一个只有一个字母的。堆栈框架是:
\n\nmultiarray.cpython-36m-darwin.so`UNICODE_setitem + 296\nRun Code Online (Sandbox Code Playgroud)\n\n\xe2\x80\xa6 及以上multiarray都是纯 Python 一直到import pandas. 所以,如果你想确切地知道 Pandas 在哪里调用这个函数,你需要在 中进行调试pdb,而我还没有这样做。但我认为我们现在已经获得了足够的信息。
| 归档时间: |
|
| 查看次数: |
571 次 |
| 最近记录: |