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.py
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s8
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.py
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s9. Như bạn có thể thấy có cả trình biên dịch và trình thông dịch
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.pyc0
print(“Hello World!”)hello()
Hãy thử chạy tập lệnh
$ time python3 a.pyHello 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.pyc1. 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.pyc2
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
________số 8_______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.pyHello 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.pyc3
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.pyc4 để xem nội dung của $ ls __pycache__/
hello.cpython-38.pyc5 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.pyc6 để ban đầu chuyển mã nguồn thành AST, sau đó là $ ls __pycache__/
hello.cpython-38.pyc7. Do đó, mã được biên dịch sau này có thể được chạy bởi $ ls __pycache__/
hello.cpython-38.pyc8 hoặc $ ls __pycache__/
hello.cpython-38.pyc9
>>> exec(c)
Hello World!
Ở đây, $ python3 -m compileall hello.py0 ngụ ý rằng mã nguồn chứa nhiều câu lệnh Python
Bạ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.pyc7
b’t\x00d\x01\x83\x01\x01\x00d\x00S\x00'
Để xem $ python3 -m compileall hello.py2 theo nghĩa đen, chúng ta có thể sử dụng $ ls __pycache__/
hello.cpython-38.pyc7
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.py4 là lệnh một byte. Trong khi $ python3 -m compileall hello.py5 là đối số dành riêng cho hướng dẫn
$ time python3 a.pyHello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s0
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.py6, tên thân thiện với con người của $ python3 -m compileall hello.py7. Để tìm tên, chúng ta có thể kiểm tra danh sách $ python3 -m compileall hello.py8 trong mô-đun $ python3 -m compileall hello.py9
$ time python3 a.pyHello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s1
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.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s0
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s2
Ở đâ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.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s1. Ví dụ
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s3
- $ time python3 a.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s2 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 $ time python3 a.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s3 - $ time python3 a.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s4 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 - $ time python3 a.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s5 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à $ time python3 a.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s6. 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
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.pyHello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s4
Để biết thông tin về co_attributes, bạn có thể xem chuỗi tài liệu này
$ time python3 a.pyHello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s5
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
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s7yêu cầu python trả về phần tử vị trí thứ 0 trong $ time python3 a.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s3và đẩy nó vào ngăn xếp đánh giá. Trong trường hợp này là chức năng in - $ time python3 a.py
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s9 sẽ lấy giá trị theo nghĩa đen ở chỉ mục 1 của $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s0 và đẩy nó lên đầu. Giá trị là $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s1 - $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s2 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ề - $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s3 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 - $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s4 sẽ thêm phần tử vị trí o từ $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s5 là Không có vào giá trị cao nhất của ngăn xếp - $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s6 sẽ trả về đỉnh ngăn xếp cho người gọi hàm
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.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s7 một mục vào $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s8 của ngăn xếp hoặc $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s9 mục đó từ $ time __pycache__/hello.cpython-38.pyc
Hello World!real 0m0.020s
user 0m0.008s
sys 0m0.012s8. Chúng tôi có >>> 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ình
Mỗ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
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s6
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!6
ghi 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ối
Khung 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
Hello World!real 0m0.038s
user 0m0.025s
sys 0m0.010s7
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