Nội dung chính ShowShow Show
Tìm hiểu về mã Python byteBytecode 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ộngpython -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ệppython -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 0Chúng ta cũng có thể sử dụng hàm python -m compileall file_1.py ... file_n.py 1python -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 26python -m compileall file_1.py ... file_n.py 2Chú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 3python -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 31python -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
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 0Vì 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 0Khi 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 1Và đầu ra sẽ là: python -m compileall file_1.py ... file_n.py 2Giố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 4Kế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 5gives: python -m compileall file_1.py ... file_n.py 6Vì 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 7gives: python -m compileall file_1.py ... file_n.py 8Vì 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 9Chú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 01Hai 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 02và 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 03Hai 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 04Ngoà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 05Tuy 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 06Khi 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 07Vì 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 09và 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 12Bâ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 13Vì 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 14python -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 15Kế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 163 -________ 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 17Bâ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 18Tạ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 195 -________ 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 19Bâ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 21sau đó 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 22Tươ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 913 -________ 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 924 -________ 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 0455 -________ 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 0496 -________ 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 23Bâ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 24Mỗ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 26python -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 28It 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 27The 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 modulepython -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:
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 30Và Chún tôm python -m compileall file_1.py ... file_n.py 31Ngoà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 34Bậ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 ________ 235Bậ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 117Trả 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 38Ngoà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 72Sau đó, mã byte được tháo rời cho dòng 2 sẽ là: python -m compileall file_1.py ... file_n.py 73Mộ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ẫnpython -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ẫnpython -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 76Vì 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ẫnpython -m compileall file_1.py ... file_n.py 77Bậ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ẫnpython -m compileall file_1.py ... file_n.py 78gọ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ẫnpython -m compileall file_1.py ... file_n.py 79bậ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 2Chứ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 00sẽ được chuyển đổi thành: import file_name 01Ban đầ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 04python -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 05Như đã đề 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
Tuyên bố có điều kiện và nhảy python -m compileall file_1.py ... file_n.py 194:import file_name 06Xem xét mã nguồn sau đây có câu lệnh import file_name 07Mã 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ẫnimport file_name 08Thự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 09Hướ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ộtpython -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ẫnimport file_name 60tă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 62Trong 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 63Hướ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 65Nó 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 66Giả 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ẫnimport 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ẫnimport file_name 68Loạ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 3Như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 69Chú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ànhimport file_name 80Opcode 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 81Cá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 82python -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 3Trong 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 85Bây giờ python -m compileall file_1.py ... file_n.py 291 bằng với:import file_name 86Bâ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 87Hướng dẫn import file_name 88Bậ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 89Sau đó, chúng tôi tạo một đối tượng mã mới: python -m compileall file_1.py ... file_n.py 00Chú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 01THẬ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 andpython -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 02Bây giờ nếu kiểm tra đối tượng mã của nó: python -m compileall file_1.py ... file_n.py 03Trê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 04Như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 05Hó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 07Bạ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 08Như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 09Như 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 10Biế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 11Khi 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 12Nó 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 13Bâ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 14Khi 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ẫnpython -m compileall file_1.py ... file_n.py 15là 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 16Bâ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. |