V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
peterliu502
V2EX  ›  Python

关于 Python 字符串默认编码下的字节数问题

  •  
  •   peterliu502 · 2020-02-03 00:38:33 +08:00 · 2511 次点击
    这是一个创建于 1757 天前的主题,其中的信息可能已经有所发展或是发生改变。

    python 中字符串默认的是 Unicode 编码,数字字符、英文字符和中文字符在 Unicode 编码中不是都是 2 字节么?
    为什么我用 getsizeof()函数来看字符串的内存大小,发现 1 个汉字字符是 2 字节没错,但 1 个数字字符却只有 1 字节?
    我用 utf-8 编码检验了一下,没发现问题。1 个数字字符 1 字节,一个汉字 3 字节。

    print('Unicode_123 =', sys.getsizeof('123'))  
    print('Unicode_12 =', sys.getsizeof('12'))
    # Unicode_123-Unicode_12 可得字符串默认编码下 1 个数字字符所占内存
    print('Unicode_一二三 =', sys.getsizeof('一二三'))  
    print('Unicode_一二 =', sys.getsizeof('一二'))
    # Unicode_一二三-Unicode_一二可得字符串默认编码下 1 个汉字字符所占内存
    print('Utf-8_123 =', sys.getsizeof('123'.encode('utf-8')))  
    print('Utf-8_12 =', sys.getsizeof('12'.encode('utf-8')))
    # Utf-8_123-Utf-8_12 可得 utf-8 编码下 1 个数字字符所占内存
    print('Utf-8_一二三 =', sys.getsizeof('一二三'.encode('utf-8')))  
    print('Utf-8_一二 =', sys.getsizeof('一二'.encode('utf-8')))
    # Utf-8_一二三-Utf-8_一二可得 utf-8 编码下 1 个汉字字符所占内存
    

    显示结果如下:
    Unicode_123 = 52
    Unicode_12 = 51
    Unicode_一二三 = 80
    Unicode_一二 = 78
    Utf-8_123 = 36
    Utf-8_12 = 35
    Utf-8_一二三 = 42
    Utf-8_一二 = 39

    但如果字符串是数字或英文字母混合汉字时,新增 1 个数字字符是增加 2 字节

    print('Unicode_一 =', sys.getsizeof('一'))
    print('Unicode_1 一 =', sys.getsizeof('1 一'))
    print('Unicode_a 一 =', sys.getsizeof('a 一'))
    print('Unicode_一二 =', sys.getsizeof('一二'))
    

    显示结果如下: Unicode_一 = 76
    Unicode_1 一 = 78
    Unicode_a 一 = 78
    Unicode_一二 = 78
    这种情况是为什么呢?

    8 条回复    2020-02-05 18:47:59 +08:00
    justou
        1
    justou  
       2020-02-03 22:20:58 +08:00
    '1 一' 'a 一' 是不是多插入了一个空格?
    peterliu502
        2
    peterliu502  
    OP
       2020-02-04 12:03:38 +08:00
    @justou 好像还真是……
    peterliu502
        3
    peterliu502  
    OP
       2020-02-04 12:04:24 +08:00
    @justou 但还是没明白为何 ASCII 字符是 1 字节
    justou
        4
    justou  
       2020-02-04 14:46:31 +08:00
    Unicode 字符串即 py3 的 str 底层是一个 Py_UNICODE 的数组,其实就是一个 wchar_t 数组( wchar_t 大小平台相关,或 16bit 或 32bit ),str 就是一个对底层 wchar_t 数组的封装。

    按照你的例子,str 增加一个 ascii 字符,大小增加 2 字节的话,那么底层的 wchar_t 大小应该是 16bit ( 2 字节)

    https://docs.python.org/3/c-api/unicode.html#unicode-type
    peterliu502
        5
    peterliu502  
    OP
       2020-02-05 12:39:51 +08:00
    @justou str 增加一个 ascii 字符是 1 字节,非 ascii 字符才是 2 字节
    chenstack
        6
    chenstack  
       2020-02-05 15:05:35 +08:00
    Python3 字符串 encode 默认编码是 utf-8,中文一般占 3 字节,ascii 字符是 1 字节
    下面例子是计算 bytes 的大小
    >>> print('Utf-8_一二三 =', sys.getsizeof('一二三'.encode('utf-8')))
    >>> print('Utf-8_一二 =', sys.getsizeof('一二'.encode('utf-8')))
    Utf-8_一二三 = 42
    Utf-8_一二 = 39
    相差 3


    下面例子是计算 str 的大小,每个字符都是 2 字节,也就是 @justou #4 说的
    >>> print('Unicode_a 一 =', sys.getsizeof('a 一'))
    >>> print('Unicode_一二 =', sys.getsizeof('一二'))
    Unicode_a 一 = 78
    Unicode_一二 = 78
    justou
        7
    justou  
       2020-02-05 15:35:28 +08:00
    我上面的回复不全面,可以用以下函数来探索下:

    from sys import getsizeof

    def probe_size_increment(init_str="", code_points=range(2, 300)):
    for i in code_points:
    s1 = init_str + ''.join(chr(n) for n in range(i))
    s2 = init_str + ''.join(chr(n) for n in range(i+1))
    print(f"{i}~{i+1} {getsizeof(s2) - getsizeof(s1)}")

    1. probe_size_increment(init_str=""); 可以用一个字节来表示的码点最大为 255,可以看到,在这之前都是 1 的增长,除了 255~256 分配了一定空间,后面都是 2 的增长。

    2. probe_size_increment(init_str="", code_points=range(2**16 - 10, 2**16 - 1));都是 2 的增长。

    3. probe_size_increment(init_str="", code_points=range(2**16 + 10, 2**16 + 20)); 都是 4 的增长。

    首先意识到 str 下面管理的是一个单一类型的 c 数组,可以有以下推论:当所有字符都可以用 1 字节表示时,这个 c 数组是 Py_UCS1[], 所以在 255 之前增长都是 1 ;同理,2 字节增长的是 Py_UCS2[],4 字节增长的是 Py_UCS4[]

    找了下源码,应该是这个位置 https://github.com/python/cpython/blob/3.8/Objects/unicodeobject.c#L2322 跟我们的推论一致

    Python 的一些实现用了很多优化手段,本身就是 C 语言搭建起来的一个框架。想探究其原理建议直接看 C 代码吧。之前有人发过解读 Python 源码的帖子,你找找。
    peterliu502
        8
    peterliu502  
    OP
       2020-02-05 18:47:59 +08:00
    @justou 谢谢,你这个答案我觉得算是解答了我的这个疑问
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   6053 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 02:25 · PVG 10:25 · LAX 18:25 · JFK 21:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.