Chuỗi nào được lưu trữ dưới dạng 16 bit trong Python?

Kể từ Python 3, loại

import ctypes

class PyUnicodeObject(ctypes.Structure):
    # internal fields of the string object
    _fields_ = [("ob_refcnt", ctypes.c_long),
                ("ob_type", ctypes.c_void_p),
                ("length", ctypes.c_ssize_t),
                ("hash", ctypes.c_ssize_t),
                ("interned", ctypes.c_uint, 2),
                ("kind", ctypes.c_uint, 3),
                ("compact", ctypes.c_uint, 1),
                ("ascii", ctypes.c_uint, 1),
                ("ready", ctypes.c_uint, 1),
                # ...
                # ...
                ]


def get_string_kind(string):
    return PyUnicodeObject.from_address(id(string)).kind
1 sử dụng biểu diễn Unicode. Các chuỗi Unicode có thể chiếm tới 4 byte cho mỗi ký tự tùy thuộc vào mã hóa, điều này đôi khi có thể tốn kém từ góc độ bộ nhớ

Để giảm mức tiêu thụ bộ nhớ và cải thiện hiệu suất, Python sử dụng ba loại biểu diễn bên trong cho chuỗi Unicode

  • 1 byte trên mỗi ký tự (mã hóa Latin-1)
  • 2 byte mỗi ký tự (mã hóa UCS-2)
  • 4 byte trên mỗi ký tự (mã hóa UCS-4)

Khi lập trình bằng Python, tất cả các chuỗi hoạt động giống nhau và hầu hết thời gian chúng tôi không nhận thấy bất kỳ sự khác biệt nào. Tuy nhiên, sự khác biệt có thể rất đáng chú ý và đôi khi bất ngờ khi làm việc với lượng lớn văn bản

Để thấy sự khác biệt trong các biểu diễn bên trong, chúng ta có thể sử dụng hàm

import ctypes

class PyUnicodeObject(ctypes.Structure):
    # internal fields of the string object
    _fields_ = [("ob_refcnt", ctypes.c_long),
                ("ob_type", ctypes.c_void_p),
                ("length", ctypes.c_ssize_t),
                ("hash", ctypes.c_ssize_t),
                ("interned", ctypes.c_uint, 2),
                ("kind", ctypes.c_uint, 3),
                ("compact", ctypes.c_uint, 1),
                ("ascii", ctypes.c_uint, 1),
                ("ready", ctypes.c_uint, 1),
                # ...
                # ...
                ]


def get_string_kind(string):
    return PyUnicodeObject.from_address(id(string)).kind
2, trả về kích thước của một đối tượng theo byte

>>> import sys
>>> string = 'hello'
>>> sys.getsizeof(string)
54
>>> # 1-byte encoding
>>> sys.getsizeof(string+'!')-sys.getsizeof(string)
1
>>> # 2-byte encoding
>>> string2  = '你'
>>> sys.getsizeof(string2+'好')-sys.getsizeof(string2)
2
>>> sys.getsizeof(string2)
76
>>> # 4-byte encoding
>>> string3 = '🐍'
>>> sys.getsizeof(string3+'💻')-sys.getsizeof(string3)
4
>>> sys.getsizeof(string3)
80

Như bạn có thể thấy, tùy thuộc vào nội dung của một chuỗi, Python sử dụng các cách mã hóa khác nhau. Lưu ý rằng mỗi chuỗi trong Python chiếm thêm 49-80 byte bộ nhớ, nơi nó lưu trữ thông tin bổ sung, chẳng hạn như hàm băm, độ dài, độ dài tính bằng byte, loại mã hóa và cờ chuỗi. Đó là lý do tại sao một chuỗi trống chiếm 49 byte bộ nhớ

Chúng tôi có thể truy xuất mã hóa trực tiếp từ một đối tượng bằng cách sử dụng

import ctypes

class PyUnicodeObject(ctypes.Structure):
    # internal fields of the string object
    _fields_ = [("ob_refcnt", ctypes.c_long),
                ("ob_type", ctypes.c_void_p),
                ("length", ctypes.c_ssize_t),
                ("hash", ctypes.c_ssize_t),
                ("interned", ctypes.c_uint, 2),
                ("kind", ctypes.c_uint, 3),
                ("compact", ctypes.c_uint, 1),
                ("ascii", ctypes.c_uint, 1),
                ("ready", ctypes.c_uint, 1),
                # ...
                # ...
                ]


def get_string_kind(string):
    return PyUnicodeObject.from_address(id(string)).kind
3

import ctypes

class PyUnicodeObject(ctypes.Structure):
    # internal fields of the string object
    _fields_ = [("ob_refcnt", ctypes.c_long),
                ("ob_type", ctypes.c_void_p),
                ("length", ctypes.c_ssize_t),
                ("hash", ctypes.c_ssize_t),
                ("interned", ctypes.c_uint, 2),
                ("kind", ctypes.c_uint, 3),
                ("compact", ctypes.c_uint, 1),
                ("ascii", ctypes.c_uint, 1),
                ("ready", ctypes.c_uint, 1),
                # ...
                # ...
                ]


def get_string_kind(string):
    return PyUnicodeObject.from_address(id(string)).kind

>>> get_string_kind('Hello')
1
>>> get_string_kind('你好')
2
>>> get_string_kind('🐍')
4

Nếu tất cả các ký tự trong một chuỗi có thể vừa với phạm vi ASCII, thì chúng được mã hóa bằng mã hóa Latin-1 1 byte. Về cơ bản, Latin-1 đại diện cho 256 ký tự Unicode đầu tiên. Nó hỗ trợ nhiều ngôn ngữ Latinh, chẳng hạn như tiếng Anh, tiếng Thụy Điển, tiếng Ý, tiếng Na Uy, v.v. Tuy nhiên, nó không thể lưu trữ các ngôn ngữ không phải là tiếng Latinh, chẳng hạn như tiếng Trung, tiếng Nhật, tiếng Do Thái, tiếng Cyrillic. Đó là do các điểm mã của chúng (chỉ mục số) được xác định bên ngoài phạm vi 1 byte (0-255)

>>> ord('a')
97
>>> ord('你')
20320
>>> ord('!')
33

Hầu hết các ngôn ngữ tự nhiên phổ biến đều có thể phù hợp với mã hóa 2 byte (UCS-2). Mã hóa 4 byte (UCS-4) được sử dụng khi một chuỗi chứa các ký hiệu đặc biệt, biểu tượng cảm xúc hoặc ngôn ngữ hiếm. Có gần 300 khối (phạm vi) trong tiêu chuẩn Unicode. Bạn có thể tìm thấy các khối 4 byte sau khối 0xFFFF

Giả sử chúng ta có 10GB văn bản ASCII và chúng ta muốn tải nó vào bộ nhớ. Nếu bạn chèn một biểu tượng cảm xúc vào văn bản của chúng tôi, kích thước của chuỗi sẽ tăng lên gấp 4 lần. Đây là một sự khác biệt rất lớn mà bạn có thể gặp phải trong thực tế khi làm việc với các vấn đề NLP

Tại sao Python không sử dụng mã hóa UTF-8 trong nội bộ

Mã hóa Unicode phổ biến và nổi tiếng nhất là UTF-8, nhưng Python không sử dụng nó trong nội bộ

Khi một chuỗi được lưu trữ trong mã hóa UTF-8, mỗi ký tự được mã hóa bằng 1-4 byte tùy thuộc vào ký tự mà nó đại diện. Đó là một mã hóa lưu trữ hiệu quả, nhưng nó có một nhược điểm đáng kể. Vì mỗi ký tự có thể khác nhau về độ dài byte, nên không có cách nào để truy cập ngẫu nhiên một ký tự riêng lẻ theo chỉ mục mà không quét chuỗi. Vì vậy, để thực hiện một thao tác đơn giản như

import ctypes

class PyUnicodeObject(ctypes.Structure):
    # internal fields of the string object
    _fields_ = [("ob_refcnt", ctypes.c_long),
                ("ob_type", ctypes.c_void_p),
                ("length", ctypes.c_ssize_t),
                ("hash", ctypes.c_ssize_t),
                ("interned", ctypes.c_uint, 2),
                ("kind", ctypes.c_uint, 3),
                ("compact", ctypes.c_uint, 1),
                ("ascii", ctypes.c_uint, 1),
                ("ready", ctypes.c_uint, 1),
                # ...
                # ...
                ]


def get_string_kind(string):
    return PyUnicodeObject.from_address(id(string)).kind
0 với UTF-8, Python sẽ cần quét một chuỗi cho đến khi tìm thấy ký tự bắt buộc. Mã hóa độ dài cố định không gặp vấn đề như vậy, để xác định vị trí ký tự theo chỉ mục Python chỉ cần nhân số chỉ mục với độ dài của một ký tự (1, 2 hoặc 4 byte)

thực tập chuỗi

Khi làm việc với chuỗi trống hoặc chuỗi ASCII có một ký tự, Python sử dụng chuỗi thực tập. Các chuỗi được lồng vào hoạt động như các chuỗi đơn lẻ, nghĩa là, nếu bạn có hai chuỗi giống hệt nhau được lồng vào, thì chỉ có một bản sao của chúng trong bộ nhớ

>>> a = 'hello'
>>> b = 'world'
>>> a[4],b[1]
('o', 'o')
>>> id(a[4]), id(b[1]), a[4] is b[1]
(4567926352, 4567926352, True)
>>> id('')
4545673904
>>> id('')
4545673904

Như bạn có thể thấy, cả hai lát chuỗi đều trỏ đến cùng một địa chỉ trong bộ nhớ. Có thể vì chuỗi Python là bất biến

Trong Python, việc thực tập chuỗi không giới hạn ở các ký tự hoặc chuỗi rỗng. Các chuỗi được tạo trong quá trình biên dịch mã cũng có thể được thực hiện nếu độ dài của chúng không vượt quá 20 ký tự

Điêu nay bao gôm

  • tên hàm và lớp
  • tên biến
  • tên đối số
  • hằng số (tất cả các chuỗi được xác định trong mã)
  • chìa khóa từ điển
  • tên của các thuộc tính

Khi bạn nhấn enter trong Python REPL, câu lệnh của bạn sẽ được biên dịch thành mã byte. Đó là lý do tại sao tất cả các chuỗi ngắn trong REPL cũng được thực tập

>>> a = 'teststring'
>>> b = 'teststring'
>>> id(a), id(b), a is b
(4569487216, 4569487216, True)
>>> a = 'test'*5
>>> b = 'test'*5
>>> len(a), id(a), id(b), a is b
(20, 4569499232, 4569499232, True)
>>> a = 'test'*6
>>> b = 'test'*6
>>> len(a), id(a), id(b), a is b
(24, 4569479328, 4569479168, False)

Ví dụ này sẽ không hoạt động, bởi vì các chuỗi như vậy không phải là hằng số

>>> open('test.txt','w').write('hello')
5
>>> open('test.txt','r').read()
'hello'
>>> a = open('test.txt','r').read()
>>> b = open('test.txt','r').read()
>>> id(a), id(b), a is b
(4384934576, 4384934688, False)
>>> len(a), id(a), id(b), a is b
(5, 4384934576, 4384934688, False)

Kỹ thuật thực tập chuỗi giúp tiết kiệm hàng chục nghìn phân bổ chuỗi trùng lặp. Trong nội bộ, việc thực tập chuỗi được duy trì bởi một từ điển toàn cầu nơi các chuỗi được sử dụng làm khóa. Để kiểm tra xem đã có một chuỗi giống hệt nhau trong bộ nhớ Python thực hiện thao tác thành viên từ điển

Đối tượng unicode gần 16 000 dòng code C nên còn rất nhiều tối ưu nhỏ không đề cập trong bài viết này. Nếu bạn muốn tìm hiểu thêm về Unicode trong Python, tôi khuyên bạn nên đọc PEP về chuỗi và kiểm tra mã của đối tượng unicode

Bài đăng phổ biến trong danh mục Python

07 Tháng Mười, 2017

Thu gom rác bằng Python. Những điều bạn cần biết

28 Tháng Chín, 2017

Quản lý bộ nhớ trong Python

09 Tháng Năm, 2018

Trích xuất văn bản từ HTML bằng Python. một cách tiếp cận rất nhanh

21 Tháng Giêng, 2018

Hiểu nội bộ của các lớp Python

03 Tháng Tư, 2018

Thủ thuật tối ưu hóa trong Python. danh sách và bộ dữ liệu

29 Tháng mười hai, 2017

Cấu trúc dữ liệu thưa thớt trong Python

python ,  nội bộ cpython,  bộ nhớ

Chia sẻ

  • liên kết
  • điện tín
  • VK
  • reddit
  • tin tặc
  • Twitter

  • RSS

    Bình luận

    • Kevin Bai 4 năm, 4 tháng trước (từ disqus)

      Bài viết hay. Tôi có thể chuyển nó sang tiếng Trung kèm theo liên kết nguồn không?
      Mong nhận được phản hồi của bạn.

      Đáp lại

      • Artem 4 năm, 4 tháng trước (từ disqus)

        Chắc chắn không có vấn đề

        Đáp lại

        • Kevin Bai 4 năm, 4 tháng trước (từ disqus)

          Vâng, cảm ơn

          Đáp lại

    • Ẩn danh 3 năm trước

      Tôi cảm thấy có hai tuyên bố mâu thuẫn ở đây. Bạn đã nói rằng việc chèn một biểu tượng cảm xúc vào văn bản có kích thước 10GB ASCII sẽ tăng kích thước lên gấp 4 lần. Nhưng trong Python, mỗi ký tự được mã hóa bằng 1-4 byte tùy thuộc vào ký tự mà nó đại diện. Vì vậy, lý tưởng nhất là chỉ riêng ký tự biểu tượng cảm xúc nên được mã hóa bằng 4 byte chứ không phải toàn bộ văn bản 10 GB. Vậy làm cách nào để chèn một biểu tượng cảm xúc làm tăng kích thước văn bản lên gấp 4 lần?

      Đáp lại

      • Artem 3 năm trước

        Điều đó xảy ra vì Python sẽ sử dụng mã hóa một ký tự cho toàn bộ chuỗi khi tải nó vào một biến. Bạn không thể trộn chúng vì bạn muốn có khả năng lập chỉ mục hoặc quét một chuỗi lớn một cách nhanh chóng

        Một biểu tượng cảm xúc buộc Python sử dụng bốn byte cho mỗi ký tự. Do đó, Python mất thời gian liên tục để truy cập một chỉ mục ngẫu nhiên, e. g.

        import ctypes
        
        class PyUnicodeObject(ctypes.Structure):
            # internal fields of the string object
            _fields_ = [("ob_refcnt", ctypes.c_long),
                        ("ob_type", ctypes.c_void_p),
                        ("length", ctypes.c_ssize_t),
                        ("hash", ctypes.c_ssize_t),
                        ("interned", ctypes.c_uint, 2),
                        ("kind", ctypes.c_uint, 3),
                        ("compact", ctypes.c_uint, 1),
                        ("ascii", ctypes.c_uint, 1),
                        ("ready", ctypes.c_uint, 1),
                        # ...
                        # ...
                        ]
        
        
        def get_string_kind(string):
            return PyUnicodeObject.from_address(id(string)).kind
        
        1

        Đáp lại

        • Ẩn danh 3 năm trước

          Điều đó giải thích tốt. Cảm ơn bạn

          Đáp lại

          • Jorge 2 năm, 2 tháng trước

            Có vẻ như bạn sẽ là người hâm mộ UTF-8. Nó làm chính xác những gì bạn đang mong muốn. ("Chỉ ký tự biểu tượng cảm xúc nên được mã hóa bằng 4 byte chứ không phải toàn bộ văn bản 10 GB. ")

            Đáp lại

    • mưa phùn 3 năm trước

      Bài viết xuất sắc. Tôi rất vui khi học được điều gì đó sáng nay

      Đáp lại

    • mrsmith 2 năm, 7 tháng trước

      bài đăng tuyệt vời. Cám ơn vì đã chia sẻ

      Đáp lại

    • Sia 2 năm, 3 tháng trước

      thực sự mát mẻ. Cảm ơn vì lời giải thích chi tiết

      Đáp lại

    • Ameer 1 năm, 7 tháng trước

      Có lỗi trong khối mã cuối cùng thứ hai. Lẽ ra cũng phải đúng Đây là những gì tôi nhận được

      len(a), id(a), id(b), a là b (24, 139946711810096, 139946711810096, Đúng)

      Đáp lại

    • Ameer 1 năm, 7 tháng trước

      Có lỗi trong khối mã cuối cùng thứ hai. Lẽ ra cũng phải đúng

      Đây là những gì tôi nhận được

      a = 'test'6 b = 'test'6 len(a), id(a), id(b), a là b (24, 139946711810096, 139946711810096, True)

      Đáp lại

    • Tarunika 1 năm, 1 tháng trước

      S= "Xin chào thế giới" In (S. đếm ("") Nó được in 12 dưới dạng đầu ra như thế nào?. bất cứ ai có thể giải thích? . Nếu chúng tôi không chỉ định không gian, nó sẽ cho 12 làm đầu ra

      Đáp lại

    • Sergi 10 tháng, 1 tuần trước

      Cảm ơn vì bài đăng. Tôi có một vài điều rõ ràng và hữu ích mà tôi không nhận được sau khi đọc một vài bài đăng khác

      Đáp lại

    • Chàng trai 6 tháng, 3 tuần trước

      cảm ơn bài viết tuyệt vời. bạn có thể vui lòng giải thích những điều sau đây

      import ctypes
      
      class PyUnicodeObject(ctypes.Structure):
          # internal fields of the string object
          _fields_ = [("ob_refcnt", ctypes.c_long),
                      ("ob_type", ctypes.c_void_p),
                      ("length", ctypes.c_ssize_t),
                      ("hash", ctypes.c_ssize_t),
                      ("interned", ctypes.c_uint, 2),
                      ("kind", ctypes.c_uint, 3),
                      ("compact", ctypes.c_uint, 1),
                      ("ascii", ctypes.c_uint, 1),
                      ("ready", ctypes.c_uint, 1),
                      # ...
                      # ...
                      ]
      
      
      def get_string_kind(string):
          return PyUnicodeObject.from_address(id(string)).kind
      
      2

      import ctypes
      
      class PyUnicodeObject(ctypes.Structure):
          # internal fields of the string object
          _fields_ = [("ob_refcnt", ctypes.c_long),
                      ("ob_type", ctypes.c_void_p),
                      ("length", ctypes.c_ssize_t),
                      ("hash", ctypes.c_ssize_t),
                      ("interned", ctypes.c_uint, 2),
                      ("kind", ctypes.c_uint, 3),
                      ("compact", ctypes.c_uint, 1),
                      ("ascii", ctypes.c_uint, 1),
                      ("ready", ctypes.c_uint, 1),
                      # ...
                      # ...
                      ]
      
      
      def get_string_kind(string):
          return PyUnicodeObject.from_address(id(string)).kind
      
      3

      Đáp lại

      • Artem 6 tháng, 3 tuần trước

        Python thực tập chuỗi ngắn

        Đáp lại

        • Chàng trai 6 tháng, 3 tuần trước

          Có nhưng tại sao '. ' gây ra hành vi khác biệt này vì nó là một trong các ký tự ascii và có được lưu trữ giống nhau không?

          Đáp lại

          • Artem 6 tháng, 2 tuần trước

            Có một kiểm tra cho các ký tự phụ. https. //github. com/python/cpython/blob/1603a1029f44f0fdc87c65b02063229962194f84/Objects/codeobject. c#L21

            Đáp lại

            • Chàng trai 6 tháng, 2 tuần trước

              Thanks. Nhiều đánh giá cao

    • Chàng trai 2 tháng, 2 tuần trước

      Nhưng đoạn mã dưới đây mâu thuẫn với mô hình chuỗi này được lưu trữ dưới dạng một khối ký tự liền kề, nhưng giống như một mảng tham chiếu/con trỏ tới các ký tự riêng lẻ vì o trong cả hai chuỗi đều trỏ đến cùng một đối tượng

      Làm thế nào các chuỗi được lưu trữ trong Python?

      Chuỗi được lưu trữ dưới dạng các ký tự riêng lẻ trong một vị trí bộ nhớ liền kề . Nó có thể được truy cập từ cả hai hướng. tiến và lùi. Nhân vật không là gì ngoài biểu tượng. Chuỗi là kiểu dữ liệu bất biến trong Python, có nghĩa là một khi chuỗi được tạo thì không thể thay đổi được.

      Chuỗi Unicode trong Python là gì?

      Tóm tắt phần trước. một chuỗi Unicode là một chuỗi các điểm mã, là các số từ 0 đến 0x10FFFF (1.114.111 thập phân) . Chuỗi điểm mã này cần được biểu diễn trong bộ nhớ dưới dạng một tập hợp các đơn vị mã và sau đó các đơn vị mã được ánh xạ tới các byte 8 bit.

      UTF là gì

      UTF-8 là hệ thống mã hóa cho Unicode . Nó có thể dịch bất kỳ ký tự Unicode nào thành chuỗi nhị phân duy nhất phù hợp và cũng có thể dịch chuỗi nhị phân trở lại ký tự Unicode. Đây là ý nghĩa của “UTF” hoặc “Định dạng chuyển đổi Unicode. ”

      Chuỗi byte trong Python là gì?

      Trong Python, một chuỗi byte chỉ có thế. chuỗi byte . Nó không phải là con người có thể đọc được. Về cơ bản, mọi thứ phải được chuyển đổi thành chuỗi byte trước khi có thể lưu trữ trong máy tính. Mặt khác, một chuỗi ký tự, thường được gọi là "chuỗi", là một chuỗi các ký tự. Nó là con người có thể đọc được.