Trăn có vm không?

Hoạt động nội bộ của Python

Cải thiện bài viết

Lưu bài viết

Thích bài viết

  • Độ khó. Dễ dàng
  • Cập nhật lần cuối. 27 tháng 5 năm 2021

  • Đọc
  • Bàn luận
  • khóa học
  • Luyện tập
  • Băng hình
  • Cải thiện bài viết

    Lưu bài viết

    Python là một ngôn ngữ lập trình hướng đối tượng như Java. Python được gọi là ngôn ngữ thông dịch. Python sử dụng các mô-đun mã có thể hoán đổi cho nhau thay vì một danh sách dài các hướng dẫn là tiêu chuẩn cho các ngôn ngữ lập trình chức năng. Việc triển khai tiêu chuẩn của python được gọi là “cpython”. Đây là triển khai mặc định và được sử dụng rộng rãi của Python.  
    Python không chuyển mã của nó thành mã máy, thứ mà phần cứng có thể hiểu được. Nó thực sự chuyển đổi nó thành một thứ gọi là mã byte. Vì vậy, trong python, quá trình biên dịch xảy ra, nhưng nó không phải là ngôn ngữ máy. Đó là mã byte (. pyc hoặc. pyo) và mã byte này không thể được hiểu bởi CPU. Vì vậy, chúng tôi cần một trình thông dịch được gọi là máy ảo python để thực thi mã byte.  
     

    Trăn có vm không?

    Mã nguồn Python đi qua phần sau để tạo mã thực thi.  
     

    • Bước 1. Trình biên dịch python đọc mã nguồn hoặc hướng dẫn python. Sau đó, nó xác minh rằng hướng dẫn được định dạng đúng, tôi. e. nó kiểm tra cú pháp của từng dòng. Nếu gặp lỗi, nó sẽ ngay lập tức dừng dịch và hiển thị thông báo lỗi
    • Bước 2. Nếu không có lỗi, tôi. e. nếu hướng dẫn python hoặc mã nguồn được định dạng tốt thì trình biên dịch sẽ dịch nó sang dạng tương đương bằng ngôn ngữ trung gian gọi là “Mã byte”
    • Bước 3. Mã byte sau đó được gửi đến Máy ảo Python (PVM) là trình thông dịch python. PVM chuyển đổi mã byte python thành mã máy thực thi được. Nếu xảy ra lỗi trong quá trình diễn giải này thì quá trình chuyển đổi sẽ bị tạm dừng với thông báo lỗi

    Người giới thiệu.  
    https. // leanpub. com/insidethepythonvirtualmachine/đọc
    http. // cao hơn. giáo dục. com/sites/9387067572/information_center_view0/index. html
     

    Ghi chú cá nhân của tôi arrow_drop_up

    Cứu

    Vui lòng Đăng nhập để nhận xét.

    Chúng tôi biết rằng máy tính chỉ hiểu mã máy bao gồm 1 và 0. Vì máy tính chỉ hiểu mã máy, nên bắt buộc chúng ta phải chuyển đổi bất kỳ chương trình nào thành mã máy trước khi gửi cho máy tính để thực thi. Với mục đích này, chúng ta nên nhờ sự trợ giúp của trình biên dịch. Trình biên dịch thường chuyển đổi mã nguồn chương trình thành mã máy

    Trình biên dịch Python thực hiện nhiệm vụ tương tự nhưng theo một cách hơi khác. Nó chuyển đổi mã nguồn chương trình thành mã khác, được gọi là mã byte. Mỗi câu lệnh chương trình Python được chuyển đổi thành một nhóm các lệnh mã byte. Vậy mã byte là gì? . Kích thước của mỗi lệnh mã byte là 1 byte (hoặc 8 bit) và do đó chúng được gọi là lệnh mã byte. Tổ chức Python nói rằng thỉnh thoảng có thể có các hướng dẫn mới hơn được thêm vào các hướng dẫn mã byte hiện có. Chúng ta có thể tìm thấy hướng dẫn mã byte trong. tập tin pyc. Hình dưới đây cho thấy vai trò của máy ảo trong việc chuyển đổi các lệnh mã byte thành mã máy

    Python Virtual Machine (PVM)

    Vai trò của Máy ảo Python (PVM) là chuyển đổi các lệnh mã byte thành mã máy để máy tính có thể thực thi các lệnh mã máy đó và hiển thị đầu ra cuối cùng. Để thực hiện việc chuyển đổi này, PVM được trang bị máy phiên dịch. Trình thông dịch chuyển mã byte thành mã máy và gửi mã máy đó tới bộ xử lý máy tính để thực thi. Vì trình thông dịch đóng vai trò chính nên thường Máy ảo Python còn được gọi là trình thông dịch

    Bài báo này mở ra một loạt bài tìm cách trả lời chính câu hỏi này. Chúng ta sẽ đi sâu vào nội dung bên trong của CPython, triển khai phổ biến nhất của Python. Bằng cách đó, chúng ta sẽ hiểu ngôn ngữ ở mức độ sâu hơn. Đó là mục tiêu chính của loạt bài này. Nếu bạn quen thuộc với Python và đọc C thoải mái nhưng không có nhiều kinh nghiệm làm việc với mã nguồn của CPython, rất có thể bạn sẽ thấy bài viết này thú vị

    CPython là gì và tại sao mọi người muốn nghiên cứu nó

    Hãy bắt đầu bằng cách nêu một số sự thật nổi tiếng. CPython là một trình thông dịch Python được viết bằng C. Đây là một trong những triển khai Python, cùng với PyPy, Jython, IronPython và nhiều ứng dụng khác. CPython được phân biệt ở chỗ nó là bản gốc, được bảo trì nhiều nhất và phổ biến nhất

    CPython triển khai Python, nhưng Python là gì? . Câu trả lời trở nên sắc thái hơn nhiều khi cùng một câu hỏi được đặt đúng. điều gì định nghĩa Python là gì? . Thứ gần nhất với nó là Tham chiếu ngôn ngữ Python bắt đầu bằng các từ sau

    Trong khi tôi đang cố gắng chính xác nhất có thể, tôi đã chọn sử dụng tiếng Anh thay vì các thông số kỹ thuật chính thức cho mọi thứ ngoại trừ cú pháp và phân tích từ vựng. Điều này sẽ làm cho tài liệu dễ hiểu hơn đối với người đọc bình thường, nhưng sẽ để lại chỗ cho sự mơ hồ. Do đó, nếu bạn đến từ sao Hỏa và cố gắng triển khai lại Python chỉ từ tài liệu này, bạn có thể phải đoán mọi thứ và trên thực tế, có thể bạn sẽ phải triển khai một ngôn ngữ hoàn toàn khác. Mặt khác, nếu bạn đang sử dụng Python và thắc mắc các quy tắc chính xác về một lĩnh vực cụ thể của ngôn ngữ là gì, bạn chắc chắn có thể tìm thấy chúng tại đây

    Vì vậy, Python không chỉ được xác định bởi tham chiếu ngôn ngữ của nó. Cũng sẽ sai khi nói rằng Python được xác định bởi triển khai tham chiếu của nó, CPython, vì có một số chi tiết triển khai không phải là một phần của ngôn ngữ. Trình thu gom rác dựa trên số tham chiếu là một ví dụ. Vì không có nguồn sự thật duy nhất, chúng tôi có thể nói rằng Python được xác định một phần bởi Tham chiếu ngôn ngữ Python và một phần bởi triển khai chính của nó, CPython

    Lập luận như vậy có vẻ khoa trương, nhưng tôi nghĩ điều quan trọng là phải làm rõ vai trò chính của chủ đề chúng ta sẽ nghiên cứu. Tuy nhiên, bạn có thể vẫn thắc mắc tại sao chúng ta nên nghiên cứu nó. Bên cạnh sự tò mò đơn thuần, tôi thấy những lý do sau

    • Có một bức tranh đầy đủ giúp hiểu sâu hơn về ngôn ngữ. Việc nắm bắt một số đặc thù của Python sẽ dễ dàng hơn nhiều nếu bạn biết về các chi tiết triển khai của nó
    • Chi tiết thực hiện quan trọng trong thực tế. Cách các đối tượng được lưu trữ, cách hoạt động của trình thu gom rác và cách phối hợp nhiều luồng là những chủ đề có tầm quan trọng cao khi một người muốn hiểu khả năng ứng dụng của ngôn ngữ và các hạn chế của nó, ước tính hiệu suất hoặc phát hiện sự thiếu hiệu quả
    • CPython cung cấp API Python/C cho phép mở rộng Python với C và nhúng Python vào bên trong C. Để sử dụng API này một cách hiệu quả, lập trình viên cần hiểu rõ về cách thức hoạt động của CPython

    Cần những gì để hiểu cách thức hoạt động của CPython

    CPython được thiết kế để dễ bảo trì. Một người mới chắc chắn có thể mong đợi có thể đọc mã nguồn và hiểu nó làm gì. Tuy nhiên, nó có thể mất một thời gian. Bằng cách viết loạt bài này, tôi hy vọng sẽ giúp bạn rút ngắn nó

    Làm thế nào loạt bài này được đặt ra

    Tôi chọn cách tiếp cận từ trên xuống. Trong phần này, chúng ta sẽ khám phá các khái niệm cốt lõi của máy ảo CPython (VM). Tiếp theo, chúng ta sẽ xem cách Python biên dịch chương trình thành thứ gì đó mà VM có thể thực thi. Sau đó, chúng ta sẽ làm quen với mã nguồn và từng bước thực hiện một chương trình đang nghiên cứu các phần chính của trình thông dịch. Cuối cùng, chúng ta sẽ có thể chọn ra từng khía cạnh khác nhau của ngôn ngữ và xem chúng được triển khai như thế nào. Đây không phải là một kế hoạch nghiêm ngặt mà là ý tưởng gần đúng của tôi

    Ghi chú. Trong bài viết này tôi đang đề cập đến CPython 3. 9. Một số chi tiết triển khai chắc chắn sẽ thay đổi khi CPython phát triển. Tôi sẽ cố gắng theo dõi những thay đổi quan trọng và thêm ghi chú cập nhật

    bức tranh lớn

    Quá trình thực thi chương trình Python bao gồm ba giai đoạn

    1. Khởi tạo
    2. Biên soạn
    3. Diễn dịch

    Trong giai đoạn khởi tạo, CPython khởi tạo cấu trúc dữ liệu cần thiết để chạy Python. Nó cũng chuẩn bị những thứ như kiểu tích hợp, định cấu hình và tải các mô-đun tích hợp, thiết lập hệ thống nhập và thực hiện nhiều việc khác. Đây là công đoạn rất quan trọng nhưng thường bị các nhà thám hiểm CPython bỏ qua vì tính chất dịch vụ của nó

    Tiếp đến là giai đoạn tổng hợp. CPython là một trình thông dịch, không phải trình biên dịch theo nghĩa là nó không tạo ra mã máy. Tuy nhiên, các trình thông dịch thường dịch mã nguồn thành một số biểu diễn trung gian trước khi thực thi nó. CPython cũng vậy. Giai đoạn dịch thuật này thực hiện những điều tương tự mà một trình biên dịch điển hình thực hiện. phân tích cú pháp mã nguồn và xây dựng AST (Cây cú pháp trừu tượng), tạo mã byte từ AST và thậm chí thực hiện một số tối ưu hóa mã byte

    Trước khi xem giai đoạn tiếp theo, chúng ta cần hiểu bytecode là gì. Bytecode là một loạt các hướng dẫn. Mỗi lệnh bao gồm hai byte. một cho một opcode và một cho một đối số. Hãy xem xét một ví dụ

    def g(x):
        return x + 3
    

    CPython dịch phần thân của hàm

    def g(x):
        return x + 3
    
    1 thành chuỗi byte sau.
    def g(x):
        return x + 3
    
    2. Nếu chúng tôi chạy mô-đun
    def g(x):
        return x + 3
    
    3 tiêu chuẩn để tháo rời nó, đây là những gì chúng tôi sẽ nhận được

    $ python -m dis example1.py
    ...
    2           0 LOAD_FAST            0 (x)
                2 LOAD_CONST           1 (3)
                4 BINARY_ADD
                6 RETURN_VALUE
    

    Opcode

    def g(x):
        return x + 3
    
    4 tương ứng với byte
    def g(x):
        return x + 3
    
    5 và có đối số
    def g(x):
        return x + 3
    
    6. Opcode
    def g(x):
        return x + 3
    
    7 tương ứng với byte
    def g(x):
        return x + 3
    
    8 và có đối số
    def g(x):
        return x + 3
    
    9. Các lệnh
    $ python -m dis example1.py
    ...
    2           0 LOAD_FAST            0 (x)
                2 LOAD_CONST           1 (3)
                4 BINARY_ADD
                6 RETURN_VALUE
    
    0 và
    $ python -m dis example1.py
    ...
    2           0 LOAD_FAST            0 (x)
                2 LOAD_CONST           1 (3)
                4 BINARY_ADD
                6 RETURN_VALUE
    
    1 luôn được mã hóa lần lượt là
    $ python -m dis example1.py
    ...
    2           0 LOAD_FAST            0 (x)
                2 LOAD_CONST           1 (3)
                4 BINARY_ADD
                6 RETURN_VALUE
    
    2 và
    $ python -m dis example1.py
    ...
    2           0 LOAD_FAST            0 (x)
                2 LOAD_CONST           1 (3)
                4 BINARY_ADD
                6 RETURN_VALUE
    
    3 vì chúng không cần đối số

    Trung tâm của CPython là một máy ảo thực thi mã byte. Bằng cách nhìn vào ví dụ trước, bạn có thể đoán nó hoạt động như thế nào. Máy ảo của CPython dựa trên ngăn xếp. Có nghĩa là nó thực thi các lệnh sử dụng ngăn xếp để lưu trữ và truy xuất dữ liệu. Lệnh

    def g(x):
        return x + 3
    
    4 đẩy một biến cục bộ lên ngăn xếp.
    def g(x):
        return x + 3
    
    7 đẩy một hằng số.
    $ python -m dis example1.py
    ...
    2           0 LOAD_FAST            0 (x)
                2 LOAD_CONST           1 (3)
                4 BINARY_ADD
                6 RETURN_VALUE
    
    0 lấy hai đối tượng ra khỏi ngăn xếp, cộng chúng lại và đẩy kết quả trở lại. Cuối cùng,
    $ python -m dis example1.py
    ...
    2           0 LOAD_FAST            0 (x)
                2 LOAD_CONST           1 (3)
                4 BINARY_ADD
                6 RETURN_VALUE
    
    1 bật bất cứ thứ gì trong ngăn xếp và trả kết quả cho người gọi nó

    Việc thực thi mã byte diễn ra trong một vòng lặp đánh giá khổng lồ chạy trong khi có hướng dẫn để thực thi. Nó dừng lại để mang lại một giá trị hoặc nếu xảy ra lỗi

    Một tổng quan ngắn gọn như vậy làm phát sinh rất nhiều câu hỏi

    • Các đối số cho mã opcode
      def g(x):
          return x + 3
      
      4 và
      def g(x):
          return x + 3
      
      7 có ý nghĩa gì?
    • Máy ảo có đặt các giá trị hoặc tham chiếu đến các đối tượng trên ngăn xếp không?
    • Làm thế nào để CPython biết rằng
      def f(x):
          return x + 1
      
      print(f(1))
      
      0 là một biến cục bộ?
    • Điều gì xảy ra nếu một đối số quá lớn để vừa với một byte đơn?
    • Hướng dẫn cộng hai số có giống hướng dẫn nối hai chuỗi không?

    Để trả lời những câu hỏi này và những câu hỏi hấp dẫn khác, chúng ta cần xem xét các khái niệm cốt lõi của CPython VM

    Đối tượng mã, đối tượng chức năng, khung

    đối tượng mã

    Chúng ta đã thấy mã byte của một hàm đơn giản trông như thế nào. Nhưng một chương trình Python điển hình phức tạp hơn. Làm thế nào để VM thực thi một mô-đun chứa các định nghĩa hàm và thực hiện các lệnh gọi hàm?

    Xem xét chương trình

    def f(x):
        return x + 1
    
    print(f(1))
    

    Mã byte của nó trông như thế nào? . Nó định nghĩa hàm

    def f(x):
        return x + 1
    
    print(f(1))
    
    1, gọi
    def f(x):
        return x + 1
    
    print(f(1))
    
    1 với đối số là ___0_______9 và in kết quả của cuộc gọi. Dù chức năng
    def f(x):
        return x + 1
    
    print(f(1))
    
    1 làm gì, nó không phải là một phần của mã byte của mô-đun. Chúng ta có thể tự đảm bảo bằng cách chạy trình dịch ngược

    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    

    Ở dòng 1, chúng ta định nghĩa hàm

    def f(x):
        return x + 1
    
    print(f(1))
    
    1 bằng cách tạo hàm từ một thứ gọi là đối tượng mã và gắn tên
    def f(x):
        return x + 1
    
    print(f(1))
    
    6 cho nó. Chúng tôi không thấy mã byte của hàm
    def f(x):
        return x + 1
    
    print(f(1))
    
    1 trả về một đối số tăng dần

    Các đoạn mã được thực thi dưới dạng một đơn vị như mô-đun hoặc thân hàm được gọi là khối mã. CPython lưu trữ thông tin về chức năng của một khối mã trong một cấu trúc được gọi là đối tượng mã. Nó chứa mã byte và những thứ như danh sách tên của các biến được sử dụng trong khối. Để chạy một mô-đun hoặc gọi một hàm có nghĩa là bắt đầu đánh giá một đối tượng mã tương ứng

    đối tượng chức năng

    Tuy nhiên, một chức năng không chỉ đơn thuần là một đối tượng mã. Nó phải bao gồm thông tin bổ sung như tên hàm, chuỗi tài liệu, đối số mặc định và giá trị của các biến được xác định trong phạm vi kèm theo. Thông tin này, cùng với một đối tượng mã, được lưu trữ bên trong một đối tượng chức năng. Lệnh

    def f(x):
        return x + 1
    
    print(f(1))
    
    8 được sử dụng để tạo nó. Định nghĩa về cấu trúc đối tượng chức năng trong mã nguồn CPython được đặt trước bởi nhận xét sau

    Đối tượng chức năng và đối tượng mã không nên nhầm lẫn với nhau

    Các đối tượng hàm được tạo bằng cách thực thi câu lệnh 'def'. Họ tham chiếu một đối tượng mã trong thuộc tính __code__ của họ, là một đối tượng cú pháp thuần túy, tôi. e. không gì khác hơn là một phiên bản được biên dịch của một số dòng mã nguồn. Có một đối tượng mã cho mỗi "đoạn" mã nguồn, nhưng mỗi đối tượng mã có thể được tham chiếu bằng 0 hoặc nhiều đối tượng hàm chỉ tùy thuộc vào số lần câu lệnh 'def' trong nguồn được thực thi cho đến nay

    Làm thế nào mà một số đối tượng chức năng có thể tham chiếu đến một đối tượng mã duy nhất?

    def make_add_x(x):
        def add_x(y):
            return x + y
        return add_x
    
    add_4 = make_add_x(4)
    add_5 = make_add_x(5)
    

    Mã byte của hàm

    def f(x):
        return x + 1
    
    print(f(1))
    
    9 chứa lệnh
    def f(x):
        return x + 1
    
    print(f(1))
    
    8. Các hàm
    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    1 và
    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    2 là kết quả của việc gọi hướng dẫn này với cùng một đối tượng mã làm đối số. Nhưng có một đối số khác – giá trị của
    def f(x):
        return x + 1
    
    print(f(1))
    
    0. Mỗi chức năng có riêng nó theo cơ chế của các biến ô cho phép chúng ta tạo các bao đóng như
    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    1 và
    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    2

    Trước khi chúng ta chuyển sang khái niệm tiếp theo, hãy xem các định nghĩa về mã và các đối tượng chức năng để hiểu rõ hơn về chúng là gì

    struct PyCodeObject {
        PyObject_HEAD
        int co_argcount;            /* #arguments, except *args */
        int co_posonlyargcount;     /* #positional only arguments */
        int co_kwonlyargcount;      /* #keyword only arguments */
        int co_nlocals;             /* #local variables */
        int co_stacksize;           /* #entries needed for evaluation stack */
        int co_flags;               /* CO_..., see below */
        int co_firstlineno;         /* first source line number */
        PyObject *co_code;          /* instruction opcodes */
        PyObject *co_consts;        /* list (constants used) */
        PyObject *co_names;         /* list of strings (names used) */
        PyObject *co_varnames;      /* tuple of strings (local variable names) */
        PyObject *co_freevars;      /* tuple of strings (free variable names) */
        PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    
        Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
        PyObject *co_filename;      /* unicode (where it was loaded from) */
        PyObject *co_name;          /* unicode (name, for reference) */
            /* .. more members .. */
    };
    

    typedef struct {
        PyObject_HEAD
        PyObject *func_code;        /* A code object, the __code__ attribute */
        PyObject *func_globals;     /* A dictionary (other mappings won't do) */
        PyObject *func_defaults;    /* NULL or a tuple */
        PyObject *func_kwdefaults;  /* NULL or a dict */
        PyObject *func_closure;     /* NULL or a tuple of cell objects */
        PyObject *func_doc;         /* The __doc__ attribute, can be anything */
        PyObject *func_name;        /* The __name__ attribute, a string object */
        PyObject *func_dict;        /* The __dict__ attribute, a dict or NULL */
        PyObject *func_weakreflist; /* List of weak references */
        PyObject *func_module;      /* The __module__ attribute, can be anything */
        PyObject *func_annotations; /* Annotations, a dict or NULL */
        PyObject *func_qualname;    /* The qualified name */
        vectorcallfunc vectorcall;
    } PyFunctionObject;
    

    đối tượng khung

    Khi VM thực thi một đối tượng mã, nó phải theo dõi các giá trị của biến và ngăn xếp giá trị thay đổi liên tục. Nó cũng cần nhớ nơi nó dừng thực thi đối tượng mã hiện tại để thực thi đối tượng khác và nơi tiếp tục quay trở lại. CPython lưu trữ thông tin này bên trong một đối tượng khung hoặc đơn giản là một khung. Khung cung cấp trạng thái trong đó đối tượng mã có thể được thực thi. Vì chúng ta đã quen với mã nguồn hơn, nên tôi cũng để định nghĩa về đối tượng khung ở đây

    struct _frame {
        PyObject_VAR_HEAD
        struct _frame *f_back;      /* previous frame, or NULL */
        PyCodeObject *f_code;       /* code segment */
        PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
        PyObject *f_globals;        /* global symbol table (PyDictObject) */
        PyObject *f_locals;         /* local symbol table (any mapping) */
        PyObject **f_valuestack;    /* points after the last local */
    
        PyObject **f_stacktop;          /* Next free slot in f_valuestack.  .. */
        PyObject *f_trace;          /* Trace function */
        char f_trace_lines;         /* Emit per-line trace events? */
        char f_trace_opcodes;       /* Emit per-opcode trace events? */
    
        /* Borrowed reference to a generator, or NULL */
        PyObject *f_gen;
    
        int f_lasti;                /* Last instruction if called */
        /* .. */
        int f_lineno;               /* Current line number */
        int f_iblock;               /* index in f_blockstack */
        char f_executing;           /* whether the frame is still executing */
        PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
        PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
    };
    

    Khung đầu tiên được tạo để thực thi đối tượng mã của mô-đun. CPython tạo một khung mới bất cứ khi nào nó cần thực thi một đối tượng mã khác. Mỗi khung có một tham chiếu đến khung trước đó. Do đó, các khung tạo thành một chồng khung, còn được gọi là ngăn xếp cuộc gọi, với khung hiện tại nằm trên cùng. Khi một chức năng được gọi, một khung mới được đẩy vào ngăn xếp. Khi trở về từ khung hiện đang thực thi, CPython tiếp tục thực hiện khung trước đó bằng cách ghi nhớ lệnh được xử lý cuối cùng của nó. Theo một nghĩa nào đó, CPython VM không làm gì ngoài việc xây dựng và thực thi các khung. Tuy nhiên, như chúng ta sẽ sớm thấy, bản tóm tắt này, nói một cách nhẹ nhàng, che giấu một số chi tiết

    Chủ đề, trình thông dịch, thời gian chạy

    Chúng ta đã xem xét ba khái niệm quan trọng

    • một đối tượng mã
    • một đối tượng chức năng;
    • đối tượng khung

    CPython có thêm ba

    • một trạng thái chủ đề
    • một trạng thái thông dịch viên;
    • một trạng thái thời gian chạy

    trạng thái chủ đề

    Trạng thái luồng là cấu trúc dữ liệu chứa dữ liệu dành riêng cho luồng bao gồm ngăn xếp cuộc gọi, trạng thái ngoại lệ và cài đặt gỡ lỗi. Không nên nhầm lẫn với một chuỗi hệ điều hành. Chúng được kết nối chặt chẽ, mặc dù. Xem xét điều gì sẽ xảy ra khi bạn sử dụng mô-đun

    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    6 tiêu chuẩn để chạy một chức năng trong một chuỗi riêng biệt

    from threading import Thread
    
    def f():
        """Perform an I/O-bound task"""
        pass
    
    t = Thread(target=f)
    t.start()
    t.join()
    

    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    7 thực sự tạo ra một luồng hệ điều hành mới bằng cách gọi hàm OS (
    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    8 trên các hệ thống giống UNIX và
    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    9 trên Windows). Chuỗi mới được tạo gọi hàm từ mô-đun
    def make_add_x(x):
        def add_x(y):
            return x + y
        return add_x
    
    add_4 = make_add_x(4)
    add_5 = make_add_x(5)
    
    0 chịu trách nhiệm gọi mục tiêu. Hàm này không chỉ nhận được mục tiêu và các đối số của mục tiêu mà còn nhận được trạng thái luồng mới sẽ được sử dụng trong luồng hệ điều hành mới. Một luồng hệ điều hành đi vào vòng đánh giá với trạng thái luồng riêng của nó, do đó luôn có sẵn luồng đó

    Chúng ta có thể nhớ ở đây GIL (Khóa phiên dịch toàn cầu) nổi tiếng ngăn chặn nhiều luồng trong vòng đánh giá cùng một lúc. Lý do chính cho điều đó là để bảo vệ trạng thái của CPython khỏi tham nhũng mà không đưa ra các khóa chi tiết hơn. Tham chiếu API Python/C giải thích rõ ràng về GIL

    Trình thông dịch Python không hoàn toàn an toàn cho luồng. Để hỗ trợ các chương trình Python đa luồng, có một khóa toàn cầu, được gọi là khóa trình thông dịch toàn cầu hoặc GIL, khóa này phải được giữ bởi luồng hiện tại trước khi có thể truy cập các đối tượng Python một cách an toàn. Không có khóa, ngay cả những thao tác đơn giản nhất cũng có thể gây ra sự cố trong chương trình đa luồng. ví dụ: khi hai luồng đồng thời tăng số tham chiếu của cùng một đối tượng, thì số tham chiếu có thể chỉ được tăng một lần thay vì hai lần

    Để quản lý nhiều luồng, cần có cấu trúc dữ liệu cấp cao hơn trạng thái luồng

    trình thông dịch và trạng thái thời gian chạy

    Trên thực tế, có hai người trong số họ. trạng thái trình thông dịch và trạng thái thời gian chạy. Sự cần thiết cho cả hai có thể không rõ ràng ngay lập tức. Tuy nhiên, việc thực thi bất kỳ chương trình nào cũng có ít nhất một phiên bản của mỗi chương trình và có những lý do chính đáng cho điều đó

    Trạng thái trình thông dịch là một nhóm các luồng cùng với dữ liệu dành riêng cho nhóm này. Các chủ đề chia sẻ những thứ như mô-đun đã tải (_______32_______1), nội trang (

    def make_add_x(x):
        def add_x(y):
            return x + y
        return add_x
    
    add_4 = make_add_x(4)
    add_5 = make_add_x(5)
    
    2) và hệ thống nhập (
    def make_add_x(x):
        def add_x(y):
            return x + y
        return add_x
    
    add_4 = make_add_x(4)
    add_5 = make_add_x(5)
    
    3)

    Trạng thái thời gian chạy là một biến toàn cục. Nó lưu trữ dữ liệu cụ thể cho một quá trình. Điều này bao gồm trạng thái của CPython (e. g. nó có được khởi tạo hay không?) và cơ chế GIL

    Thông thường, tất cả các luồng của một quy trình đều thuộc về cùng một trình thông dịch. Tuy nhiên, có một số trường hợp hiếm gặp khi một người có thể muốn tạo một trình thông dịch con để cô lập một nhóm luồng. , sử dụng các trình thông dịch riêng biệt để chạy các ứng dụng WSGI, là một ví dụ. Hiệu quả rõ ràng nhất của sự cô lập là mỗi nhóm luồng có phiên bản riêng của tất cả các mô-đun bao gồm

    def make_add_x(x):
        def add_x(y):
            return x + y
        return add_x
    
    add_4 = make_add_x(4)
    add_5 = make_add_x(5)
    
    4, là một không gian tên chung

    CPython không cung cấp một cách dễ dàng để tạo trình thông dịch mới tương tự như mô-đun

    $ python -m dis example2.py
    
    1           0 LOAD_CONST               0 ("example.py", line 1>)
                2 LOAD_CONST               1 ('f')
                4 MAKE_FUNCTION            0
                6 STORE_NAME               0 (f)
    
    4           8 LOAD_NAME                1 (print)
               10 LOAD_NAME                0 (f)
               12 LOAD_CONST               2 (1)
               14 CALL_FUNCTION            1
               16 CALL_FUNCTION            1
               18 POP_TOP
               20 LOAD_CONST               3 (None)
               22 RETURN_VALUE
    ...
    
    6. Tính năng này chỉ được hỗ trợ thông qua API Python/C, nhưng điều này có thể thay đổi vào một ngày nào đó

    tóm tắt kiến ​​trúc

    Hãy tóm tắt nhanh kiến ​​trúc của CPython để xem mọi thứ khớp với nhau như thế nào. Trình thông dịch có thể được xem như một cấu trúc phân lớp. Sau đây tổng hợp các lớp là gì

    1. thời gian chạy. trạng thái toàn cục của một tiến trình;
    2. Thông dịch viên. một nhóm các luồng và một số dữ liệu mà chúng chia sẻ, chẳng hạn như các mô-đun đã nhập
    3. Chủ đề. dữ liệu cụ thể cho một chuỗi hệ điều hành duy nhất;
    4. Khung. một phần tử của ngăn xếp cuộc gọi;
    5. vòng đánh giá. một nơi mà một đối tượng khung được thực thi

    Các lớp được biểu diễn bằng các cấu trúc dữ liệu tương ứng mà chúng ta đã thấy. Trong một số trường hợp, chúng không tương đương, mặc dù. Ví dụ, cơ chế cấp phát bộ nhớ được thực hiện bằng các biến toàn cục. Nó không phải là một phần của trạng thái thời gian chạy nhưng chắc chắn là một phần của lớp thời gian chạy

    Phần kết luận

    Trong phần này, chúng tôi đã phác thảo những gì

    def g(x):
        return x + 3
    
    0 thực hiện để thực thi chương trình Python. Chúng tôi đã thấy rằng nó hoạt động trong ba giai đoạn

    1. khởi tạo CPython

    2. biên dịch mã nguồn thành đối tượng mã của mô-đun;

    3. thực thi mã byte của đối tượng mã

    Phần trình thông dịch chịu trách nhiệm thực thi mã byte được gọi là máy ảo. CPython VM có một số khái niệm đặc biệt quan trọng. đối tượng mã, đối tượng khung, trạng thái luồng, trạng thái trình thông dịch và thời gian chạy. Các cấu trúc dữ liệu này tạo thành cốt lõi của kiến ​​trúc CPython

    Chúng tôi chưa đề cập đến nhiều thứ. Chúng tôi tránh đào sâu vào mã nguồn. Các giai đoạn khởi tạo và biên dịch hoàn toàn nằm ngoài phạm vi của chúng tôi. Thay vào đó, chúng tôi bắt đầu với tổng quan về VM. Theo tôi, có như vậy chúng ta mới thấy rõ hơn trách nhiệm của từng khâu. Bây giờ chúng ta biết CPython biên dịch mã nguồn thành gì – thành đối tượng mã. Lần tới chúng ta sẽ xem nó làm điều đó như thế nào


    Nếu bạn có bất kỳ câu hỏi, nhận xét hoặc đề xuất nào, vui lòng liên hệ với tôi theo địa chỉ victor@tenthousandmeters. com


    Cập nhật từ ngày 4 tháng 9 năm 2020. Tôi đã tạo một danh sách các tài nguyên mà tôi đã sử dụng để tìm hiểu về nội bộ CPython

    Python có thứ gì giống như JVM không?

    Máy ảo tiến trình đơn giản là một chương trình cung cấp môi trường lập trình chung -- một chương trình có thể được lập trình. Java có trình thông dịch cũng như máy ảo và Python có máy ảo cũng như trình thông dịch .

    Máy ảo Python làm gì?

    Vai trò của Máy ảo Python (PVM) là chuyển đổi hướng dẫn mã byte thành mã máy để máy tính có thể thực thi các hướng dẫn mã máy đó và hiển thị đầu ra cuối cùng.

    Làm cách nào để tạo một máy ảo trong Python?

    Ví dụ 2. 12. .
    Phải có 512 MB bộ nhớ, được biểu thị bằng byte. Sao chép. Đã sao chép. .
    Phải được đính kèm với cụm Mặc định và do đó là Trung tâm dữ liệu mặc định. Sao chép. Đã sao chép. .
    Phải dựa trên mẫu Trống mặc định. Sao chép. Đã sao chép. .
    Phải khởi động từ ổ đĩa cứng ảo. Sao chép

    Máy ảo Python được viết bằng gì?

    Trình thông dịch Python có tên là “CPython” và được viết bằng ngôn ngữ lập trình C . Đây là triển khai mặc định cho Python.