Hướng dẫn which module is used to view the python byte code? - mô-đun nào được sử dụng để xem mã byte python?

Nếu bạn đã từng viết, hoặc thậm chí chỉ được sử dụng, Python, có lẽ bạn đã quen với việc thấy các tệp mã nguồn Python; Họ có tên kết thúc trong .py. Và bạn cũng có thể đã thấy một loại tệp khác, với một tên kết thúc bằng .pyc và bạn có thể đã nghe nói rằng chúng là các tệp "ByteCode" Python. . Ngăn chặn Python khỏi phải phân tích lại mã nguồn của bạn mỗi khi nó chạy.

Nhưng ngoài "Ồ, đó là mã Python byte," Bạn có thực sự biết những gì trong các tệp đó và cách Python sử dụng chúng không?

Nếu không, hôm nay là ngày may mắn của bạn! Tôi sẽ đưa bạn qua mã Python Bytecode, Python sử dụng nó như thế nào để thực thi mã của bạn và cách biết về nó có thể giúp bạn.

Làm thế nào Python hoạt động

Python thường được mô tả là một ngôn ngữ được giải thích, một trong đó mã nguồn của bạn được dịch thành các hướng dẫn CPU gốc khi chương trình chạy nhưng điều này chỉ chính xác một phần. Python, giống như nhiều ngôn ngữ được giải thích, thực sự biên dịch mã nguồn cho một tập hợp các hướng dẫn cho một máy ảo và trình thông dịch Python là một triển khai của máy ảo đó. Định dạng trung gian này được gọi là "mã byte."

Vì vậy, các tệp .pyc lá python nằm xung quanh không chỉ là một phiên bản "nhanh hơn" hoặc "tối ưu hóa" của mã nguồn của bạn; Chúng là các hướng dẫn mã byte sẽ được thực hiện bởi máy ảo của Python khi chương trình của bạn chạy.

Hãy xem xét một ví dụ. Đây là một tác phẩm kinh điển "Xin chào, Thế giới!" Viết bằng Python:

def hello()
    print("Hello, World!")

Và đây là mã byte mà nó biến thành (dịch thành dạng có thể đọc được của con người):

2           0 LOAD_GLOBAL              0 (print)
            2 LOAD_CONST               1 ('Hello, World!')
            4 CALL_FUNCTION            1

Nếu bạn nhập chức năng hello() đó và sử dụng trình thông dịch CPython để chạy nó, danh sách trên là những gì Python sẽ thực thi. Tuy nhiên, nó có vẻ hơi kỳ lạ, vì vậy chúng ta hãy nhìn sâu hơn vào những gì đang diễn ra.

Bên trong máy ảo Python

Cpython sử dụng một máy ảo dựa trên ngăn xếp. Đó là, nó được định hướng hoàn toàn xung quanh các cấu trúc dữ liệu ngăn xếp (nơi bạn có thể "đẩy" một mục lên "trên cùng" của cấu trúc hoặc "bật" một mục ra khỏi "trên cùng").

CPython sử dụng ba loại ngăn xếp:

  1. Các ngăn xếp cuộc gọi. Đây là cấu trúc chính của một chương trình Python đang chạy. Nó có một mục, một "khung", cho mỗi cuộc gọi chức năng hiện đang hoạt động, với phần dưới của ngăn xếp là điểm nhập của chương trình. Mỗi cuộc gọi chức năng đều đẩy một khung mới vào ngăn xếp cuộc gọi và mỗi khi một chức năng gọi lại, khung của nó được bật ra.call stack. This is the main structure of a running Python program. It has one item—a "frame"—for each currently active function call, with the bottom of the stack being the entry point of the program. Every function call pushes a new frame onto the call stack, and every time a function call returns, its frame is popped off.
  2. Trong mỗi khung, có một ngăn xếp đánh giá (còn được gọi là ngăn xếp dữ liệu). Ngăn xếp này là nơi thực hiện chức năng Python xảy ra và thực thi mã Python bao gồm chủ yếu là đẩy mọi thứ lên ngăn xếp này, thao túng chúng và bật chúng ra.evaluation stack (also called the data stack). This stack is where execution of a Python function occurs, and executing Python code consists mostly of pushing things onto this stack, manipulating them, and popping them back off.
  3. Cũng trong mỗi khung, có một ngăn xếp khối. Điều này được Python sử dụng để theo dõi một số loại cấu trúc điều khiển nhất định: vòng lặp, ____ 10/________ 11 khối và

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    2 khối tất cả các mục nhập được đẩy lên ngăn xếp khối và ngăn xếp khối được bật ra bất cứ khi nào bạn thoát khỏi một trong các cấu trúc đó. Điều này giúp Python biết khối nào đang hoạt động tại bất kỳ thời điểm nào để, ví dụ, một câu lệnh

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    3 hoặc

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    4 có thể ảnh hưởng đến khối chính xác.block stack. This is used by Python to keep track of certain types of control structures: loops,

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    0/

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    1 blocks, and

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    2 blocks all cause entries to be pushed onto the block stack, and the block stack gets popped whenever you exit one of those structures. This helps Python know which blocks are active at any given moment so that, for example, a

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    3 or

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    4 statement can affect the correct block.

Hầu hết các hướng dẫn mã byte của Python thao tác với ngăn xếp đánh giá của khung ngăn xếp gọi hiện tại, mặc dù có một số hướng dẫn làm những việc khác (như nhảy vào các hướng dẫn cụ thể hoặc thao tác ngăn xếp khối).

Để có được cảm giác cho điều này, giả sử chúng ta có một số mã gọi một hàm, như sau:

2           0 LOAD_GLOBAL              0 (print)
            2 LOAD_CONST               1 ('Hello, World!')
            4 CALL_FUNCTION            1

5. Python sẽ chuyển điều này thành một chuỗi gồm bốn hướng dẫn mã byte:

  1. Hướng dẫn

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    6 Tra tra đối tượng hàm

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    7 và đẩy nó lên đỉnh của ngăn xếp đánh giá
  2. Một hướng dẫn

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    6 khác để tra cứu biến

    2           0 LOAD_GLOBAL              0 (print)
                2 LOAD_CONST               1 ('Hello, World!')
                4 CALL_FUNCTION            1

    9 và đẩy nó lên đầu ngăn xếp đánh giá
  3. Hướng dẫn

    import dis
    dis.dis(hello)

    0 để đẩy giá trị số nguyên theo nghĩa đen

    import dis
    dis.dis(hello)

    1 lên trên ngăn xếp đánh giá
  4. Một hướng dẫn

    import dis
    dis.dis(hello)

    2

Hướng dẫn

import dis
dis.dis(hello)

2 sẽ có một đối số 2, điều này chỉ ra rằng Python cần bật hai đối số vị trí ra khỏi đỉnh của ngăn xếp; Sau đó, chức năng gọi sẽ ở trên đầu và nó cũng có thể được bật ra (đối với các chức năng liên quan đến đối số từ khóa, một hướng dẫn khác ____________ 24 được sử dụng, nhưng với một nguyên tắc hoạt động tương tự và hướng dẫn thứ ba,

import dis
dis.dis(hello)

5, được sử dụng Đối với các cuộc gọi chức năng liên quan đến việc giải nén đối số với các toán tử

import dis
dis.dis(hello)

6 hoặc

import dis
dis.dis(hello)

7). Khi Python có tất cả những điều đó, nó sẽ phân bổ một khung mới trên ngăn xếp cuộc gọi, hãy đặt các biến cục bộ cho cuộc gọi hàm và thực thi mã byte của

2           0 LOAD_GLOBAL              0 (print)
            2 LOAD_CONST               1 ('Hello, World!')
            4 CALL_FUNCTION            1

7 bên trong khung đó. Sau khi hoàn thành, khung sẽ được bật ra khỏi ngăn xếp cuộc gọi và trong khung ban đầu, giá trị trả về của

2           0 LOAD_GLOBAL              0 (print)
            2 LOAD_CONST               1 ('Hello, World!')
            4 CALL_FUNCTION            1

7 sẽ được đẩy lên trên ngăn xếp đánh giá.

Truy cập và hiểu mã byte Python

Nếu bạn muốn chơi xung quanh với điều này, mô -đun

>>> hello.__code__
", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)

0 trong Thư viện tiêu chuẩn Python là một trợ giúp rất lớn; Mô-đun

>>> hello.__code__
", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)

0 cung cấp "Disassembler" cho mã byte Python, giúp dễ dàng có được phiên bản có thể đọc được của con người và tìm kiếm các hướng dẫn mã byte khác nhau. Tài liệu cho mô -đun

>>> hello.__code__
", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)

0 đi qua nội dung của nó và cung cấp một danh sách đầy đủ các hướng dẫn mã byte cùng với những gì họ làm và những đối số họ thực hiện.

Ví dụ: để có được danh sách mã byte cho hàm hello() ở trên, tôi đã nhập nó vào một trình thông dịch python, sau đó chạy:

import dis
dis.dis(hello)

Hàm

>>> hello.__code__
", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)

4 sẽ tháo rời một hàm, phương thức, lớp, mô-đun, đối tượng mã Python được biên dịch hoặc chuỗi theo nghĩa đen có chứa mã nguồn và in phiên bản có thể đọc được của con người. Một chức năng tiện dụng khác trong mô -đun

>>> hello.__code__
", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)

0 là

>>> hello.__code__
", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)

6. Bạn có thể chuyển nó một đối tượng Python Traceback hoặc gọi nó sau khi một ngoại lệ được nâng lên và nó sẽ tháo rời hàm trên cùng trên ngăn xếp cuộc gọi tại thời điểm ngoại lệ, in mã byte của nó và chèn một con trỏ vào hướng dẫn đã tăng ngoại lệ.

Nó cũng hữu ích khi xem xét các đối tượng mã được biên dịch Python xây dựng cho mọi hàm vì thực thi một hàm sử dụng các thuộc tính của các đối tượng mã đó. Dưới đây là một ví dụ xem xét chức năng hello():

>>> hello.__code__
", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)

Đối tượng mã có thể truy cập được dưới dạng thuộc tính

>>> hello.__code__
", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)

8 trên hàm và mang một vài thuộc tính quan trọng:

  • >>> hello.__code__
    ", line 1>
    >>> hello.__code__.co_consts
    (None, 'Hello, World!')
    >>> hello.__code__.co_varnames
    ()
    >>> hello.__code__.co_names
    ('print',)

    9 là một tuple của bất kỳ chữ nào xảy ra trong cơ thể chức năng
  • .py0 là một tuple chứa tên của bất kỳ biến cục bộ nào được sử dụng trong cơ thể hàm
  • .py1 là một bộ của bất kỳ tên không cục bộ nào được tham chiếu trong cơ thể chức năng

Nhiều hướng dẫn mã byte, đặc biệt là các hướng dẫn tải các giá trị được đẩy lên ngăn xếp hoặc lưu trữ các giá trị theo các biến và thuộc tính sử dụng các chỉ số sử dụng trong các bộ dữ liệu này làm đối số của chúng.

Vì vậy, bây giờ chúng ta có thể hiểu danh sách mã byte của hàm hello():

  1. .py3: bảo Python tra cứu đối tượng toàn cầu được tham chiếu bởi tên tại INDEX 0 của .py1 (là hàm .py5) và đẩy nó lên ngăn xếp đánh giá
  2. .py6: Lấy giá trị theo nghĩa đen tại INDEX 1 của

    >>> hello.__code__
    ", line 1>
    >>> hello.__code__.co_consts
    (None, 'Hello, World!')
    >>> hello.__code__.co_varnames
    ()
    >>> hello.__code__.co_names
    ('print',)

    9 và đẩy nó (giá trị tại chỉ số 0 là nghĩa đen .py8, có mặt trong

    >>> hello.__code__
    ", line 1>
    >>> hello.__code__.co_consts
    (None, 'Hello, World!')
    >>> hello.__code__.co_varnames
    ()
    >>> hello.__code__.co_names
    ('print',)

    9 vì các cuộc gọi chức năng Python có giá trị trả về ngầm là .py8 nếu không có câu lệnh .pyc1
  3. .pyc2: bảo Python gọi chức năng; Nó sẽ cần phải bật một đối số vị trí ra khỏi ngăn xếp, sau đó hàng đầu mới sẽ là chức năng để gọi.

Mã byte "Raw" như các byte không thể đọc được của con người cũng có sẵn trên đối tượng mã dưới dạng thuộc tính .pyc3. Bạn có thể sử dụng danh sách .pyc4 để tra cứu tên của các hướng dẫn mã byte từ các giá trị byte thập phân của chúng nếu bạn muốn cố gắng tháo rời chức năng theo cách thủ công.

Đặt mã byte để sử dụng

Bây giờ bạn đã đọc đến nay, bạn có thể nghĩ "OK, tôi đoán điều đó thật tuyệt, nhưng giá trị thực tế của việc biết điều này là gì?" Đặt sự tò mò sang một bên vì sự tò mò, việc hiểu mã byte Python là hữu ích theo một số cách.

Đầu tiên, hiểu mô hình thực thi của Python giúp bạn lý luận về mã của bạn. Mọi người thích đùa giỡn về C là một loại "trình biên dịch di động", nơi bạn có thể đoán tốt về những hướng dẫn máy mà một đoạn mã nguồn C cụ thể sẽ biến thành. Hiểu về mã byte sẽ cung cấp cho bạn khả năng tương tự với Python, nếu bạn có thể dự đoán mã bytecode mã Python của bạn biến thành gì, bạn có thể đưa ra quyết định tốt hơn về cách viết và tối ưu hóa nó.

Thứ hai, sự hiểu biết bytecode là một cách hữu ích để trả lời các câu hỏi về Python. Ví dụ, tôi thường thấy các lập trình viên Python mới hơn tự hỏi tại sao một số cấu trúc nhất định lại nhanh hơn các cấu trúc khác (như tại sao .pyc5 nhanh hơn .pyc6). Biết cách truy cập và đọc mã byte Python cho phép bạn giải quyết các câu trả lời (hãy thử: .pyc7 so với .pyc8).

Cuối cùng, sự hiểu biết bytecode và cách Python thực hiện nó đưa ra một viễn cảnh hữu ích về một loại lập trình cụ thể mà các lập trình viên Python không thường xuyên tham gia: lập trình định hướng ngăn xếp. Nếu bạn đã từng sử dụng một ngôn ngữ định hướng ngăn xếp như Forth hoặc Factor, thì đây có thể là tin cũ, nhưng nếu bạn không quen thuộc với phương pháp này, hãy tìm hiểu về Python Bytecode và hiểu cách thức mô hình lập trình định hướng ngăn xếp của nó hoạt động là một cách để mở rộng kiến ​​thức lập trình của bạn.

đọc thêm

Nếu bạn muốn tìm hiểu thêm về mã Python byte, máy ảo Python và cách chúng hoạt động, tôi khuyên bạn nên sử dụng các tài nguyên này:

  • Bên trong máy ảo Python của Obi Ike-Nwosu là một cuốn sách trực tuyến miễn phí, đi sâu vào phiên dịch viên Python, giải thích chi tiết về cách thức hoạt động của Python.
  • Một thông dịch viên Python được viết bằng Python của Allison Kaptur là một hướng dẫn để xây dựng một thông dịch viên Bytecode Python trong những gì khác, chính là Python, và nó thực hiện tất cả các máy móc để chạy mã Python byte.
  • Cuối cùng, trình thông dịch Cpython là nguồn mở và bạn có thể đọc qua nó trên GitHub.Việc triển khai trình thông dịch mã byte có trong tệp .pyc9.Đây là tập tin cho bản phát hành Python 3.6.4;Các hướng dẫn bytecode được xử lý bởi câu lệnh .py0 bắt đầu trên dòng 1266.

Để tìm hiểu thêm, hãy tham dự cuộc nói chuyện của James Bennett, một chút về byte: Hiểu mã Python Bytec, tại Pycon Cleveland 2018.

Hướng dẫn which module is used to view the python byte code? - mô-đun nào được sử dụng để xem mã byte python?
Công việc này được cấp phép theo giấy phép quốc tế Creative Commons Attribution-chia sẻ 4.0.