Python là một ngôn ngữ thông dịch. Phiên dịch có nghĩa là mã nguồn được dịch sang mã máy khi chương trình chạy. Nhưng đó là một sự đơn giản hóa tổng thể
Python về cơ bản có bốn bước để chạy mã nguồn của bạn. lexer, trình phân tích cú pháp, trình biên dịch và trình thông dịch. Lexer và trình phân tích cú pháp là hiển nhiên. Nó được sử dụng để tạo cây cú pháp trừu tượng. Trình biên dịch có thể gây sốc cho hầu hết mọi người
Giống như hầu hết các ngôn ngữ thông dịch khác, Python ban đầu biên dịch mã nguồn do chúng tôi viết sang định dạng trung gian
$ time python3 a.py8
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Mã byte là một tập lệnh cấp thấp được gọi là chứa tập lệnh có thể được giải thích bởi
$ time python3 a.py9. Như bạn có thể thấy có cả trình biên dịch và trình thông dịch
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Nhưng lý do Python được gọi là ngôn ngữ thông dịch là vì hầu hết công việc được thực hiện bởi trình thông dịch. Có thể đọc và hiểu bytecode sẽ giúp bạn tối ưu code cũng như hiểu Python hơn
Chúng tôi có một kịch bản đơn giản
$ ls __pycache__/
hello.cpython-38.pyc
0def hello[]:
print[“Hello World!”]hello[]
Hãy thử chạy tập lệnh
$ time python3 a.py
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Khi bạn chạy tệp, thỉnh thoảng bạn sẽ thấy thư mục con
$ ls __pycache__/
hello.cpython-38.pyc
1. Nếu bạn nhìn vào bên trong nó, bạn sẽ thấy tên tệp mã nguồn của mình với phần mở rộng $ ls __pycache__/
hello.cpython-38.pyc
2$ ls __pycache__/
hello.cpython-38.pyc
Trong trường hợp bạn không nhìn thấy thư mục. Bạn có thể tự biên dịch tệp bằng CLI
Nếu bạn chạy lại nó, bạn sẽ thấy thời gian thực thi sẽ được cải thiện một chút vì Python không phải phân tích lại mã nguồn của chúng tôi mỗi khi nó chạy
$ time python3 a.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
Bạn thậm chí có thể chạy trực tiếp tệp
$ ls __pycache__/
hello.cpython-38.pyc
3$ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
Tháo rời mã byte
Sử dụng
$ ls __pycache__/
hello.cpython-38.pyc
4 để xem nội dung của $ ls __pycache__/
hello.cpython-38.pyc
5 sẽ cung cấp cho bạn các giá trị rác. Chúng tôi có thể thấy mã byte theo nghĩa đen của mã của chúng tôi bằng cách sử dụng $ ls __pycache__/
hello.cpython-38.pyc
6 để ban đầu chuyển mã nguồn thành AST, sau đó là $ ls __pycache__/
hello.cpython-38.pyc
7. Do đó, mã được biên dịch sau này có thể được chạy bởi $ ls __pycache__/
hello.cpython-38.pyc
8 hoặc $ ls __pycache__/
hello.cpython-38.pyc
9>>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
Ở đây,
$ python3 -m compileall hello.py
0 ngụ ý rằng mã nguồn chứa nhiều câu lệnh PythonBạn có thể xem mã byte cho các giá trị đã được biên dịch bằng cách sử dụng
$ ls __pycache__/
hello.cpython-38.pyc
7>>> hello.__code__.co_code
b’t\x00d\x01\x83\x01\x01\x00d\x00S\x00'
Để xem
$ python3 -m compileall hello.py
2 theo nghĩa đen, chúng ta có thể sử dụng $ ls __pycache__/
hello.cpython-38.pyc
7>>> c.co_code
b'd\x00d\x01\x84\x00Z\x00d\x02S\x00'
Hãy cố gắng hiểu điều này. Chúng tôi biết rằng mỗi lệnh trong Python bao gồm hai byte ở định dạng sau
opcode oparg
$ python3 -m compileall hello.py
4 là lệnh một byte. Trong khi $ python3 -m compileall hello.py
5 là đối số dành riêng cho hướng dẫn$ time python3 a.py0
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Một điều sẽ ngay lập tức đến với bạn khi bạn thấy điều này là nó chẳng có ý nghĩa gì đối với con người chúng ta. Vì vậy, chúng tôi có một thứ gọi là
$ python3 -m compileall hello.py
6, tên thân thiện với con người của $ python3 -m compileall hello.py
7. Để tìm tên, chúng ta có thể kiểm tra danh sách $ python3 -m compileall hello.py
8 trong mô-đun $ python3 -m compileall hello.py
9$ time python3 a.py1
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Thay vì tổ chức opcode và oparg để xem hướng dẫn mã byte Chúng ta có thể phân tích trực tiếp đối tượng python thành dạng mã byte có thể đọc được bằng cách sử dụng mô-đun
$ time python3 a.py0
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
$ time python3 a.py2
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Ở đây, số 2 ở bên trái biểu thị số dòng
đồng thuộc tính
Có rất nhiều thuộc tính quan trọng được mang bởi
$ time python3 a.py1. Ví dụ
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
$ time python3 a.py3
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
$ time python3 a.py
2 là một tuple chứa các thuộc tính và phương thức toàn cục được sử dụng bên trong phạm vi. Hàm in là một hàm toàn cục đang được sử dụng trong hàm hiện tại nên chúng ta có thể thấy nó trong bộ dữ liệu
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time python3 a.py
3
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time python3 a.py
4 là bộ chứa các tên biến cục bộ được sử dụng trong hàm. Không có biến nào được khai báo trong hàm nên bộ dữ liệu trống
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time python3 a.py
5 Nó sẽ trả về các chữ được sử dụng bởi bytecode. Trong hàm hello, giá trị theo nghĩa đen duy nhất chúng ta thấy là
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time python3 a.py
6. Python thêm Không làm một trong các tham số trả về mặc định cho hàm. Điều này sẽ cho phép python xử lý trong trường hợp không tìm thấy câu lệnh return
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
Có co_attributes khác mà bạn có thể kiểm tra chúng bằng cách sử dụng
$ time python3 a.py4
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Để biết thông tin về co_attributes, bạn có thể xem chuỗi tài liệu này
$ time python3 a.py5
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Bây giờ hãy quay lại opcode. Đây là hướng dẫn mà máy ảo python sẽ thực thi chức năng xin chào của chúng tôi
$ time python3 a.py
7yêu cầu python trả về phần tử vị trí thứ 0 trong
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time python3 a.py
3và đẩy nó vào ngăn xếp đánh giá. Trong trường hợp này là chức năng in
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time python3 a.py
9 sẽ lấy giá trị theo nghĩa đen ở chỉ mục 1 của
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time __pycache__/hello.cpython-38.pyc
0 và đẩy nó lên đầu. Giá trị là
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time __pycache__/hello.cpython-38.pyc
1
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time __pycache__/hello.cpython-38.pyc
2 yêu cầu python gọi hàm. Nó sẽ bật 1 đối số vị trí từ ngăn xếp. Bên dưới các đối số là đối tượng có thể gọi được. Tôi gọi đối tượng có thể gọi được bằng các đối số đã bật và đẩy giá trị trả về
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time __pycache__/hello.cpython-38.pyc
3 loại bỏ đỉnh ngăn xếp mà trong trường hợp hiện tại là chức năng của chúng tôi
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time __pycache__/hello.cpython-38.pyc
4 sẽ thêm phần tử vị trí o từ
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time __pycache__/hello.cpython-38.pyc
5 là Không có vào giá trị cao nhất của ngăn xếp
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s$ time __pycache__/hello.cpython-38.pyc
6 sẽ trả về đỉnh ngăn xếp cho người gọi hàm
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
Như bạn có thể nhận thấy, các hướng dẫn mã byte của Python bao gồm thao tác đánh giá ngăn xếp của khung ngăn xếp cuộc gọi hiện tại. Chúng tôi sẽ nhanh chóng lướt qua máy ảo python để hiểu lý do
Cần phải hiểu rằng mã byte có thể không phổ biến giữa nhiều phiên bản Python. Tôi đang sử dụng Python3. 8 cho bài viết của tôi. Bạn có thể tìm hiểu về mã byte cụ thể của phiên bản tại đây
Máy ảo Python
Cpython sử dụng máy ảo dựa trên ngăn xếp. Nó đơn giản và mạnh mẽ. Không có đăng ký. Thay vào đó, chúng ta có thể thêm
$ time __pycache__/hello.cpython-38.pyc7 một mục vào
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
$ time __pycache__/hello.cpython-38.pyc8 của ngăn xếp hoặc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
$ time __pycache__/hello.cpython-38.pyc9 mục đó từ
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
$ time __pycache__/hello.cpython-38.pyc8. Chúng tôi có
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s
>>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
1 là ngăn xếp chính cho chương trình Python. Nó có một mục tên là >>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
2 cho mỗi lệnh gọi hàm đang hoạt động. Đáy ngăn xếp là điểm vào của chương trìnhMỗi lần gọi hàm sẽ thêm một khung mới vào ngăn xếp và mỗi khi hàm gọi trả về, nó sẽ bật lên khung. Trong Python, chúng ta có thể dễ dàng truy cập các khung này bằng mô-đun
>>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
3$ time python3 a.py6
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Thanh tra trả lại một
>>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
4 được gọi là >>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
5. Nó bao gồm >>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
6Ghi chú. Chúng tôi đang thấy
>>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
7 vì chúng tôi đang sử dụng thiết bị đầu cuốiKhung hiện tại sẽ luôn ở trên cùng để bạn có thể truy cập khung hiện tại bằng cách sử dụng
>>> c = compile['def hello[]:\n\tprint["Hello World!"]\nhello[]', '', "exec"]
>>> exec[c]
Hello World!
8$ time python3 a.py7
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s
Bạn có thể nhận thêm thông tin về khung bằng các thuộc tính đối tượng khung. để biết thêm thông tin tham khảo tại đây
Phần kết luận
Để bài viết ngắn gọn, chúng tôi xin kết thúc tại đây. Còn rất nhiều thứ còn lại nhưng với chừng này bạn có thể bắt đầu khám phá mã byte python
Học cách đọc và viết mã byte Python sẽ giúp bạn hiểu Python hơn và giúp bạn tối ưu hóa mã của mình
Ngoài ra, đây là bước khởi đầu để hiểu cách thức hoạt động của máy ảo. Điều này là dành cho hôm nay. Tôi đã thêm các liên kết hữu ích để học thêm