Hướng dẫn does python use bytecode? - python có sử dụng mã byte không?

Nội dung chính ShowShow

  • Tìm hiểu về mã Python byte
  • Bytecode operations
  • Làm thế nào để bạn viết mã byte trong Python?
  • Làm thế nào để Python thực thi mã byte?
  • Tại sao Bytecode được sử dụng trong Python?
  • Làm thế nào bytecode được thực thi?

Tìm hiểu về mã Python byte

Bytecode operations

Làm thế nào để bạn viết mã byte trong Python?

Làm thế nào để Python thực thi mã byte?

Tại sao Bytecode được sử dụng trong Python?

Làm thế nào bytecode được thực thi?

Mã nguồn của ngôn ngữ lập trình có thể được thực thi bằng trình thông dịch hoặc trình biên dịch. Trong một ngôn ngữ được biên dịch, một trình biên dịch sẽ dịch mã nguồn trực tiếp thành mã máy nhị phân. Mã máy này dành riêng cho máy đích đó vì mỗi máy có thể có một hệ điều hành và phần cứng khác nhau. Sau khi biên dịch, máy đích sẽ trực tiếp chạy mã máy.

Trong một ngôn ngữ được giải thích, mã nguồn không được chạy trực tiếp bởi máy đích. Có một chương trình khác được gọi là trình thông dịch đọc và thực thi mã nguồn trực tiếp. Trình thông dịch, cụ thể cho máy đích, dịch từng câu lệnh của mã nguồn thành mã máy và chạy nó.

Python thường được gọi là ngôn ngữ được giải thích, tuy nhiên, nó kết hợp biên dịch và diễn giải. Khi chúng tôi thực thi mã nguồn (một tệp có tiện ích mở rộng

python -m compileall file_1.py ... file_n.py
17), trước tiên, Python biên dịch nó thành mã byte. Bytecode là một biểu diễn độc lập với nền tảng thấp của mã nguồn của bạn, tuy nhiên, đó không phải là mã máy nhị phân và không thể được chạy trực tiếp bởi máy đích. Trên thực tế, nó là một tập hợp các hướng dẫn cho một máy ảo được gọi là máy ảo Python (PVM).

Sau khi biên dịch, mã byte được gửi để thực hiện cho PVM. PVM là một trình thông dịch chạy mã byte và là một phần của hệ thống Python. Mã byte là độc lập với nền tảng, nhưng PVM là cụ thể cho máy đích. Việc triển khai mặc định của ngôn ngữ lập trình Python là CPython được viết bằng ngôn ngữ lập trình C. Cpython biên dịch mã nguồn Python vào mã byte và mã byte này sau đó được thực thi bởi máy ảo CPython.

Tạo tập tin bytecode

Trong Python, mã byte được lưu trữ trong tệp

python -m compileall file_1.py ... file_n.py
18. Trong Python 3, các tệp byte được lưu trữ trong một thư mục có tên
python -m compileall file_1.py ... file_n.py
19. Thư mục này được tự động tạo khi bạn cố gắng nhập một tệp khác mà bạn đã tạo:
import file_name

Tuy nhiên, nó sẽ không được tạo nếu chúng tôi không nhập tệp khác trong mã nguồn. Trong trường hợp đó, chúng ta vẫn có thể tạo ra nó theo cách thủ công. Để biên dịch các tệp riêng lẻ

python -m compileall file_1.py ... file_n.py
20 thành
python -m compileall file_1.py ... file_n.py
21 từ dòng lệnh, chúng ta có thể viết:
python -m compileall file_1.py ... file_n.py

Tất cả các tệp

python -m compileall file_1.py ... file_n.py
22 được tạo sẽ được lưu trữ trong
python -m compileall file_1.py ... file_n.py
23Folder. Nếu bạn không cung cấp tên tệp sau
python -m compileall file_1.py ... file_n.py
24, nó sẽ biên dịch tất cả các tệp mã nguồn Python trong thư mục hiện tại.

python -m compileall file_1.py ... file_n.py
0

Chúng ta cũng có thể sử dụng hàm

python -m compileall file_1.py ... file_n.py
1

python -m compileall file_1.py ... file_n.py
25 để biên dịch một chuỗi chứa mã nguồn Python. Cú pháp của chức năng này là:
python -m compileall file_1.py ... file_n.py
26

python -m compileall file_1.py ... file_n.py
2

Chúng tôi chỉ tập trung vào ba đối số đầu tiên được yêu cầu (những đối số khác là tùy chọn).

python -m compileall file_1.py ... file_n.py
3

python -m compileall file_1.py ... file_n.py
27 là mã nguồn để biên dịch có thể là một chuỗi, đối tượng byte hoặc đối tượng AST.
python -m compileall file_1.py ... file_n.py
28 là tên của tệp mà mã nguồn đến từ. Nếu mã nguồn không đến từ một tệp, bạn có thể viết bất cứ thứ gì bạn thích hoặc để lại một chuỗi trống.
python -m compileall file_1.py ... file_n.py
29 có thể là:
python -m compileall file_1.py ... file_n.py
30: Chấp nhận mã nguồn Python dưới mọi hình thức (bất kỳ số lượng câu lệnh hoặc khối). Nó biên dịch chúng thành một mã byte cuối cùng trả về
python -m compileall file_1.py ... file_n.py
31
python -m compileall file_1.py ... file_n.py
32: Chấp nhận một biểu thức duy nhất và biên dịch nó thành mã byte cuối cùng trả về giá trị của biểu thức đó
python -m compileall file_1.py ... file_n.py
33: Chỉ chấp nhận một câu lệnh duy nhất (hoặc nhiều câu lệnh được phân tách bằng
python -m compileall file_1.py ... file_n.py
34). Nếu câu lệnh cuối cùng là một biểu thức, thì mã byte kết quả sẽ in, giá trị của biểu thức đó thành đầu ra tiêu chuẩn.

Ví dụ: để biên dịch một số câu lệnh Python chúng ta có thể viết:

hoặc viết tương đương:

Để đánh giá một biểu thức chúng ta có thể viết:

Chế độ này gây ra lỗi nếu bạn không có biểu thức:

Ở đây

python -m compileall file_1.py ... file_n.py
36 không phải là một biểu thức và không trả lại bất cứ điều gì, vì vậy chúng tôi không thể sử dụng chế độ
python -m compileall file_1.py ... file_n.py
37. Tuy nhiên, chúng ta có thể sử dụng chế độ
python -m compileall file_1.py ... file_n.py
38 để biên dịch nó:
python -m compileall file_1.py ... file_n.py
7

Nhưng những gì được trả lại bởi

python -m compileall file_1.py ... file_n.py
39? Khi bạn chạy chức năng
python -m compileall file_1.py ... file_n.py
39, Python trả về:
import file_name
0

Vì vậy, những gì hàm

python -m compileall file_1.py ... file_n.py
46.
import file_name
8

Đối tượng mã không chỉ chứa mã byte mà còn một số thông tin khác cần thiết để cpython chạy mã byte (chúng sẽ được thảo luận sau). Một đối tượng mã có thể được thực thi hoặc đánh giá bằng cách chuyển nó cho hàm

python -m compileall file_1.py ... file_n.py
47 hoặc
python -m compileall file_1.py ... file_n.py
48. Vì vậy, chúng tôi có thể viết:
python -m compileall file_1.py ... file_n.py
0

Khi bạn xác định một hàm trong Python, nó sẽ tạo một đối tượng mã cho nó và bạn có thể truy cập nó bằng thuộc tính

python -m compileall file_1.py ... file_n.py
49. Ví dụ: chúng ta có thể viết:
python -m compileall file_1.py ... file_n.py
1

Và đầu ra sẽ là:

python -m compileall file_1.py ... file_n.py
2

Giống như bất kỳ đối tượng nào khác, đối tượng mã có một số thuộc tính và để nhận mã byte được lưu trữ trong một đối tượng mã, bạn có thể sử dụng thuộc tính

python -m compileall file_1.py ... file_n.py
50 của nó:
python -m compileall file_1.py ... file_n.py
3

Đầu ra là:

python -m compileall file_1.py ... file_n.py
4

Kết quả là một byte theo nghĩa đen được đặt trước với

python -m compileall file_1.py ... file_n.py
51, nó là một chuỗi byte bất biến và có một loại
python -m compileall file_1.py ... file_n.py
52. Mỗi byte có thể có giá trị thập phân từ 0 đến 255. Vì vậy, một byte theo nghĩa đen là một chuỗi số nguyên bất động trong khoảng từ 0 đến 255. Mỗi byte có thể được hiển thị bởi một ký tự ASCII có mã ký tự giống như giá trị byte hoặc nó có thể Thể hiện bởi một
python -m compileall file_1.py ... file_n.py
53 hàng đầu theo sau là hai ký tự. Escape
python -m compileall file_1.py ... file_n.py
53 hàng đầu có nghĩa là hai ký tự tiếp theo được hiểu là các chữ số hex cho mã ký tự. Ví dụ:
python -m compileall file_1.py ... file_n.py
5

gives:

python -m compileall file_1.py ... file_n.py
6

Vì phần tử đầu tiên có giá trị thập phân là 101 và có thể được hiển thị với ký tự

python -m compileall file_1.py ... file_n.py
55 có mã ký tự ASCII là 101. hoặc::
python -m compileall file_1.py ... file_n.py
7

gives:

python -m compileall file_1.py ... file_n.py
8

Vì phần tử thứ 4 có giá trị thập phân là 131. Giá trị thập lục phân là 131 là 83. Vì vậy, byte này có thể được hiển thị với một ký tự có mã ký tự là

python -m compileall file_1.py ... file_n.py
56.

Các chuỗi byte này có thể được giải thích bằng Cpython, nhưng chúng không thân thiện với con người. Vì vậy, chúng ta cần hiểu làm thế nào các byte này được ánh xạ tới các hướng dẫn thực tế sẽ được thực thi bởi CPython. Trong phần tiếp theo, chúng tôi sẽ tháo rời mã byte thành một số hướng dẫn thân thiện với con người để xem mã byte được thực thi bởi CPython như thế nào.

Chi tiết mã byte

Trước khi đi sâu vào chi tiết, điều quan trọng cần lưu ý là việc triển khai chi tiết mã byte thường thay đổi giữa các phiên bản của Python. Vì vậy, những gì bạn thấy trong bài viết này có thể không hợp lệ cho tất cả các phiên bản của Python. Trên thực tế, nó bao gồm các thay đổi xảy ra trong phiên bản 3.6 và một số chi tiết có thể không hợp lệ cho các phiên bản cũ hơn. Mã trong bài viết này đã được thử nghiệm với Python 3.7.

Mã byte có thể được coi là một loạt các hướng dẫn hoặc một chương trình cấp thấp cho trình thông dịch Python. Sau phiên bản 3.6, Python sử dụng 2 byte cho mỗi lệnh. Một byte là cho mã của hướng dẫn đó được gọi là opcode và một byte được dành riêng cho itargumentwhich được gọi là oparg. Mỗi opcode có một tên thân thiện với con người được gọi là opname. Các hướng dẫn bytecode có định dạng chung như thế này:

python -m compileall file_1.py ... file_n.py
9

Chúng tôi đã có mã hóa trong mã byte của chúng tôi và chúng tôi chỉ cần ánh xạ chúng đến tên opname tương ứng của chúng. Có một mô -đun gọi là

python -m compileall file_1.py ... file_n.py
57 có thể giúp với điều đó. Trong mô -đun này, có một danh sách gọi là
python -m compileall file_1.py ... file_n.py
58 lưu trữ tất cả các tên opnames. Phần tử thứ i của danh sách này cung cấp tên opname cho một lệnh có mã opcode bằng i.

Một số hướng dẫn không cần một đối số, vì vậy họ bỏ qua byte sau opcode. Các opcodes có giá trị dưới một số nhất định bỏ qua đối số của họ. Giá trị này được lưu trữ trong

python -m compileall file_1.py ... file_n.py
59 và hiện bằng 90. Vì vậy, các opcodes> = ________ 159 có một đối số và các opcodes

Ví dụ, giả sử rằng chúng ta có một mã byte ngắn

python -m compileall file_1.py ... file_n.py
62 và chúng ta muốn tháo rời nó. Mã byte này đại diện cho một chuỗi bốn byte. Chúng ta có thể dễ dàng hiển thị giá trị thập phân của chúng:
python -m compileall file_1.py ... file_n.py
00

Đầu ra sẽ là:

python -m compileall file_1.py ... file_n.py
01

Hai byte đầu tiên của mã byte là

python -m compileall file_1.py ... file_n.py
63. Byte đầu tiên là opcode. Để có được tên opname của nó, chúng ta có thể viết (
python -m compileall file_1.py ... file_n.py
57 nên được nhập trước):
python -m compileall file_1.py ... file_n.py
02

và kết quả là

python -m compileall file_1.py ... file_n.py
65. Vì opcode lớn hơn
python -m compileall file_1.py ... file_n.py
59, nên nó có Oparg là byte thứ hai
python -m compileall file_1.py ... file_n.py
67. Vì vậy,
python -m compileall file_1.py ... file_n.py
63 dịch thành:
python -m compileall file_1.py ... file_n.py
03

Hai byte cuối cùng trong mã byte là

python -m compileall file_1.py ... file_n.py
69. Một lần nữa chúng tôi viết
python -m compileall file_1.py ... file_n.py
70 và kết quả là
python -m compileall file_1.py ... file_n.py
71. 83 thấp hơn 90 (
python -m compileall file_1.py ... file_n.py
59), do đó, opcode này bỏ qua oparg và
python -m compileall file_1.py ... file_n.py
69 được tháo rời vào:
python -m compileall file_1.py ... file_n.py
04

Ngoài ra, một số hướng dẫn có thể có một đối số quá lớn để phù hợp với một byte mặc định. Có một opcode đặc biệt

python -m compileall file_1.py ... file_n.py
74 để xử lý các hướng dẫn này. Tên opname của nó là
python -m compileall file_1.py ... file_n.py
75, và nó cũng được lưu trữ trong
python -m compileall file_1.py ... file_n.py
76. Opcode này có tiền tố bất kỳ opcode nào có đối số lớn hơn một byte. Ví dụ: giả sử rằng chúng ta có opcode 131 (tên opname của nó là
python -m compileall file_1.py ... file_n.py
77) và Oparg của nó cần phải là 260. Vì vậy, nó phải là:
python -m compileall file_1.py ... file_n.py
05

Tuy nhiên, số lượng tối đa mà một byte có thể lưu trữ là 255 và 260 không phù hợp với byte. Vì vậy, opcode này được tiền tố với

python -m compileall file_1.py ... file_n.py
75:
python -m compileall file_1.py ... file_n.py
06

Khi trình thông dịch thực thi

python -m compileall file_1.py ... file_n.py
75, oparg của nó (là 1) được dịch chuyển trái bởi tám bit và được lưu trữ trong một biến tạm thời. Hãy để Lừa gọi nó là
python -m compileall file_1.py ... file_n.py
80 (đừng nhầm lẫn với opname
python -m compileall file_1.py ... file_n.py
75):
python -m compileall file_1.py ... file_n.py
07

Vì vậy, giá trị nhị phân

python -m compileall file_1.py ... file_n.py
82 (giá trị nhị phân của 1) được chuyển đổi thành
python -m compileall file_1.py ... file_n.py
83. Điều này giống như nhân 1 với 256 trong hệ thống thập phân và
python -m compileall file_1.py ... file_n.py
84 sẽ bằng 256. Bây giờ chúng ta có hai byte trong
python -m compileall file_1.py ... file_n.py
84. Khi trình thông dịch đạt được hướng dẫn tiếp theo, giá trị hai byte này được thêm vào oparg của nó (là 4 ở đây) bằng cách sử dụng bitwise
python -m compileall file_1.py ... file_n.py
86.
python -m compileall file_1.py ... file_n.py
08

Điều này giống như thêm giá trị của OPARG vào

python -m compileall file_1.py ... file_n.py
84. Vì vậy, bây giờ chúng tôi có:
python -m compileall file_1.py ... file_n.py
09

và giá trị này sẽ được sử dụng làm oparg thực tế của

python -m compileall file_1.py ... file_n.py
77. Vì vậy, trên thực tế,
python -m compileall file_1.py ... file_n.py
06

được giải thích là:

python -m compileall file_1.py ... file_n.py
11

Đối với mỗi opcode, nhiều nhất là ba tiền tố

python -m compileall file_1.py ... file_n.py
75 được cho phép, tạo thành một đối số từ hai byte đến bốn byte.

Bây giờ chúng ta có thể tập trung vào chính Oparg. Nó có nghĩa là gì? Trên thực tế, ý nghĩa của mỗi oparg phụ thuộc vào opcode của nó. Như đã đề cập trước đây, đối tượng mã lưu trữ một số thông tin khác ngoài mã byte. Thông tin này có thể được truy cập bằng cách sử dụng các thuộc tính khác nhau của đối tượng mã và chúng tôi cần một số thuộc tính này để giải mã ý nghĩa của mỗi oparg. Các thuộc tính này là:

python -m compileall file_1.py ... file_n.py
90,
python -m compileall file_1.py ... file_n.py
91,
python -m compileall file_1.py ... file_n.py
92,
python -m compileall file_1.py ... file_n.py
93 và
python -m compileall file_1.py ... file_n.py
94.

Thuộc tính đối tượng mã

Tôi sẽ giải thích ý nghĩa của các thuộc tính này bằng một ví dụ. Giả sử bạn có đối tượng mã của mã nguồn này:

python -m compileall file_1.py ... file_n.py
12

Bây giờ chúng ta có thể kiểm tra những gì được lưu trữ trong mỗi thuộc tính này:

1 -________ 190: Một tuple chứa các chữ được sử dụng bởi mã byte. Ở đây

python -m compileall file_1.py ... file_n.py
96 Trả về:
python -m compileall file_1.py ... file_n.py
13

Vì vậy, các chữ

python -m compileall file_1.py ... file_n.py
97 và
python -m compileall file_1.py ... file_n.py
98 và tên của hàm
python -m compileall file_1.py ... file_n.py
99 đều được lưu trữ trong bộ tuple này. Ngoài ra, phần thân của hàm
python -m compileall file_1.py ... file_n.py
000 được lưu trữ trong một đối tượng mã riêng biệt và được đối xử như một chữ theo nghĩa đen cũng được lưu trữ trong bộ tu này. Hãy nhớ rằng chế độ
python -m compileall file_1.py ... file_n.py
001 trong
python -m compileall file_1.py ... file_n.py
25 tạo mã byte cuối cùng trả về
python -m compileall file_1.py ... file_n.py
31. Giá trị
python -m compileall file_1.py ... file_n.py
31 này cũng được lưu trữ dưới dạng theo nghĩa đen. Trên thực tế, nếu bạn biên dịch một biểu thức trong chế độ
python -m compileall file_1.py ... file_n.py
37 như thế này:
python -m compileall file_1.py ... file_n.py
14
python -m compileall file_1.py ... file_n.py
31 won đã được đưa vào bộ
python -m compileall file_1.py ... file_n.py
90 nữa. Lý do là biểu thức này trả về giá trị cuối cùng của nó không phải
python -m compileall file_1.py ... file_n.py
31.

Nếu bạn cố gắng lấy

python -m compileall file_1.py ... file_n.py
009 cho mã đối tượng của một hàm như:
python -m compileall file_1.py ... file_n.py
15

Kết quả sẽ là

python -m compileall file_1.py ... file_n.py
010. Trên thực tế, giá trị trả về mặc định cho một hàm là
python -m compileall file_1.py ... file_n.py
31 và nó luôn được thêm vào dưới dạng theo nghĩa đen. Như tôi đã giải thích sau, vì lợi ích của hiệu quả, Python không kiểm tra xem bạn có luôn luôn đạt được câu lệnh
python -m compileall file_1.py ... file_n.py
012 hay không, vì vậy
python -m compileall file_1.py ... file_n.py
31 luôn được thêm vào làm giá trị trả về mặc định.

2 -________ 191: Một tuple chứa các tên được sử dụng bởi mã byte có thể là các biến, hàm và các lớp toàn cầu hoặc các thuộc tính được tải từ các đối tượng. Ví dụ, đối với mã đối tượng trong Liệt kê 1,

python -m compileall file_1.py ... file_n.py
015 đưa ra:
python -m compileall file_1.py ... file_n.py
16

3 -________ 192: Một tuple chứa các tên cục bộ được sử dụng bởi mã byte (đối số đầu tiên, sau đó là các biến cục bộ). Nếu chúng tôi thử nó cho mã đối tượng của Danh sách 1, nó sẽ cho một bộ xử lý trống. Lý do là các tên cục bộ được xác định bên trong các chức năng và hàm bên trong Liệt kê 1 được lưu trữ dưới dạng đối tượng mã riêng biệt, do đó các biến cục bộ của nó sẽ không được đưa vào trong bộ thuật này. Để truy cập các biến cục bộ của một hàm, chúng ta nên sử dụng thuộc tính này cho đối tượng mã của hàm đó. Vì vậy, trước tiên chúng tôi viết mã nguồn này:

python -m compileall file_1.py ... file_n.py
17

Bây giờ

python -m compileall file_1.py ... file_n.py
017 cung cấp cho đối tượng mã của
python -m compileall file_1.py ... file_n.py
000 và
python -m compileall file_1.py ... file_n.py
019 đưa ra:
python -m compileall file_1.py ... file_n.py
18

Tại sao

python -m compileall file_1.py ... file_n.py
020 không được bao gồm? Lý do là
python -m compileall file_1.py ... file_n.py
020 không phải là biến cục bộ là
python -m compileall file_1.py ... file_n.py
000. Đây là một biến không thuộc địa do nó được truy cập bởi việc đóng
python -m compileall file_1.py ... file_n.py
023 bên trong
python -m compileall file_1.py ... file_n.py
000. Trên thực tế,
python -m compileall file_1.py ... file_n.py
025 cũng là một biến không thuộc địa điểm, nhưng vì đó là đối số chức năng, nó luôn được bao gồm trong bộ tu này. Để tìm hiểu thêm về việc đóng cửa và các biến phi địa phương, bạn có thể tham khảo bài viết này.

4 -________ 193: Một tuple chứa tên của các biến không thuộc địa. Đây là các biến cục bộ của một hàm được truy cập bởi các hàm bên trong của nó. Vì vậy,

python -m compileall file_1.py ... file_n.py
027 đưa ra:
python -m compileall file_1.py ... file_n.py
19

5 -________ 194: Một tuple chứa tên của các biến miễn phí. Các biến miễn phí là các biến cục bộ của hàm bên ngoài được truy cập bởi hàm bên trong của nó. Vì vậy, thuộc tính này nên được sử dụng với đối tượng mã của việc đóng

python -m compileall file_1.py ... file_n.py
029. Bây giờ
python -m compileall file_1.py ... file_n.py
030 cho kết quả tương tự:A tuple containing the names of free variables. Free variables are the local variables of an outer function which are accessed by its inner function. So this attribute should be used with the code object of the closure
python -m compileall file_1.py ... file_n.py
029. Now
python -m compileall file_1.py ... file_n.py
030 gives the same result:
python -m compileall file_1.py ... file_n.py
19A tuple containing the names of free variables. Free variables are the local variables of an outer function which are accessed by its inner function. So this attribute should be used with the code object of the closure
python -m compileall file_1.py ... file_n.py
029. Now
python -m compileall file_1.py ... file_n.py
030 gives the same result:
python -m compileall file_1.py ... file_n.py
19

Bây giờ chúng ta đã quen thuộc với các thuộc tính này, chúng ta có thể quay lại Opargs. Ý nghĩa của mỗi oparg phụ thuộc vào opcode của nó. Chúng tôi có các loại opcode khác nhau và đối với mỗi loại, oparg có một ý nghĩa khác nhau. Trong mô -đun

python -m compileall file_1.py ... file_n.py
57, có một số danh sách cung cấp mã hóa cho từng loại:

1 -________ 232: Danh sách này bằng [100]. Vì vậy, chỉ có opcode 100 (opname của nó là load_const) nằm trong danh mục

python -m compileall file_1.py ... file_n.py
033. Oparg của opcode này cung cấp chỉ số của một phần tử trong bộ
python -m compileall file_1.py ... file_n.py
90. Ví dụ: trong mã byte của Danh sách 1, nếu chúng ta có:
python -m compileall file_1.py ... file_n.py
21

sau đó OPARG là yếu tố của

python -m compileall file_1.py ... file_n.py
90 có chỉ số là 1. vì vậy chúng ta nên thay thế
python -m compileall file_1.py ... file_n.py
036 bằng
python -m compileall file_1.py ... file_n.py
037 bằng
python -m compileall file_1.py ... file_n.py
98. Vì vậy, hướng dẫn sẽ được giải thích là:
python -m compileall file_1.py ... file_n.py
22

Tương tự, có một số danh sách khác trong mô -đun

python -m compileall file_1.py ... file_n.py
57 xác định các danh mục khác cho các opcodes:

2 -________ 240: Oparg cho các opcode trong danh sách này, là chỉ số của một phần tử trong

python -m compileall file_1.py ... file_n.py
91

3 -________ 242: Oparg cho các opcode trong danh sách này, là chỉ số của một yếu tố trong

python -m compileall file_1.py ... file_n.py
92

4 -________ 244: Oparg cho các opcode trong danh sách này, là chỉ số của một phần tử trong

python -m compileall file_1.py ... file_n.py
045

5 -________ 246: Oparg cho opcode trong danh sách này, là chỉ số của một phần tử của tuple

python -m compileall file_1.py ... file_n.py
047. Tuple này chứa các nhà khai thác so sánh và tư cách thành viên như
python -m compileall file_1.py ... file_n.py
048 hoặc
python -m compileall file_1.py ... file_n.py
049

6 -________ 250: OPARG cho các opcode trong danh sách này, nên được thay thế bằng

python -m compileall file_1.py ... file_n.py
051 trong đó
python -m compileall file_1.py ... file_n.py
052 là chỉ số của byte trong chuỗi bytecode đại diện cho opcode.

Đối tượng mã có một thuộc tính quan trọng hơn cần được thảo luận ở đây. Nó được gọi là

python -m compileall file_1.py ... file_n.py
053 lưu trữ thông tin số dòng của mã byte. Đây là một mảng các byte được ký kết được lưu trữ trong một byte theo nghĩa đen và được sử dụng để ánh xạ các độ lệch mã byte cho các số dòng mã nguồn. Hãy để tôi giải thích nó bằng một ví dụ. Giả sử rằng mã nguồn của bạn chỉ có ba dòng và nó đã được biên dịch thành mã byte có 24 byte:
python -m compileall file_1.py ... file_n.py
23

Bây giờ chúng tôi có một ánh xạ từ mã ByteCode đến các số dòng như bảng này:

ByteCode Offset luôn bắt đầu ở 0. Đối tượng mã có một thuộc tính có tên

python -m compileall file_1.py ... file_n.py
054 cung cấp số dòng cho số bù 0. Đối với ví dụ này
python -m compileall file_1.py ... file_n.py
054 bằng 1. Thay vì lưu trữ các số bù và dòng theo nghĩa đen, Python chỉ lưu trữ các mức tăng từ hàng này sang hàng tiếp theo (không bao gồm hàng đầu tiên). Vì vậy, bảng trước đó biến thành:

Hai cột gia tăng này được nén lại với nhau theo một chuỗi như thế này:

python -m compileall file_1.py ... file_n.py
24

Mỗi số được lưu trữ trong một byte và toàn bộ chuỗi được lưu trữ dưới dạng byte theo nghĩa đen trong

python -m compileall file_1.py ... file_n.py
053 của đối tượng mã. Vì vậy, nếu bạn kiểm tra giá trị của
python -m compileall file_1.py ... file_n.py
053 bạn nhận được:
python -m compileall file_1.py ... file_n.py
25

đó là các byte theo nghĩa đen cho chuỗi trước. Vì vậy, bằng cách có các thuộc tính

python -m compileall file_1.py ... file_n.py
053 và
python -m compileall file_1.py ... file_n.py
054, bạn có thể truy xuất ánh xạ từ độ lệch mã byte đến các số dòng mã nguồn.
python -m compileall file_1.py ... file_n.py
053 là một chuỗi các byte có chữ ký. Vì vậy, mỗi byte được ký trong đó có thể lấy giá trị từ -128 đến 127 (các giá trị này vẫn được lưu trữ trong một byte có 0 đến 255. nhưng giá trị từ 128 đến 255 được coi là số âm). Một mức tăng âm có nghĩa là số dòng đang giảm (tính năng này được sử dụng trong các trình tối ưu hóa). Nhưng điều gì xảy ra nếu mức tăng dòng lớn hơn 127? Trong trường hợp đó, mức tăng dòng sẽ được chia thành 127 và một số byte bổ sung và các byte bổ sung đó sẽ được lưu trữ với mức tăng bù 0 (nếu nó nhỏ hơn -128, nó sẽ được chia thành -128 và một số byte bổ sung với tăng áp gia tăng không). Ví dụ: giả sử rằng phần bù bytecode so với số dòng giống như sau:

Sau đó, mức tăng bù so với mức tăng số dòng phải là:

139 bằng 127 + 12. Vì vậy, hàng trước nên được viết là:

và nên được lưu trữ dưới dạng

python -m compileall file_1.py ... file_n.py
061. Vì vậy, giá trị của
python -m compileall file_1.py ... file_n.py
053 sẽ là:
python -m compileall file_1.py ... file_n.py
063.

Tháo rời mã byte

Bây giờ chúng tôi đã quen thuộc với cấu trúc byte, chúng tôi có thể viết một chương trình Disassembler đơn giản. Trước tiên, chúng tôi viết một hàm trình tạo để giải nén từng lệnh và mang lại phần bù, opcode và oparg:

Hàm này đọc cặp byte tiếp theo từ mã byte. Byte đầu tiên là opcode. Bằng cách so sánh opcode này với

python -m compileall file_1.py ... file_n.py
59, chức năng quyết định xem nó có nên lấy byte thứ hai làm oparg hay bỏ qua nó không. Giá trị của
python -m compileall file_1.py ... file_n.py
80 sẽ được thêm vào Oparg bằng cách sử dụng bitwise hoặc (
python -m compileall file_1.py ... file_n.py
066). Ban đầu, nó bằng không và không có tác dụng đối với Oparg. Nếu opcode bằng
python -m compileall file_1.py ... file_n.py
76, OPARG của nó sẽ được dịch chuyển trái bởi tám bit và được lưu trữ trong một biến tạm thời gọi là
python -m compileall file_1.py ... file_n.py
80.

Trong lần lặp tiếp theo, biến tạm thời này sẽ được thêm vào OPARG tiếp theo và thêm một byte vào nó. Quá trình này tiếp tục nếu opcode tiếp theo là

python -m compileall file_1.py ... file_n.py
76 một lần nữa và mỗi lần thêm một byte vào
python -m compileall file_1.py ... file_n.py
80. Cuối cùng, khi nó đạt đến một opcode khác,
python -m compileall file_1.py ... file_n.py
80 sẽ được thêm vào oparg của nó và đặt trở lại 0.

The

python -m compileall file_1.py ... file_n.py
072 function returns a dictionary that contains the source code line number for each bytecode offset.

It first divided the

python -m compileall file_1.py ... file_n.py
053 bytes literal into two sequences. One is the offset increments and the other is the line number increments. The line number for offset
python -m compileall file_1.py ... file_n.py
67 is in
python -m compileall file_1.py ... file_n.py
054. The increments are added to these two numbers to get the bytecode offset and its corresponding line number. If the line number increment is equal or bigger than 128 (0x80), it will be considered a decrement.

The

python -m compileall file_1.py ... file_n.py
072 function returns a dictionary that contains the source code line number for each bytecode offset.

The

python -m compileall file_1.py ... file_n.py
072 function returns a dictionary that contains the source code line number for each bytecode offset.

It first divided the

python -m compileall file_1.py ... file_n.py
053 bytes literal into two sequences. One is the offset increments and the other is the line number increments. The line number for offset
python -m compileall file_1.py ... file_n.py
67 is in
python -m compileall file_1.py ... file_n.py
054. The increments are added to these two numbers to get the bytecode offset and its corresponding line number. If the line number increment is equal or bigger than 128 (0x80), it will be considered a decrement.

python -m compileall file_1.py ... file_n.py
076 function returns the human-friendly meaning of each oparg. It first checks to which category the opcode belongs and then figures out what the oparg is referring to.

python -m compileall file_1.py ... file_n.py
26

python -m compileall file_1.py ... file_n.py
077 function finds all the offsets in the bytecode which are jump targets and returns a list of these offsets. The jump targets will be discussed in the next section.

Now we can use all these functions to disassemble the bytecode. The

python -m compileall file_1.py ... file_n.py
078 function takes a code object and disassembles it:

python -m compileall file_1.py ... file_n.py
28

It will first unpack the offset, opcode and oparg for each pair of bytes in the bytecode of the code object. Then it finds the corresponding source code line numbers, and checks if the offset is a jump target. Finally, it finds the opname and the meaning of the oparg and prints all the information. As mentioned before each function definition is stored in a separate code object. So at the end the function calls itself recursively to disassemble all the function definitions in the bytecode. Here is an example of using this function. Initially, we have this source code:

We first store it in a string and compile it to get the object code. Then we use the

python -m compileall file_1.py ... file_n.py
079 function to disassemble its bytecode:
python -m compileall file_1.py ... file_n.py
27

The output is:

So 4 lines of source code are converted into 38 bytes of bytecode or 19 lines of bytecode. In the next section, I will explain the meaning of these instructions and how they will be interpreted by CPython.

The module

python -m compileall file_1.py ... file_n.py57 has a function named python -m compileall file_1.py ... file_n.py081 which can disassemble the code object similarly. In fact, the python -m compileall file_1.py ... file_n.py082 function in this article is a simplified version of python -m compileall file_1.py ... file_n.py083 function. So instead of writing, python -m compileall file_1.py ... file_n.py084 we could write python -m compileall file_1.py ... file_n.py085 to get a similar output.

Disassembling a pyc file

As mentioned before, when the source code is compiled, the bytecode is stored in a

python -m compileall file_1.py ... file_n.py
22 file. This bytecode can be disassembled in a similar way. However, it is important to mention that the
python -m compileall file_1.py ... file_n.py
22 file contains some metadata plus the code object in marshal format. The marshal format is used for Python’s internal object serialization. The size of the metadata depends on the Python version, and for version 3.7 it is 16 bytes. So when you read the
python -m compileall file_1.py ... file_n.py
22 file, first you should read the metadata, and then load the code object using the
python -m compileall file_1.py ... file_n.py
089 module. For example, to disassemble a
python -m compileall file_1.py ... file_n.py
22 file named
python -m compileall file_1.py ... file_n.py
091 in the
python -m compileall file_1.py ... file_n.py
19 folder we can write:

  • Bytecode operations
  • So far we learned how to disassemble the bytecode instructions. We can now focus on the meaning of these instructions and how they are executed by CPython. CPython which is the default implementation of Python uses a stack-based virtual machine. So first we should get familiar with the stack.

Stack and heap

Dữ liệu trong Python được biểu diễn dưới dạng các đối tượng được lưu trữ trên một đống riêng tư. Việc truy cập dữ liệu trên Heap chậm hơn một chút so với ngăn xếp, tuy nhiên, kích thước của Heap chỉ bị giới hạn bởi kích thước của bộ nhớ ảo. Các yếu tố của Heap không có sự phụ thuộc với nhau và có thể được truy cập ngẫu nhiên bất cứ lúc nào. Tất cả mọi thứ trong Python là một đối tượng và các đối tượng luôn được lưu trữ trên đống. Nó chỉ có tham chiếu (hoặc con trỏ) đến đối tượng được lưu trữ trong ngăn xếp.

Cpython sử dụng ngăn xếp cuộc gọi để chạy chương trình Python. Khi một hàm được gọi trong Python, một khung mới được đẩy lên 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ó sẽ bị tắt. Mô-đun mà chương trình chạy có khung dưới cùng được gọi là khung toàn cầu hoặc khung mô-đun.is pushed onto the call stack, and every time a function call returns, its frame is popped off. The module in which the program runs has the bottom-most frame which is called the global frame or the module frame.

Mỗi khung hình có một ngăn xếp đánh giá trong đó việc thực hiện chức năng Python xảy ra. Các đối số chức năng và các biến cục bộ của nó được đẩy vào ngăn xếp đánh giá này. CPython sử dụng ngăn xếp đánh giá để lưu trữ các tham số cần thiết cho bất kỳ hoạt động nào và cũng là kết quả của các hoạt động đó. Trước khi bắt đầu thao tác đó, tất cả các tham số cần thiết được đẩy vào ngăn xếp đánh giá. Sau đó, hoạt động được bắt đầu và nó bật các tham số của nó. Khi hoạt động kết thúc, nó đẩy kết quả trở lại vào ngăn xếp đánh giá.

Tất Cả Các Đối tượng Đan lưu trữ trên đeo Vì vậy, các tham Hầu hết các Hướng dẫn mà byte python, Thao Tác ngăn xếp Đangnh Giá Trong Khung Hiện Tại. B ết này bất nào.

HÃY Để TUY

python -m compileall file_1.py ... file_n.py
29

Để lÀm Điều Đó, Chúng Ta ngẫu, Viết:

python -m compileall file_1.py ... file_n.py
30

Và Chún tôm

python -m compileall file_1.py ... file_n.py
31

Ngoài ra, chún taó thể kiểm tra một

python -m compileall file_1.py ... file_n.py
32

Ở Đây MÃ ĐANG CHạY TRONG Mô -đun, vì vậy chún tôm Hướng dẫn đầu tin lào

python -m compileall file_1.py ... file_n.py
093. Hướng dẫn ________ 233

Đẩy Giá trị Của

python -m compileall file_1.py ... file_n.py
094 Lên ngăn xếp. Vì vậy, Chún tôm

Điều Quan trọng cần lưu ý là Stack Hoạt Động Với Các thim Chiếu Đến Các ĐI Tượng. Vì vậy, bất cứ Khih Điều tương tự cũng xảy ra khi biết Một lần nữa tào liệu Tham Khảo của nó Đan bật lênn. Trình Thông dịch biết cách truy xuất hoặc lưu trữ dữ liệu của đối tượng bằng các tào liệu tham

Hướng dẫn

python -m compileall file_1.py ... file_n.py
34

Bật phần trên cùng của ngăn xếp và lưu trữ nó vào

python -m compileall file_1.py ... file_n.py
097 CủA ĐốI Tượng MÃ. Vì vậy,
python -m compileall file_1.py ... file_n.py
098 Tham Chiếu Đến Đối tượng này là
python -m compileall file_1.py ... file_n.py
100 là
python -m compileall file_1.py ... file_n.py
101. Hai hướng dẫn này là mà byte tương Đương với
python -m compileall file_1.py ... file_n.py
102 Trong MÃ Nguồn.
python -m compileall file_1.py ... file_n.py
103 ĐượC Chuyển Đổ Dngng Cuối Cùng Của MÃ Nguồn Là
python -m compileall file_1.py ... file_n.py
106. Hướng dẫn ________ 235

Bật hai yếu tố hàng Đầu của ngăn xếp (

python -m compileall file_1.py ... file_n.py
036 VÀ Vì vậy, bây giờ
python -m compileall file_1.py ... file_n.py
109 là trên đỉnh của ngăn xếp. Sau Đói
python -m compileall file_1.py ... file_n.py
111 Bật Đ-NH Bây GIờ HÃY NHớ RằNG
python -m compileall file_1.py ... file_n.py
39 TRUNG Hướng dẫn
python -m compileall file_1.py ... file_n.py
116 ĐẩY
python -m compileall file_1.py ... file_n.py
117

Trả về với phần trênn cIng của ngăn xếp Cho người gọi của chức năng. Tất Nhiênn, ở Đây Chún tôm Đoang ở Trong ph

python -m compileall file_1.py ... file_n.py
31 là Kết Quả CUốI CùnG vẫn Cuy TrênN ĐỉNH CủA ngăn xếp Toàn Cầu. Hình 1 cho thấy tất cả các hoạt động bytecode với độ lệch 0 đến 14 (một lần nữa cần lưu ý rằng các tham chiếu đến các đối tượng được đẩy lên ngăn xếp, không phải các đối tượng hoặc giá trị của chúng. Hình không Hiển thị rõ ràng).

Chức năng

Bây GIờ, HÃY Để XEM NHữNG Gì xảy ra nếu chún ta cũng đó Chún Tôi Sẽ Tháio rời MÃ byte

python -m compileall file_1.py ... file_n.py
37

Đầu ra là:

python -m compileall file_1.py ... file_n.py
38

Ngoài ra, chúng ta có thể kiểm tra một số thuộc tính khác của đối tượng mã:

python -m compileall file_1.py ... file_n.py
39

Ở đây mã đang chạy trong mô -đun, vì vậy chúng tôi đang ở trong khung toàn cầu. Hướng dẫn đầu tiên là

python -m compileall file_1.py ... file_n.py
093. Hướng dẫn

Đẩy giá trị của

python -m compileall file_1.py ... file_n.py
094 lên ngăn xếp. Vì vậy, chúng tôi đang đẩy ________ 295 (bằng
python -m compileall file_1.py ... file_n.py
036) lên ngăn xếp.
python -m compileall file_1.py ... file_n.py
70

Điều quan trọng cần lưu ý là Stack hoạt động với các tham chiếu đến các đối tượng. Vì vậy, bất cứ khi nào chúng ta nói rằng một lệnh đẩy một đối tượng hoặc giá trị của một đối tượng lên ngăn xếp, điều đó có nghĩa là một tham chiếu (hoặc con trỏ) đến đối tượng đó đang được đẩy. Điều tương tự cũng xảy ra khi một đối tượng hoặc giá trị của nó được bật ra khỏi ngăn xếp. Một lần nữa tài liệu tham khảo của nó được bật lên. Trình thông dịch biết cách truy xuất hoặc lưu trữ dữ liệu của đối tượng bằng các tài liệu tham khảo này.

Hướng dẫn

python -m compileall file_1.py ... file_n.py
71

được sử dụng để tạo chức năng. Nó cần một số thông số nên được đẩy lên ngăn xếp. Tên của chức năng phải nằm trên đầu ngăn xếp và đối tượng mã chức năng phải ở bên dưới nó. Trong ví dụ này, OPARG của nó bằng không, nhưng nó có thể có các giá trị khác. Ví dụ: nếu định nghĩa hàm có đối số từ khóa như:

python -m compileall file_1.py ... file_n.py
72

Sau đó, mã byte được tháo rời cho dòng 2 sẽ là:

python -m compileall file_1.py ... file_n.py
73

Một oparg là

python -m compileall file_1.py ... file_n.py
036 cho
python -m compileall file_1.py ... file_n.py
136 chỉ ra rằng hàm có một số đối số từ khóa và một tuple chứa các giá trị mặc định phải được đẩy lên ngăn xếp trước đối tượng mã hàm (ở đây là
python -m compileall file_1.py ... file_n.py
137). Sau khi tạo chức năng,
python -m compileall file_1.py ... file_n.py
136 đẩy đối tượng chức năng mới lên ngăn xếp. Sau đó, tại Offset 14,
python -m compileall file_1.py ... file_n.py
111 bật đối tượng chức năng và lưu trữ nó dưới dạng đối tượng hàm được tham chiếu bởi
python -m compileall file_1.py ... file_n.py
000.

Bây giờ, hãy để Lừa nhìn vào bên trong đối tượng mã của

python -m compileall file_1.py ... file_n.py
141 bắt đầu ở dòng 5. Câu lệnh
python -m compileall file_1.py ... file_n.py
142 không chuyển đổi thành một lệnh riêng biệt trong mã byte. Nó chỉ hướng dẫn trình biên dịch rằng
python -m compileall file_1.py ... file_n.py
101 nên được coi là một biến toàn cầu. Vì vậy,
python -m compileall file_1.py ... file_n.py
144 sẽ được sử dụng để thay đổi giá trị của nó. Hướng dẫn
python -m compileall file_1.py ... file_n.py
74

Đẩy một tham chiếu đến đối tượng được đề cập bởi

python -m compileall file_1.py ... file_n.py
097 lên ngăn xếp. Sau đó, nó được lưu trữ trong
python -m compileall file_1.py ... file_n.py
105 bằng cách sử dụng
python -m compileall file_1.py ... file_n.py
144. Hướng dẫn
python -m compileall file_1.py ... file_n.py
75

Đẩy một tham chiếu đến đối tượng có tham chiếu là

python -m compileall file_1.py ... file_n.py
148 lên ngăn xếp. Trong đối tượng mã của hàm
python -m compileall file_1.py ... file_n.py
000, thuộc tính
python -m compileall file_1.py ... file_n.py
92 chứa:
python -m compileall file_1.py ... file_n.py
76

Vì vậy,

python -m compileall file_1.py ... file_n.py
151 đẩy
python -m compileall file_1.py ... file_n.py
025 lên ngăn xếp. Sau đó
python -m compileall file_1.py ... file_n.py
036 được đẩy lên ngăn xếp.
python -m compileall file_1.py ... file_n.py
154 Pops
python -m compileall file_1.py ... file_n.py
025 và
python -m compileall file_1.py ... file_n.py
036, thêm chúng lại với nhau và đẩy kết quả lên ngăn xếp. Hướng dẫn
python -m compileall file_1.py ... file_n.py
77

Bật đỉnh của ngăn xếp và lưu trữ nó vào một đối tượng có tài liệu tham khảo được lưu trữ trong

python -m compileall file_1.py ... file_n.py
148. Vì vậy,
python -m compileall file_1.py ... file_n.py
158 bật kết quả và lưu trữ nó trong một đối tượng có tham chiếu là
python -m compileall file_1.py ... file_n.py
159.
python -m compileall file_1.py ... file_n.py
160 và
python -m compileall file_1.py ... file_n.py
161 được sử dụng với các biến cục bộ của các hàm. Vì vậy, chúng không được sử dụng ở phạm vi mô -đun. Mặt khác,
python -m compileall file_1.py ... file_n.py
162 và
python -m compileall file_1.py ... file_n.py
163 được sử dụng cho các biến toàn cầu được truy cập bên trong các chức năng. Cuối cùng,
python -m compileall file_1.py ... file_n.py
164 sẽ đẩy giá trị của
python -m compileall file_1.py ... file_n.py
159 lên trên ngăn xếp và
python -m compileall file_1.py ... file_n.py
71 sẽ trả nó cho người gọi của hàm là mô -đun.

Nhưng làm thế nào chức năng này được gọi? Nếu bạn nhìn vào mã byte của dòng 8, trước tiên,

python -m compileall file_1.py ... file_n.py
131
python -m compileall file_1.py ... file_n.py
108 sẽ đẩy đối tượng hàm có tham chiếu là
python -m compileall file_1.py ... file_n.py
000 lên ngăn xếp.
python -m compileall file_1.py ... file_n.py
170 đẩy đối số của nó (
python -m compileall file_1.py ... file_n.py
171) lên ngăn xếp. Hướng dẫn
python -m compileall file_1.py ... file_n.py
78

gọi một đối tượng có thể gọi với các đối số vị trí. OPARG của nó, ARGC chỉ ra số lượng đối số vị trí. Phần trên của ngăn xếp chứa các đối số vị trí, với đối số bên phải trên cùng. Bên dưới các đối số là đối tượng có thể gọi chức năng để gọi.

python -m compileall file_1.py ... file_n.py
77 Đầu tiên bật tất cả các đối số và đối tượng có thể gọi ra khỏi ngăn xếp. Sau đó, nó sẽ phân bổ một khung mới trên ngăn xếp cuộc gọi, điền vào các biến cục bộ cho cuộc gọi hàm và thực thi mã byte của hàm 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 trước, giá trị trả về của hàm sẽ được đẩy lên trên ngăn xếp đánh giá. Nếu không có khung trước đó, nó sẽ được đẩy lên trên đỉnh đánh giá của khung toàn cầu.

Trong ví dụ của chúng tôi, chúng tôi chỉ có một đối số vị trí, vì vậy hướng dẫn sẽ là

python -m compileall file_1.py ... file_n.py
173. Sau đó, hướng dẫn
python -m compileall file_1.py ... file_n.py
79

bật các mặt hàng trên đầu ngăn xếp. Đó là bởi vì chúng tôi không cần giá trị trả về của hàm nữa. Hình 2 cho thấy tất cả các hoạt động bytecode với độ lệch từ 16 đến 22. Các hướng dẫn byte bên trong

python -m compileall file_1.py ... file_n.py
141 được hiển thị màu đỏ.Hình 2

Chức năng tích hợp sẵn

Trong dòng 9 của mã byte được tháo rời của Danh sách 2, chúng tôi muốn

python -m compileall file_1.py ... file_n.py
175.
python -m compileall file_1.py ... file_n.py
176 cũng là một chức năng, nhưng nó là một hàm Python tích hợp. Tên của hàm là một tham chiếu đến đối tượng có thể gọi của nó. Vì vậy, đầu tiên nó được đẩy lên ngăn xếp và sau đó lập luận của nó được đẩy. Cuối cùng, nó sẽ được gọi bằng cách sử dụng
python -m compileall file_1.py ... file_n.py
77.
python -m compileall file_1.py ... file_n.py
176 sẽ trả về
python -m compileall file_1.py ... file_n.py
31 và giá trị được trả về sẽ được bật ra khỏi ngăn xếp sau đó.

Python sử dụng các chức năng tích hợp của nó để tạo cấu trúc dữ liệu. Ví dụ: dòng sau:

import file_name
00

sẽ được chuyển đổi thành:

import file_name
01

Ban đầu, mỗi yếu tố của danh sách được đẩy lên ngăn xếp. Sau đó, hướng dẫn

import file_name
02

được gọi để tạo danh sách bằng cách sử dụng các mục đếm từ ngăn xếp và đẩy đối tượng danh sách kết quả lên ngăn xếp. Cuối cùng, đối tượng trên ngăn xếp sẽ được bật và lưu trữ trên đống và

python -m compileall file_1.py ... file_n.py
101 sẽ là tài liệu tham khảo của nó.

EXTENDED_ARG

Như đã đề cập trước đây, một số hướng dẫn có thể có một đối số quá lớn để phù hợp với một byte mặc định và chúng sẽ được tiền tố bởi lệnh

python -m compileall file_1.py ... file_n.py
75. Đây là một ví dụ. Giả sử rằng chúng ta muốn in 260
python -m compileall file_1.py ... file_n.py
182 ký tự. Chúng tôi chỉ có thể viết
python -m compileall file_1.py ... file_n.py
183. Tuy nhiên, thay vào đó tôi sẽ viết một cái gì đó bất thường:
import file_name
03

Ở đây

python -m compileall file_1.py ... file_n.py
184 chứa hàm
python -m compileall file_1.py ... file_n.py
176 có 260 đối số và mỗi đối số là một ký tự
python -m compileall file_1.py ... file_n.py
182. Bây giờ hãy nhìn vào kết quả được tháo rời mã byte:
import file_name
04

Ở đây

python -m compileall file_1.py ... file_n.py
184 chứa hàm
python -m compileall file_1.py ... file_n.py
176 có 260 đối số và mỗi đối số là một ký tự
python -m compileall file_1.py ... file_n.py
182. Bây giờ hãy nhìn vào kết quả được tháo rời mã byte:
import file_name
04

python -m compileall file_1.py ... file_n.py
176 được đẩy lên ngăn xếp trước. Sau đó 260 đối số của nó được đẩy. Sau đó
python -m compileall file_1.py ... file_n.py
77 nên gọi chức năng. Nhưng nó cần số lượng đối số (của hàm đích) là oparg của nó. Ở đây con số này là 260 lớn hơn số tối đa mà một byte có thể lấy. Hãy nhớ rằng Oparg chỉ là một byte. Vì vậy,
python -m compileall file_1.py ... file_n.py
77 được có tiền tố bởi
python -m compileall file_1.py ... file_n.py
75. Mã byte thực tế là:
import file_name
05

Như đã đề cập trước khi oparg của extends_arg sẽ được dịch chuyển trái bởi tám bit hoặc đơn giản là nhân với 256 và sẽ được thêm vào oparg của opcode tiếp theo. Vì vậy, oparg của

python -m compileall file_1.py ... file_n.py
77 sẽ được hiểu là
python -m compileall file_1.py ... file_n.py
192 (xin lưu ý rằng hàm
python -m compileall file_1.py ... file_n.py
079 cho thấy điều này được giải thích OPARG không phải là oparg thực tế trong mã byte).

Tuyên bố có điều kiện và nhảy

python -m compileall file_1.py ... file_n.py
194:
import file_name
06

Xem xét mã nguồn sau đây có câu lệnh

import file_name
07

Mã byte được tháo rời là:

Chúng tôi có một vài hướng dẫn mới ở đây. Trong dòng 2, đối tượng mà

python -m compileall file_1.py ... file_n.py
101 đề cập đến được đẩy lên ngăn xếp, và sau đó nghĩa đen
python -m compileall file_1.py ... file_n.py
67 được đẩy. Hướng dẫn
import file_name
08

Thực hiện một hoạt động boolean. Tên hoạt động có thể được tìm thấy trong

python -m compileall file_1.py ... file_n.py
197. Các giá trị của
python -m compileall file_1.py ... file_n.py
198 được lưu trữ trong một danh sách có tên
python -m compileall file_1.py ... file_n.py
047. Hướng dẫn đầu tiên bật hai yếu tố hàng đầu của ngăn xếp. Chúng tôi gọi cái đầu tiên
python -m compileall file_1.py ... file_n.py
200 và cái thứ hai
python -m compileall file_1.py ... file_n.py
201. Sau đó, hoạt động Boolean được chọn bởi Oparg được thực hiện trên chúng
python -m compileall file_1.py ... file_n.py
202 và kết quả được đẩy lên trên đỉnh của ngăn xếp. Trong ví dụ này
python -m compileall file_1.py ... file_n.py
203 và
python -m compileall file_1.py ... file_n.py
204. Ngoài ra, OPARG là
python -m compileall file_1.py ... file_n.py
97 và
python -m compileall file_1.py ... file_n.py
206. Vì vậy,
python -m compileall file_1.py ... file_n.py
198 sẽ kiểm tra
python -m compileall file_1.py ... file_n.py
208 và lưu trữ kết quả (đúng hoặc sai) trên đầu ngăn xếp.

import file_name
09

Hướng dẫn

Thực hiện một bước nhảy có điều kiện. Đầu tiên, nó bật lên đỉnh của ngăn xếp. Nếu phần tử trên đầu ngăn xếp là sai, nó sẽ đặt bộ đếm bytecode thành mục tiêu. Bộ đếm bytecode hiển thị phần bù mã byte hiện tại đang được thực thi. Vì vậy, nó nhảy đến phần bù bytecode bằng với mục tiêu và việc thực thi mã byte tiếp tục từ đó. Phần bù 18 trong mã byte là một mục tiêu nhảy, do đó có một

python -m compileall file_1.py ... file_n.py
209 ở phía trước trong đó trong mã byte được tháo rời. Hướng dẫn
import file_name
60

tăng bộ đếm bytecode của delta. Trong mã byte trước đó, phần bù của hướng dẫn này là 16 và chúng tôi biết rằng mỗi lệnh có 2 byte. Vì vậy, khi hướng dẫn này kết thúc, bộ đếm bytecode là

python -m compileall file_1.py ... file_n.py
210. Ở đây
python -m compileall file_1.py ... file_n.py
211 và
python -m compileall file_1.py ... file_n.py
212, vì vậy nó nhảy đến phần bù
python -m compileall file_1.py ... file_n.py
213. Offset 24 là một mục tiêu nhảy và nó cũng có dấu
python -m compileall file_1.py ... file_n.py
209.

Bây giờ chúng ta có thể thấy cách câu lệnh jumpsto the offset 18 which is the start of
python -m compileall file_1.py ... file_n.py
219 block. If it is true, the
python -m compileall file_1.py ... file_n.py
220 block will be executed and then
python -m compileall file_1.py ... file_n.py
221 jumps to the offset 24 and does not execute the
python -m compileall file_1.py ... file_n.py
219 block.

python -m compileall file_1.py ... file_n.py
194 được chuyển đổi thành mã byte.
python -m compileall file_1.py ... file_n.py
198 kiểm tra nếu
python -m compileall file_1.py ... file_n.py
208. Nếu kết quả là sai,
python -m compileall file_1.py ... file_n.py
218JumpSto, offset 18 là khởi đầu của khối
python -m compileall file_1.py ... file_n.py
219. Nếu đó là sự thật, khối
python -m compileall file_1.py ... file_n.py
220 sẽ được thực thi và sau đó
python -m compileall file_1.py ... file_n.py
221 nhảy đến offset 24 và không thực thi khối
python -m compileall file_1.py ... file_n.py
219.jumpsto the offset 18 which is the start of
python -m compileall file_1.py ... file_n.py
219 block. If it is true, the
python -m compileall file_1.py ... file_n.py
220 block will be executed and then
python -m compileall file_1.py ... file_n.py
221 jumps to the offset 24 and does not execute the
python -m compileall file_1.py ... file_n.py
219 block.

import file_name
61

Ở đây chúng tôi có một

python -m compileall file_1.py ... file_n.py
223 logic. Mã byte được tháo rời là:
import file_name
62

Trong Python

python -m compileall file_1.py ... file_n.py
223 là một toán tử ngắn mạch. Vì vậy, khi đánh giá
python -m compileall file_1.py ... file_n.py
225, nó chỉ đánh giá
python -m compileall file_1.py ... file_n.py
226 nếu
python -m compileall file_1.py ... file_n.py
227 là đúng. Điều này có thể dễ dàng nhìn thấy trong mã byte. Trong dòng 3, đầu tiên, toán hạng bên trái của
python -m compileall file_1.py ... file_n.py
223 được đánh giá. Nếu
python -m compileall file_1.py ... file_n.py
229 là sai, nó không đánh giá toán hạng thứ hai và nhảy đến phần bù 30 để thực hiện
python -m compileall file_1.py ... file_n.py
230Block. Tuy nhiên, nếu đó là sự thật, toán hạng thứ hai
python -m compileall file_1.py ... file_n.py
231 cũng sẽ được đánh giá.

Vòng lặp và ngăn chặn

Như đã đề cập trước đây, có một ngăn xếp đánh giá bên trong mỗi khung hình. Ngoài ra, trong mỗi khung, có một ngăn xếp khối. Nó được sử dụng bởi CPython để theo dõi các loại cấu trúc điều khiển nhất định như các vòng, các khối

python -m compileall file_1.py ... file_n.py
232 và các khối
python -m compileall file_1.py ... file_n.py
233. Khi Cpython muốn nhập một trong các cấu trúc này, một mục mới được đẩy vào ngăn xếp khối và khi CPython thoát ra cấu trúc đó, mục cho cấu trúc đó sẽ bật ra khỏi ngăn xếp khối. Sử dụng ngăn xếp khối Cpython biết cấu trúc nào hiện đang hoạt động. Vì vậy, khi nó đạt đến một tuyên bố
python -m compileall file_1.py ... file_n.py
234 hoặc
python -m compileall file_1.py ... file_n.py
235, nó biết cấu trúc nào sẽ bị ảnh hưởng.

Hãy để xem cách các vòng lặp được thực hiện trong mã byte. Xem xét mã sau và mã byte được tháo rời của nó:

import file_name
63

Hướng dẫn

import file_name
64

được thực hiện trước khi vòng lặp bắt đầu. Lệnh này đẩy một mục mới (còn được gọi là một khối) vào ngăn xếp khối. Delta được thêm vào bộ đếm byte để xác định độ lệch của lệnh tiếp theo sau vòng lặp. Ở đây, phần bù của

python -m compileall file_1.py ... file_n.py
236 là
python -m compileall file_1.py ... file_n.py
67, do đó bộ đếm bytecode là
python -m compileall file_1.py ... file_n.py
238. Ngoài ra, Delta là
python -m compileall file_1.py ... file_n.py
213, do đó, phần bù của lệnh tiếp theo sau vòng lặp là
python -m compileall file_1.py ... file_n.py
240. Phần bù này được lưu trữ trong khối được đẩy vào ngăn xếp khối. Ngoài ra, số lượng các mục hiện tại trong ngăn xếp đánh giá được lưu trữ trong khối này.

Sau đó, hàm

python -m compileall file_1.py ... file_n.py
241 nên được thực thi. Oparg của nó (
python -m compileall file_1.py ... file_n.py
109) được đẩy trước tên của hàm. Kết quả là một điều đó. Iterables có thể tạo một trình lặp bằng cách sử dụng hướng dẫn:
import file_name
65

Nó có thể đi được trên đỉnh của ngăn xếp và đẩy một người lặp của điều đó. Hướng dẫn:

import file_name
66

Giả sử rằng có một người lặp trên đầu ngăn xếp. Nó gọi phương thức

python -m compileall file_1.py ... file_n.py
243 của nó. Nếu nó mang lại một giá trị mới, giá trị này được đẩy lên trên đầu của ngăn xếp (trên trình lặp). Bên trong vòng lặp, phần trên của ngăn xếp được lưu trữ trong
python -m compileall file_1.py ... file_n.py
244 sau đó và hàm
python -m compileall file_1.py ... file_n.py
176 được thực thi. Sau đó, phần trên của ngăn xếp là giá trị hiện tại của iterator được bật ra. Sau đó, hướng dẫn
import file_name
67

Đặt bộ đếm bytecode thành mục tiêu và nhảy vào phần bù đích. Vì vậy, nó nhảy để bù 10 và chạy lại

python -m compileall file_1.py ... file_n.py
246 để lấy giá trị tiếp theo của trình lặp. Nếu trình lặp chỉ ra rằng không có phần tử nào có sẵn, thì đỉnh của ngăn xếp được bật ra và bộ đếm mã byte được tăng lên bởi Delta. Ở đây
python -m compileall file_1.py ... file_n.py
247, vì vậy sau khi hoàn thành vòng lặp, nó nhảy lên để bù 24. Khi bù 24, hướng dẫn
import file_name
68

Loại bỏ khối hiện tại khỏi đỉnh của ngăn xếp khối. Phần bù của lệnh tiếp theo sau khi vòng được lưu trữ trong khối (ở đây là 26). Vì vậy, thông dịch viên sẽ nhảy đến phần bù đó và tiếp tục thực hiện từ đó. Hình 3 cho thấy các hoạt động của mã byte với độ lệch 0, 10, 24 và 26 làm ví dụ (trên thực tế trong Hình 1 và 2, chúng tôi chỉ hiển thị ngăn xếp đánh giá trong mỗi khung).

Hình 3

Nhưng điều gì xảy ra nếu chúng ta thêm một tuyên bố

python -m compileall file_1.py ... file_n.py
234 vào vòng lặp này? Xem xét mã nguồn sau và mã byte được tháo rời của nó:
import file_name
69

Chúng tôi chỉ thêm một câu lệnh

python -m compileall file_1.py ... file_n.py
234 vào vòng lặp trước. Tuyên bố này được chuyển đổi thành
import file_name
80

Opcode này loại bỏ các mục bổ sung đó trên ngăn xếp đánh giá và bật khối từ đỉnh của ngăn xếp khối. Bạn nên nhận thấy rằng các hướng dẫn khác của vòng lặp vẫn đang sử dụng ngăn xếp đánh giá. Vì vậy, khi vòng lặp bị vỡ, các mục thuộc về nó nên được bật ra khỏi ngăn xếp đánh giá. Trong ví dụ này, đối tượng Iterator vẫn đứng trên đỉnh của ngăn xếp. Hãy nhớ rằng khối trong ngăn xếp khối lưu trữ số lượng các mục tồn tại trong ngăn xếp đánh giá trước khi bắt đầu vòng lặp.

Vì vậy, bằng cách biết số đó,

python -m compileall file_1.py ... file_n.py
250pops những mục bổ sung đó khỏi ngăn xếp đánh giá. Sau đó, nó nhảy đến phần bù được lưu trữ trong khối hiện tại của ngăn xếp khối (ở đây là 28). Đó là phần bù của hướng dẫn tiếp theo sau vòng lặp. Vì vậy, vòng lặp phá vỡ và thực hiện được tiếp tục từ đó.pops those extra items off the evaluation stack. Then it jumps to the offset which is stored in the current block of the block stack (here it is 28). That is the offset of the next instruction after the loop. So the loop breaks and the execution is continued from there.pops those extra items off the evaluation stack. Then it jumps to the offset which is stored in the current block of the block stack (here it is 28). That is the offset of the next instruction after the loop. So the loop breaks and the execution is continued from there.

Tạo đối tượng mã

Đối tượng mã là một đối tượng của loại

python -m compileall file_1.py ... file_n.py
46 và có thể tạo nó một cách linh hoạt. Mô -đun
python -m compileall file_1.py ... file_n.py
252 có thể giúp tạo động các loại mới và lớp
python -m compileall file_1.py ... file_n.py
253 trong mô -đun này trả về một đối tượng mã mới:
import file_name
81

Các đối số tạo thành tất cả các thuộc tính của đối tượng mã. Bạn đã quen thuộc với một số đối số này (như

python -m compileall file_1.py ... file_n.py
92 và
python -m compileall file_1.py ... file_n.py
054).
python -m compileall file_1.py ... file_n.py
256 và
python -m compileall file_1.py ... file_n.py
257 là tùy chọn vì chúng được sử dụng trong các đóng cửa và không phải tất cả các chức năng đều sử dụng chúng (tham khảo bài viết này để biết thêm thông tin về chúng). Các thuộc tính khác được giải thích bằng cách sử dụng hàm sau làm ví dụ:
import file_name
82
python -m compileall file_1.py ... file_n.py
258: Nếu đối tượng mã thuộc hàm, số lượng đối số cần thiết (không bao gồm các đối số từ khóa,
python -m compileall file_1.py ... file_n.py
182 hoặc
python -m compileall file_1.py ... file_n.py
260 Args). Đối với chức năng
python -m compileall file_1.py ... file_n.py
000 đó là
python -m compileall file_1.py ... file_n.py
108.
python -m compileall file_1.py ... file_n.py
263: Nếu đối tượng mã thuộc hàm, số lượng từ khóa chỉ đối số (không bao gồm
python -m compileall file_1.py ... file_n.py
260 arg). Đối với chức năng
python -m compileall file_1.py ... file_n.py
000 đó là
python -m compileall file_1.py ... file_n.py
036.
python -m compileall file_1.py ... file_n.py
267: Số lượng biến cục bộ cộng với tên của các hàm được xác định trong đối tượng mã (đối số cũng được coi là biến cục bộ). Trên thực tế, đó là số lượng các yếu tố trong
python -m compileall file_1.py ... file_n.py
92 là
python -m compileall file_1.py ... file_n.py
269. Vì vậy, nó là
python -m compileall file_1.py ... file_n.py
270 cho
python -m compileall file_1.py ... file_n.py
000.
python -m compileall file_1.py ... file_n.py
272: Hiển thị số lượng phần tử lớn nhất sẽ được đẩy vào ngăn xếp đánh giá bởi đối tượng mã này. Hãy nhớ rằng một số opcode cần phải đẩy một số yếu tố vào ngăn xếp đánh giá. Thuộc tính này cho thấy kích thước lớn nhất mà ngăn xếp sẽ phát triển từ các hoạt động byte. Trong ví dụ này là
python -m compileall file_1.py ... file_n.py
108. Hãy để tôi giải thích lý do cho điều đó. Nếu bạn tháo rời mã byte của chức năng này bạn nhận được:
import file_name
8 3

Trong dòng 2, một phần tử được đẩy lên ngăn xếp bằng cách sử dụng

python -m compileall file_1.py ... file_n.py
65 và sẽ được bật bằng cách sử dụng
python -m compileall file_1.py ... file_n.py
161. Các dòng 5 và 6 tương tự đẩy một phần tử lên ngăn xếp và bật nó sau. Nhưng trong dòng 3, hai phần tử được đẩy lên ngăn xếp để xác định hàm bên trong
python -m compileall file_1.py ... file_n.py
023: đối tượng mã của nó và tên của nó. Vì vậy, đây là số lượng tối đa các yếu tố sẽ được đẩy vào ngăn xếp đánh giá bởi đối tượng mã này và nó xác định kích thước ngăn xếp.
python -m compileall file_1.py ... file_n.py
277: Một số nguyên, với các bit chỉ ra những thứ như liệu hàm có chấp nhận số lượng đối số thay đổi hay không, liệu hàm này có phải là trình tạo hay không, v.v. Trong ví dụ của chúng tôi, giá trị của nó là
python -m compileall file_1.py ... file_n.py
278. Giá trị nhị phân của
python -m compileall file_1.py ... file_n.py
278 là
python -m compileall file_1.py ... file_n.py
280. Nó sử dụng một hệ thống ít endian trong đó các byte được viết từ trái bên phải trong việc tăng ý nghĩa. Vì vậy, bit đầu tiên là lần đầu tiên ở bên phải. Bạn có thể tham khảo liên kết này về ý nghĩa của các bit này. Ví dụ: bit thứ ba từ bên phải đại diện cho cờ
python -m compileall file_1.py ... file_n.py
281. Khi nó là
python -m compileall file_1.py ... file_n.py
036, điều đó có nghĩa là đối tượng mã có tham số vị trí thay đổi (________ 483 giống như).
python -m compileall file_1.py ... file_n.py
284: Một chuỗi, chỉ định tệp trong đó chức năng có mặt. Trong trường hợp này, đó là
python -m compileall file_1.py ... file_n.py
285 vì tôi đang chạy tập lệnh trong Jupyter Notebook.
python -m compileall file_1.py ... file_n.py
286: Một tên mà đối tượng mã này được xác định. Đây là tên của hàm
python -m compileall file_1.py ... file_n.py
99.

Tiêm mã byte

Bây giờ chúng tôi hoàn toàn quen thuộc với đối tượng mã, chúng tôi có thể bắt đầu thay đổi mã byte của nó. Điều quan trọng cần lưu ý là đối tượng mã là bất biến. Vì vậy, một khi được tạo ra, chúng ta không thể thay đổi nó. Giả sử rằng chúng ta muốn thay đổi mã byte của hàm sau:

import file_name
84

Ở đây chúng ta không thể thay đổi mã byte của đối tượng mã trực tiếp của hàm. Thay vào đó, chúng ta cần tạo một đối tượng mã mới và sau đó gán nó cho chức năng này. Để làm điều đó, chúng tôi cần thêm một vài chức năng. Hàm

python -m compileall file_1.py ... file_n.py
079 có thể tháo rời mã byte thành một số hướng dẫn thân thiện với con người. Chúng ta có thể thay đổi chúng như chúng ta muốn, nhưng sau đó chúng ta cần lắp ráp nó trở lại mã byte để gán nó cho một đối tượng mã mới. Đầu ra của
python -m compileall file_1.py ... file_n.py
079 là một chuỗi được định dạng dễ đọc, nhưng khó thay đổi. Vì vậy, tôi sẽ thêm một chức năng mới có thể tháo rời mã byte vào danh sách các hướng dẫn. Nó rất giống với
python -m compileall file_1.py ... file_n.py
079, tuy nhiên, đầu ra của nó là một danh sách.

Chúng ta có thể thử nó trên chức năng trước đó:

import file_name
85

Bây giờ

python -m compileall file_1.py ... file_n.py
291 bằng với:
import file_name
86

Bây giờ chúng ta có thể thay đổi hướng dẫn của danh sách này một cách dễ dàng. Nhưng chúng ta cũng cần lắp ráp nó trở lại mã byte:

Hàm

python -m compileall file_1.py ... file_n.py
292 giống như nghịch đảo của
python -m compileall file_1.py ... file_n.py
076. Nó có một argvalue là ý nghĩa thân thiện với con người của một oparg và trả về oparg tương ứng. Nó cần đối tượng mã làm đối số của nó vì các thuộc tính của đối tượng mã như
python -m compileall file_1.py ... file_n.py
90 là cần thiết để chuyển đổi argvalue thành oparg.

Hàm

python -m compileall file_1.py ... file_n.py
295 lấy một đối tượng mã và danh sách mã byte được tháo rời và lắp ráp lại vào mã byte. Nó sử dụng
python -m compileall file_1.py ... file_n.py
296 để chuyển đổi opname thành opcode. Sau đó, nó gọi
python -m compileall file_1.py ... file_n.py
297 để chuyển đổi argvalue thành oparg. Cuối cùng, nó trả về một byte theo nghĩa đen của danh sách mã byte. Bây giờ chúng ta có thể sử dụng các chức năng mới này để thay đổi mã byte của hàm trước đó
python -m compileall file_1.py ... file_n.py
000. Đầu tiên, chúng tôi thay đổi một trong các hướng dẫn trong
python -m compileall file_1.py ... file_n.py
291:
import file_name
87

Hướng dẫn

import file_name
88

Bật hai yếu tố hàng đầu của ngăn xếp, nhân chúng lại với nhau và đẩy kết quả lên ngăn xếp. Bây giờ chúng tôi lắp ráp mã byte được tháo rời đã sửa đổi:

import file_name
89

Sau đó, chúng tôi tạo một đối tượng mã mới:

python -m compileall file_1.py ... file_n.py
00

Chúng tôi sử dụng tất cả các thuộc tính của

python -m compileall file_1.py ... file_n.py
000 để tạo nó và chỉ thay thế mã byte mới (
python -m compileall file_1.py ... file_n.py
301). Sau đó, chúng tôi gán đối tượng mã mới cho
python -m compileall file_1.py ... file_n.py
000. Bây giờ nếu chúng ta chạy lại
python -m compileall file_1.py ... file_n.py
000, nó sẽ không thêm các đối số của nó với nhau. Thay vào đó, nó sẽ nhân chúng lại với nhau:
python -m compileall file_1.py ... file_n.py
01

THẬN TRỌNG: Hàm

python -m compileall file_1.py ... file_n.py
6704 có hai đối số tùy chọn cho
python -m compileall file_1.py ... file_n.py
256 và ____50506, tuy nhiên, bạn nên cẩn thận khi sử dụng chúng. Như đã đề cập trước các thuộc tính
python -m compileall file_1.py ... file_n.py
93 và
python -m compileall file_1.py ... file_n.py
94 của đối tượng mã chỉ được sử dụng khi đối tượng mã thuộc về một hàm có các biến miễn phí hoặc các biến không thuộc địa. Vì vậy, chức năng nên là một đóng cửa hoặc đóng cửa nên được xác định bên trong nó. Ví dụ, hãy xem xét chức năng sau:: The 04 có hai đối số tùy chọn cho
python -m compileall file_1.py ... file_n.py
256 và ____50506, tuy nhiên, bạn nên cẩn thận khi sử dụng chúng. Như đã đề cập trước các thuộc tính
python -m compileall file_1.py ... file_n.py
93 và
python -m compileall file_1.py ... file_n.py
94 của đối tượng mã chỉ được sử dụng khi đối tượng mã thuộc về một hàm có các biến miễn phí hoặc các biến không thuộc địa. Vì vậy, chức năng nên là một đóng cửa hoặc đóng cửa nên được xác định bên trong nó. Ví dụ, hãy xem xét chức năng sau:
: The

python -m compileall file_1.py ... file_n.py
304 function has two optional arguments for
python -m compileall file_1.py ... file_n.py
256 and
python -m compileall file_1.py ... file_n.py
306, however, you should be careful when using them. As mentioned before the
python -m compileall file_1.py ... file_n.py
93 and
python -m compileall file_1.py ... file_n.py
94 attributes of the code object are only used when the code object belongs to a function which has free variables or nonlocal variables. So the function should be a closure or a closure should have been defined inside it. For example, consider the following function:
python -m compileall file_1.py ... file_n.py
02

Bây giờ nếu kiểm tra đối tượng mã của nó:

python -m compileall file_1.py ... file_n.py
03

Trên thực tế, chức năng này có một biến phi tiêu điểm

python -m compileall file_1.py ... file_n.py
025 vì biến này được truy cập bởi các hàm bên trong của nó. Bây giờ chúng ta có thể thử tái tạo lại đối tượng mã của nó bằng cách sử dụng các thuộc tính tương tự:
python -m compileall file_1.py ... file_n.py
04

Nhưng nếu chúng ta kiểm tra cùng một thuộc tính của đối tượng mã mới

python -m compileall file_1.py ... file_n.py
05

Hóa ra là trống rỗng. Vì vậy,

python -m compileall file_1.py ... file_n.py
304 không thể tạo cùng một đối tượng mã. Nếu bạn cố gắng gán đối tượng mã này cho một hàm và thực thi hàm đó, bạn sẽ gặp lỗi (điều này đã được kiểm tra trên Python 3.7.4).

Tối ưu hóa mã

Hiểu các hướng dẫn bytecode có thể giúp chúng tôi tối ưu hóa mã nguồn. Xem xét mã nguồn sau:

python -m compileall file_1.py ... file_n.py
06

Ở đây chúng tôi xác định một hàm

python -m compileall file_1.py ... file_n.py
311 để tính toán biểu thức toán học đơn giản. Nó đã được định nghĩa theo hai cách khác nhau. Trong
python -m compileall file_1.py ... file_n.py
312, chúng tôi đang sử dụng biến toàn cầu
python -m compileall file_1.py ... file_n.py
313 bên trong
python -m compileall file_1.py ... file_n.py
311 và trực tiếp sử dụng hàm
python -m compileall file_1.py ... file_n.py
315 từ mô -đun
python -m compileall file_1.py ... file_n.py
316. Trong
python -m compileall file_1.py ... file_n.py
317,
python -m compileall file_1.py ... file_n.py
313 là một biến cục bộ là
python -m compileall file_1.py ... file_n.py
311. Ngoài ra,
python -m compileall file_1.py ... file_n.py
320 lần đầu tiên được lưu trữ trong biến cục bộ
python -m compileall file_1.py ... file_n.py
321. Bây giờ chúng ta có thể so sánh hiệu suất của các chức năng sau:
python -m compileall file_1.py ... file_n.py
07

Bạn có thể nhận được các số khác nhau cho

python -m compileall file_1.py ... file_n.py
322 và
python -m compileall file_1.py ... file_n.py
323, nhưng điểm mấu chốt là
python -m compileall file_1.py ... file_n.py
317 nhanh hơn
python -m compileall file_1.py ... file_n.py
312. Bây giờ, hãy để so sánh mã byte của họ để xem tại sao nó nhanh hơn. Chúng tôi chỉ nhìn vào dòng 7 trong mã được tháo rời của
python -m compileall file_1.py ... file_n.py
312 và
python -m compileall file_1.py ... file_n.py
317. Đây là mã byte cho dòng này:
python -m compileall file_1.py ... file_n.py
328.

Trong

python -m compileall file_1.py ... file_n.py
312 chúng ta có:
python -m compileall file_1.py ... file_n.py
08

Nhưng trong

python -m compileall file_1.py ... file_n.py
317 chúng tôi nhận được:
python -m compileall file_1.py ... file_n.py
09

Như bạn thấy trong

python -m compileall file_1.py ... file_n.py
312 cả
python -m compileall file_1.py ... file_n.py
313 và
python -m compileall file_1.py ... file_n.py
316 đều được tải bằng
python -m compileall file_1.py ... file_n.py
334, nhưng trong
python -m compileall file_1.py ... file_n.py
317,
python -m compileall file_1.py ... file_n.py
313 và
python -m compileall file_1.py ... file_n.py
321 được tải bằng
python -m compileall file_1.py ... file_n.py
160. Vì vậy, hai hướng dẫn
python -m compileall file_1.py ... file_n.py
162 đã được thay thế bằng
python -m compileall file_1.py ... file_n.py
160. Thực tế là
python -m compileall file_1.py ... file_n.py
160 như tên của nó cho thấy nhanh hơn nhiều so với
python -m compileall file_1.py ... file_n.py
162. Chúng tôi đã đề cập rằng tên của các biến toàn cầu và địa phương được lưu trữ trong
python -m compileall file_1.py ... file_n.py
91 và
python -m compileall file_1.py ... file_n.py
92. Nhưng làm thế nào để trình thông dịch CPython tìm thấy các giá trị khi thực thi mã được biên dịch?

Các biến cục bộ được lưu trữ trong một mảng trên mỗi khung (không được hiển thị trong các hình trước để làm cho chúng đơn giản hơn). Chúng tôi biết rằng tên của các biến cục bộ được lưu trữ trong

python -m compileall file_1.py ... file_n.py
92. Giá trị của chúng sẽ được lưu trữ với cùng một thứ tự trong mảng này. Vì vậy, khi trình thông dịch thấy một hướng dẫn như
python -m compileall file_1.py ... file_n.py
346, nó sẽ đọc phần tử của mảng đó tại INDEX
python -m compileall file_1.py ... file_n.py
036.

Toàn cầu và tích hợp của mô -đun được lưu trữ trong một từ điển. Chúng tôi biết rằng tên của họ được lưu trữ trong

python -m compileall file_1.py ... file_n.py
91. Vì vậy, khi trình thông dịch thấy một hướng dẫn như
python -m compileall file_1.py ... file_n.py
349, trước tiên nó có tên của biến toàn cầu đó từ
python -m compileall file_1.py ... file_n.py
350. Sau đó, nó sẽ tìm kiếm tên này trong từ điển để có được giá trị của nó. Đây là một quá trình chậm hơn nhiều so với việc tìm kiếm mảng đơn giản cho các biến cục bộ. Do đó,
python -m compileall file_1.py ... file_n.py
160 nhanh hơn
python -m compileall file_1.py ... file_n.py
162 và thay thế
python -m compileall file_1.py ... file_n.py
162 bằng
python -m compileall file_1.py ... file_n.py
160 có thể cải thiện hiệu suất. Nó có thể được thực hiện bằng cách lưu trữ các biến tích hợp và toàn cầu thành các biến cục bộ hoặc thay đổi trực tiếp các hướng dẫn mã byte.

Ví dụ: Xác định hằng số trong Python

Ví dụ này minh họa cách sử dụng tiêm mã byte để thay đổi hành vi của các chức năng. Chúng tôi sẽ viết một người trang trí thêm một tuyên bố const cho Python. Trong một số ngôn ngữ lập trình như C, C ++ và JavaScript có một từ khóa Const. Nếu một biến được khai báo là const sử dụng từ khóa này, thì việc thay đổi giá trị của nó là bất hợp pháp và chúng ta không thể thay đổi giá trị của biến này trong mã nguồn nữa.

Python không có tuyên bố const và tôi không cho rằng thực sự cần phải có một từ khóa như vậy trong Python. Ngoài ra, việc xác định hằng số cũng có thể được thực hiện mà không cần sử dụng tiêm mã byte. Vì vậy, đây chỉ là một ví dụ để chỉ cho bạn cách đưa tiêm mã byte vào hành động. Đầu tiên, hãy để tôi chỉ ra cách bạn có thể sử dụng nó. Từ khóa Const được cung cấp bằng cách sử dụng một trình trang trí chức năng có tên

python -m compileall file_1.py ... file_n.py
355. Khi bạn trang trí một hàm bằng
python -m compileall file_1.py ... file_n.py
355, bạn có thể khai báo biến bên trong nó dưới dạng hằng số bằng cách sử dụng từ khóa
python -m compileall file_1.py ... file_n.py
357 (
python -m compileall file_1.py ... file_n.py
358 ở cuối là một phần của từ khóa). Đây là một ví dụ:
python -m compileall file_1.py ... file_n.py
10

Biến

python -m compileall file_1.py ... file_n.py
359 bên trong
python -m compileall file_1.py ... file_n.py
000 hiện là một hằng số. Bây giờ nếu bạn cố gắng gán lại biến này bên trong
python -m compileall file_1.py ... file_n.py
000, một ngoại lệ sẽ được nêu ra:
python -m compileall file_1.py ... file_n.py
11

Khi một biến được khai báo là Const., Nó nên được gán cho giá trị ban đầu của nó và nó sẽ là một biến cục bộ của hàm đó.

Bây giờ hãy để tôi chỉ cho bạn cách nó đã được thực hiện. Giả sử rằng tôi xác định một chức năng như thế này (không trang trí):

python -m compileall file_1.py ... file_n.py
12

Nó sẽ được biên dịch đúng. Nhưng nếu bạn thử thực thi chức năng này, bạn sẽ gặp lỗi:

python -m compileall file_1.py ... file_n.py
13

Bây giờ chúng ta hãy xem mã byte được tháo rời của chức năng này:

python -m compileall file_1.py ... file_n.py
14

Khi Python cố gắng biên dịch hàm, phải mất

python -m compileall file_1.py ... file_n.py
355 như một biến toàn cầu vì nó chưa được xác định trong hàm. Biến
python -m compileall file_1.py ... file_n.py
359 được coi là một thuộc tính của biến toàn cầu
python -m compileall file_1.py ... file_n.py
359. Trên thực tế,
python -m compileall file_1.py ... file_n.py
365 giống như
python -m compileall file_1.py ... file_n.py
366 kể từ khi Python bỏ qua khoảng trắng giữa toán tử DOT và tên của thuộc tính. Tất nhiên, chúng tôi thực sự không có biến toàn cầu có tên
python -m compileall file_1.py ... file_n.py
359 trong mã nguồn. Nhưng Python sẽ không kiểm tra nó tại thời điểm biên dịch. Chỉ trong quá trình thực hiện, nó sẽ chỉ ra rằng tên
python -m compileall file_1.py ... file_n.py
355 không được xác định. Vì vậy, mã nguồn của chúng tôi sẽ được chấp nhận trong quá trình biên dịch. Nhưng chúng ta cần thay đổi mã byte của nó trước khi thực thi đối tượng mã của hàm này. Trước tiên chúng ta cần tạo một hàm để thay đổi mã byte:

Hàm này nhận được danh sách các hướng dẫn bytecode được tạo bởi

python -m compileall file_1.py ... file_n.py
369 làm đối số của nó. Nó có hai danh sách có tên
python -m compileall file_1.py ... file_n.py
370 và
python -m compileall file_1.py ... file_n.py
371 lưu trữ tên của các biến được khai báo là Const và lần đầu tiên chúng được chỉ định. Vòng lặp đầu tiên tìm kiếm danh sách các hướng dẫn bytecode và tìm thấy tất cả các hướng dẫn
python -m compileall file_1.py ... file_n.py
372. Tên của biến phải nằm trong hướng dẫn tiếp theo. Trong ví dụ này, lệnh tiếp theo là
python -m compileall file_1.py ... file_n.py
373 và tên là
python -m compileall file_1.py ... file_n.py
359. Tên này và phần bù của hướng dẫn này được lưu trữ trong
python -m compileall file_1.py ... file_n.py
370 và
python -m compileall file_1.py ... file_n.py
371. Bây giờ chúng ta cần loại bỏ biến toàn cầu
python -m compileall file_1.py ... file_n.py
355 và thuộc tính của nó và tạo ra một biến cục bộ có tên
python -m compileall file_1.py ... file_n.py
359 thay thế. Hướng dẫn
python -m compileall file_1.py ... file_n.py
15

là một mã không có gì. Khi thông dịch viên đạt đến

python -m compileall file_1.py ... file_n.py
379, nó sẽ bỏ qua nó. Chúng tôi không thể xóa Opcode khỏi danh sách các hướng dẫn vì việc xóa một lệnh làm giảm độ lệch của tất cả các hướng dẫn sau. Bây giờ nếu có một số bước nhảy trong mã byte, phần bù mục tiêu của họ cũng sẽ thay đổi. Vì vậy, việc thay thế hướng dẫn không mong muốn bằng
python -m compileall file_1.py ... file_n.py
379 sẽ dễ dàng hơn nhiều. Bây giờ chúng tôi thay thế
python -m compileall file_1.py ... file_n.py
372 bằng
python -m compileall file_1.py ... file_n.py
379 và sau đó thay thế
python -m compileall file_1.py ... file_n.py
373 bằng
python -m compileall file_1.py ... file_n.py
384. Mã byte cuối cùng trông như thế này:
python -m compileall file_1.py ... file_n.py
16

Bây giờ dòng 2 tương đương với

python -m compileall file_1.py ... file_n.py
385 trong mã nguồn và việc thực thi mã byte này không gây ra bất kỳ lỗi thời gian chạy nào. Vòng lặp cũng kiểm tra xem cùng một biến không được khai báo là const hai lần. Vì vậy, nếu biến được khai báo là const đã tồn tại trong danh sách
python -m compileall file_1.py ... file_n.py
370, nó sẽ tăng một ngoại lệ tùy chỉnh. Bây giờ điều duy nhất còn lại là đảm bảo rằng các biến const chưa được chỉ định lại.

Vòng lặp thứ hai tìm kiếm danh sách các hướng dẫn bytecode một lần nữa để tìm bất kỳ sự phân công lại của các biến không đổi. Bất kỳ hướng dẫn nào như

python -m compileall file_1.py ... file_n.py
387 hoặc
python -m compileall file_1.py ... file_n.py
384 đều có nghĩa là việc tái chỉ định trong mã nguồn, do đó, nó sẽ tăng một ngoại lệ tùy chỉnh để cảnh báo người dùng. Sự bù đắp của nhiệm vụ ban đầu của một const là bắt buộc để đảm bảo rằng nhiệm vụ ban đầu không được coi là một sự phân công lại.

Như đã đề cập trước đó, mã byte nên được thay đổi trước khi thực thi mã. Vì vậy, hàm

python -m compileall file_1.py ... file_n.py
389 cần được gọi trước khi gọi hàm
python -m compileall file_1.py ... file_n.py
000. Vì lý do này, chúng tôi đặt nó bên trong một người trang trí. Hàm trang trí
python -m compileall file_1.py ... file_n.py
355 nhận chức năng đích
python -m compileall file_1.py ... file_n.py
000 làm đối số của nó. Trước tiên, nó sẽ thay đổi mã byte của
python -m compileall file_1.py ... file_n.py
000 bằng cách sử dụng
python -m compileall file_1.py ... file_n.py
389 và sau đó tạo một đối tượng mã mới với mã byte được sửa đổi. Đối tượng mã này sẽ được gán cho
python -m compileall file_1.py ... file_n.py
000.

Khi chúng ta tạo đối tượng mã mới, một số thuộc tính của nó cần phải được sửa đổi. Trong hàm ban đầu

python -m compileall file_1.py ... file_n.py
355 là một biến toàn cầu và
python -m compileall file_1.py ... file_n.py
359 là một thuộc tính, do đó, cả hai đều được thêm vào tuple
python -m compileall file_1.py ... file_n.py
91 và chúng nên được xóa khỏi
python -m compileall file_1.py ... file_n.py
91 của đối tượng mã mới. Ngoài ra, khi một thuộc tính như
python -m compileall file_1.py ... file_n.py
359 được biến thành một biến cục bộ, tên của nó phải được thêm vào tuple
python -m compileall file_1.py ... file_n.py
92. Thuộc tính
python -m compileall file_1.py ... file_n.py
267 cung cấp số lượng biến cục bộ (cộng với các hàm được xác định) và cũng nên được cập nhật. Các thuộc tính khác vẫn giữ nguyên. Trình trang trí cuối cùng trả về chức năng đích với đối tượng mã mới và bây giờ chức năng đích đã sẵn sàng để thực thi.

Hiểu mã byte của Python, cho phép bạn làm quen với việc triển khai cấp thấp của trình biên dịch Python và máy ảo. Nếu bạn biết cách chuyển đổi mã nguồn của bạn thành mã byte, bạn có thể đưa ra quyết định tốt hơn về việc viết và tối ưu hóa mã của bạn. Tiêm Bytecode cũng là một công cụ hữu ích để tối ưu hóa mã và siêu hình. Tôi chỉ đề cập đến một số lượng nhỏ các hướng dẫn mã byte trong bài viết này. Bạn có thể tham khảo trang web mô -đun

python -m compileall file_1.py ... file_n.py
57 để xem danh sách đầy đủ các hướng dẫn mã byte Python. Tôi hy vọng rằng bạn đã thích đọc bài viết này. Tất cả các danh sách mã của bài viết này đều có sẵn để tải xuống dưới dạng Jupyter Notebook tại: https://github.com/reza-bagheri/undering-python-bytecode

Làm thế nào để bạn viết mã byte trong Python?

Mã byte được tự động tạo trong cùng một thư mục với tệp .py, khi một mô -đun Python lần đầu tiên được nhập hoặc khi nguồn gần đây hơn tệp được biên dịch hiện tại. Lần tới, khi chương trình được chạy, người thông báo Python sử dụng tệp này để bỏ qua bước biên dịch.py file, when a module of python is imported for the first time, or when the source is more recent than the current compiled file. Next time, when the program is run, python interpretator use this file to skip the compilation step. py file, when a module of python is imported for the first time, or when the source is more recent than the current compiled file. Next time, when the program is run, python interpretator use this file to skip the compilation step.

Làm thế nào để Python thực thi mã byte?

Việc triển khai mặc định của ngôn ngữ lập trình Python là CPython được viết bằng ngôn ngữ lập trình C. Cpython biên dịch mã nguồn Python vào mã byte và mã byte này sau đó được thực thi bởi máy ảo CPython. Tất cả các tệp PYC được tạo sẽ được lưu trữ trong thư mục __pycache__.by the CPython virtual machine. All the generated pyc files will be stored in the __pycache__ folder.by the CPython virtual machine. All the generated pyc files will be stored in the __pycache__ folder.

Tại sao Bytecode được sử dụng trong Python?

Bytecode là ngôn ngữ trung gian cho máy ảo Python được sử dụng làm tối ưu hóa hiệu suất.as a performance optimization.as a performance optimization.

Làm thế nào bytecode được thực thi?

Chấp hành.Một chương trình ByteCode có thể được thực thi bằng cách phân tích cú pháp và thực hiện trực tiếp các hướng dẫn, mỗi lần một.Loại thông dịch viên byte này rất di động.Một số hệ thống, được gọi là Trình dịch động hoặc trình biên dịch chỉ trong thời gian (JIT), dịch mã byte thành mã máy khi cần thiết trong thời gian chạy.by parsing and directly executing the instructions, one at a time. This kind of bytecode interpreter is very portable. Some systems, called dynamic translators, or just-in-time (JIT) compilers, translate bytecode into machine code as necessary at runtime.by parsing and directly executing the instructions, one at a time. This kind of bytecode interpreter is very portable. Some systems, called dynamic translators, or just-in-time (JIT) compilers, translate bytecode into machine code as necessary at runtime.