5. Hệ thống nhập khẩu

Mã Python trong một module có quyền truy cập vào mã trong một mô-đun khác bằng quá trình của importing. Câu lệnh import là cách phổ biến nhất để gọi máy móc nhập khẩu, nhưng nó không phải là cách duy nhất. Các chức năng như importlib.import_module()__import__() tích hợp cũng có thể được sử dụng để gọi máy nhập.

Câu lệnh import kết hợp hai thao tác; nó tìm kiếm mô-đun được đặt tên, sau đó liên kết các kết quả tìm kiếm đó với một tên trong phạm vi cục bộ. Hoạt động tìm kiếm của câu lệnh import được định nghĩa là lệnh gọi hàm __import__(), với các đối số thích hợp. Giá trị trả về của __import__() được sử dụng để thực hiện thao tác liên kết tên của câu lệnh import. Xem câu lệnh import để biết chi tiết chính xác về thao tác liên kết tên đó.

Cuộc gọi trực tiếp tới __import__() chỉ thực hiện tìm kiếm mô-đun và nếu tìm thấy thì hoạt động tạo mô-đun. Mặc dù một số tác dụng phụ nhất định có thể xảy ra, chẳng hạn như nhập gói gốc và cập nhật các bộ đệm khác nhau (bao gồm sys.modules), nhưng chỉ có câu lệnh import thực hiện thao tác liên kết tên.

Khi một câu lệnh import được thực thi, hàm __import__() dựng sẵn tiêu chuẩn sẽ được gọi. Các cơ chế khác để gọi hệ thống nhập (chẳng hạn như importlib.import_module()) có thể chọn bỏ qua __import__() và sử dụng các giải pháp của riêng chúng để triển khai ngữ nghĩa nhập.

Khi một mô-đun được nhập lần đầu tiên, Python sẽ tìm kiếm mô-đun đó và nếu tìm thấy, nó sẽ tạo một đối tượng mô-đun [1], khởi tạo nó. Nếu không tìm thấy mô-đun được đặt tên, ModuleNotFoundError sẽ xuất hiện. Python thực hiện nhiều chiến lược khác nhau để tìm kiếm mô-đun được đặt tên khi máy nhập được gọi. Những chiến lược này có thể được sửa đổi và mở rộng bằng cách sử dụng nhiều hook khác nhau được mô tả trong các phần bên dưới.

Thay đổi trong phiên bản 3.3: Hệ thống nhập đã được cập nhật để triển khai đầy đủ giai đoạn thứ hai của PEP 302. Không còn bất kỳ máy móc nhập khẩu ngầm nào nữa - toàn bộ hệ thống nhập khẩu được hiển thị thông qua sys.meta_path. Ngoài ra, hỗ trợ gói không gian tên gốc đã được triển khai (xem PEP 420).

5.1. importlib

Mô-đun importlib cung cấp API phong phú để tương tác với hệ thống nhập. Ví dụ: importlib.import_module() cung cấp API được đề xuất, đơn giản hơn __import__() tích hợp để gọi máy móc nhập khẩu. Tham khảo tài liệu thư viện importlib để biết thêm chi tiết.

5.2. Gói

Python chỉ có một loại đối tượng mô-đun và tất cả các mô-đun đều thuộc loại này, bất kể mô-đun đó được triển khai bằng Python, C hay thứ gì khác. Để giúp tổ chức các mô-đun và cung cấp hệ thống phân cấp đặt tên, Python có khái niệm packages.

Bạn có thể coi các gói là các thư mục trên một hệ thống tệp và các mô-đun là các tệp trong các thư mục, nhưng đừng hiểu sự tương tự này theo nghĩa đen vì các gói và mô-đun không cần phải bắt nguồn từ hệ thống tệp. Với mục đích của tài liệu này, chúng tôi sẽ sử dụng sự tương tự thuận tiện này của các thư mục và tệp. Giống như các thư mục hệ thống tệp, các gói được sắp xếp theo thứ bậc và bản thân các gói có thể chứa các gói con cũng như các mô-đun thông thường.

Điều quan trọng cần ghi nhớ là tất cả các gói đều là mô-đun, nhưng không phải tất cả các mô-đun đều là gói. Hay nói cách khác, các gói chỉ là một loại mô-đun đặc biệt. Cụ thể, bất kỳ mô-đun nào chứa thuộc tính __path__ đều được coi là một gói.

Tất cả các mô-đun đều có tên. Tên gói con được phân tách khỏi tên gói mẹ bằng dấu chấm, gần giống với cú pháp truy cập thuộc tính tiêu chuẩn của Python. Vì vậy, bạn có thể có một gói tên là email, gói này lần lượt có một gói con tên là email.mime và một mô-đun trong gói con đó có tên là email.mime.text.

5.2.1. Gói thông thường

Python định nghĩa hai loại gói, regular packagesnamespace packages. Các gói thông thường là các gói truyền thống vì chúng đã tồn tại trong Python 3.2 trở về trước. Gói thông thường thường được triển khai dưới dạng thư mục chứa tệp __init__.py. Khi một gói thông thường được nhập, tệp __init__.py này được thực thi ngầm và các đối tượng mà nó xác định được liên kết với các tên trong không gian tên của gói. Tệp __init__.py có thể chứa cùng một mã Python mà bất kỳ mô-đun nào khác có thể chứa và Python sẽ thêm một số thuộc tính bổ sung vào mô-đun khi nó được nhập.

Ví dụ: bố cục hệ thống tệp sau đây xác định gói parent cấp cao nhất với ba gói con

cha mẹ/
    __init__.py
    một/
        __init__.py
    hai/
        __init__.py
    ba/
        __init__.py

Nhập parent.one sẽ ngầm thực thi parent/__init__.pyparent/one/__init__.py. Các lần nhập parent.two hoặc parent.three tiếp theo sẽ thực thi parent/two/__init__.pyparent/three/__init__.py tương ứng.

5.2.2. Gói không gian tên

Gói không gian tên là sự kết hợp của nhiều portions khác nhau, trong đó mỗi phần đóng góp một gói con cho gói chính. Các phần có thể nằm ở các vị trí khác nhau trên hệ thống tệp. Các phần cũng có thể được tìm thấy trong tệp zip, trên mạng hoặc bất kỳ nơi nào khác mà Python tìm kiếm trong quá trình nhập. Các gói không gian tên có thể tương ứng hoặc không tương ứng trực tiếp với các đối tượng trên hệ thống tệp; chúng có thể là các mô-đun ảo không có biểu diễn cụ thể.

Các gói không gian tên không sử dụng danh sách thông thường cho thuộc tính __path__ của chúng. Thay vào đó, họ sử dụng loại có thể lặp tùy chỉnh sẽ tự động thực hiện tìm kiếm mới cho các phần gói trong lần thử nhập tiếp theo trong gói đó nếu đường dẫn của gói mẹ (hoặc sys.path cho gói cấp cao nhất) thay đổi.

Với các gói không gian tên, không có tệp parent/__init__.py. Trên thực tế, có thể có nhiều thư mục parent được tìm thấy trong quá trình tìm kiếm nhập, trong đó mỗi thư mục được cung cấp bởi một phần khác nhau. Do đó, parent/one có thể không được đặt bên cạnh parent/two. Trong trường hợp này, Python sẽ tạo gói không gian tên cho gói parent cấp cao nhất bất cứ khi nào gói đó hoặc một trong các gói con của nó được nhập.

Xem thêm PEP 420 để biết thông số kỹ thuật của gói không gian tên.

5.3. Tìm kiếm

Để bắt đầu tìm kiếm, Python cần tên fully qualified của mô-đun (hoặc gói, nhưng với mục đích của cuộc thảo luận này, sự khác biệt là không quan trọng) đang được nhập. Tên này có thể đến từ nhiều đối số khác nhau cho câu lệnh import hoặc từ các tham số cho các hàm importlib.import_module() hoặc __import__().

Tên này sẽ được sử dụng trong các giai đoạn khác nhau của tìm kiếm nhập và nó có thể là đường dẫn chấm tới mô-đun con, ví dụ: foo.bar.baz. Trong trường hợp này, trước tiên Python cố gắng nhập foo, sau đó là foo.bar và cuối cùng là foo.bar.baz. Nếu bất kỳ quá trình nhập trung gian nào không thành công, ModuleNotFoundError sẽ được nâng lên.

5.3.1. Bộ đệm mô-đun

Vị trí đầu tiên được kiểm tra trong quá trình tìm kiếm nhập là sys.modules. Ánh xạ này đóng vai trò là bộ nhớ đệm của tất cả các mô-đun đã được nhập trước đó, bao gồm cả các đường dẫn trung gian. Vì vậy, nếu foo.bar.baz đã được nhập trước đó, sys.modules sẽ chứa các mục nhập cho foo, foo.barfoo.bar.baz. Mỗi khóa sẽ có giá trị của đối tượng mô-đun tương ứng.

Trong quá trình nhập, tên mô-đun được tra cứu trong sys.modules và nếu có, giá trị liên quan là mô-đun đáp ứng quá trình nhập và quá trình hoàn tất. Tuy nhiên, nếu giá trị là None thì ModuleNotFoundError sẽ được nâng lên. Nếu tên mô-đun bị thiếu, Python sẽ tiếp tục tìm kiếm mô-đun.

sys.modules có thể ghi được. Việc xóa khóa có thể không hủy mô-đun được liên kết (vì các mô-đun khác có thể chứa các tham chiếu đến nó), nhưng nó sẽ làm mất hiệu lực mục nhập bộ đệm cho mô-đun được đặt tên, khiến Python phải tìm kiếm lại mô-đun được đặt tên trong lần nhập tiếp theo. Khóa cũng có thể được gán cho None, buộc lần nhập mô-đun tiếp theo sẽ tạo ra ModuleNotFoundError.

Tuy nhiên, hãy cẩn thận, vì nếu bạn giữ một tham chiếu đến đối tượng mô-đun, vô hiệu hóa mục nhập bộ đệm của nó trong sys.modules, sau đó nhập lại mô-đun đã đặt tên thì hai đối tượng mô-đun sẽ not giống nhau. Ngược lại, importlib.reload() sẽ sử dụng lại đối tượng mô-đun same và chỉ cần khởi tạo lại nội dung mô-đun bằng cách chạy lại mã của mô-đun.

5.3.2. Trình tìm kiếm và tải

Nếu không tìm thấy mô-đun được đặt tên trong sys.modules thì giao thức nhập của Python sẽ được gọi để tìm và tải mô-đun. Giao thức này bao gồm hai đối tượng khái niệm, findersloaders. Công việc của người tìm kiếm là xác định xem liệu nó có thể tìm thấy mô-đun được đặt tên bằng cách sử dụng bất kỳ chiến lược nào mà nó biết hay không. Các đối tượng triển khai cả hai giao diện này được gọi là importers - chúng tự trả về khi nhận thấy rằng chúng có thể tải mô-đun được yêu cầu.

Python bao gồm một số công cụ tìm kiếm và nhập khẩu mặc định. Cái đầu tiên biết cách xác định vị trí các mô-đun tích hợp và cái thứ hai biết cách xác định vị trí các mô-đun bị đóng băng. Công cụ tìm kiếm mặc định thứ ba tìm kiếm mô-đun trong import path. Zz001zz là danh sách các vị trí có thể đặt tên cho đường dẫn hệ thống tệp hoặc tệp zip. Nó cũng có thể được mở rộng để tìm kiếm bất kỳ tài nguyên nào có thể định vị được, chẳng hạn như những tài nguyên được xác định bằng URL.

Bộ máy nhập khẩu có thể mở rộng nên có thể thêm các công cụ tìm kiếm mới để mở rộng phạm vi và phạm vi tìm kiếm mô-đun.

Trình tìm kiếm không thực sự tải các mô-đun. Nếu họ có thể tìm thấy mô-đun được đặt tên, họ sẽ trả về module spec, một gói thông tin liên quan đến nhập của mô-đun mà sau đó máy nhập sẽ sử dụng khi tải mô-đun.

Các phần sau đây mô tả chi tiết hơn về giao thức dành cho trình tìm kiếm và trình tải, bao gồm cách bạn có thể tạo và đăng ký các giao thức mới để mở rộng cơ chế nhập.

Thay đổi trong phiên bản 3.4: Trong các phiên bản trước của Python, trình tìm kiếm trả về trực tiếp loaders, trong khi bây giờ chúng trả về thông số mô-đun mà trình tải contain. Trình tải vẫn được sử dụng trong quá trình nhập nhưng có ít trách nhiệm hơn.

5.3.3. Nhập móc

Máy móc nhập khẩu được thiết kế để có thể mở rộng; cơ chế chính cho việc này là import hooks. Có hai loại móc nhập: meta hooksimport path hooks.

Móc meta được gọi khi bắt đầu quá trình nhập, trước khi bất kỳ quá trình nhập nào khác xảy ra, ngoại trừ việc tra cứu bộ nhớ đệm sys.modules. Điều này cho phép các móc meta ghi đè quá trình xử lý sys.path, các mô-đun bị đóng băng hoặc thậm chí các mô-đun tích hợp. Các móc meta được đăng ký bằng cách thêm các đối tượng tìm kiếm mới vào sys.meta_path, như được mô tả bên dưới.

Các móc đường dẫn nhập được gọi là một phần của quá trình xử lý sys.path (hoặc package.__path__), tại thời điểm gặp mục đường dẫn liên quan của chúng. Các móc đường dẫn nhập được đăng ký bằng cách thêm các lệnh gọi mới vào sys.path_hooks như được mô tả bên dưới.

5.3.4. Đường dẫn meta

Khi không tìm thấy mô-đun được đặt tên trong sys.modules, Python sẽ tìm kiếm tiếp theo sys.meta_path, nơi chứa danh sách các đối tượng tìm đường dẫn meta. Những công cụ tìm kiếm này được truy vấn để xem liệu họ có biết cách xử lý mô-đun được đặt tên hay không. Công cụ tìm đường dẫn meta phải triển khai một phương thức có tên find_spec(), phương thức này nhận ba đối số: tên, đường dẫn nhập và (tùy chọn) mô-đun đích. Công cụ tìm đường dẫn meta có thể sử dụng bất kỳ chiến lược nào mà nó muốn để xác định xem liệu nó có thể xử lý được mô-đun được đặt tên hay không.

Nếu trình tìm đường dẫn meta biết cách xử lý mô-đun được đặt tên, nó sẽ trả về một đối tượng đặc tả. Nếu nó không thể xử lý mô-đun được đặt tên, nó sẽ trả về None. Nếu quá trình xử lý sys.meta_path đến cuối danh sách mà không trả về thông số kỹ thuật thì ModuleNotFoundError sẽ được nâng lên. Bất kỳ trường hợp ngoại lệ nào khác được nêu ra đều được phổ biến đơn giản, hủy bỏ quá trình nhập.

Phương thức find_spec() của trình tìm đường dẫn meta được gọi với hai hoặc ba đối số. Đầu tiên là tên đầy đủ của mô-đun đang được nhập, ví dụ foo.bar.baz. Đối số thứ hai là các mục đường dẫn được sử dụng cho tìm kiếm mô-đun. Đối với các mô-đun cấp cao nhất, đối số thứ hai là None, nhưng đối với các mô-đun con hoặc gói con, đối số thứ hai là giá trị của thuộc tính __path__ của gói cha. Nếu không thể truy cập thuộc tính __path__ thích hợp, ModuleNotFoundError sẽ được nâng lên. Đối số thứ ba là một đối tượng mô-đun hiện có sẽ là mục tiêu tải sau này. Hệ thống nhập chỉ chuyển mô-đun đích trong quá trình tải lại.

Đường dẫn meta có thể được duyệt nhiều lần cho một yêu cầu nhập. Ví dụ: giả sử không có mô-đun nào liên quan đã được lưu vào bộ nhớ đệm, việc nhập foo.bar.baz trước tiên sẽ thực hiện nhập cấp cao nhất, gọi mpf.find_spec("foo", None, None) trên mỗi công cụ tìm đường dẫn meta (mpf). Sau khi foo được nhập, foo.bar sẽ được nhập bằng cách duyệt qua đường dẫn meta lần thứ hai, gọi mpf.find_spec("foo.bar", foo.__path__, None). Khi foo.bar đã được nhập, quá trình truyền tải cuối cùng sẽ gọi mpf.find_spec("foo.bar.baz", foo.bar.__path__, None).

Một số công cụ tìm đường dẫn meta chỉ hỗ trợ nhập cấp cao nhất. Những nhà nhập khẩu này sẽ luôn trả về None khi bất kỳ thứ gì khác ngoài None được chuyển làm đối số thứ hai.

Zz000zz mặc định của Python có ba công cụ tìm đường dẫn meta, một công cụ biết cách nhập mô-đun tích hợp, một công cụ biết cách nhập mô-đun cố định và một công cụ biết cách nhập mô-đun từ import path (tức là path based finder).

Thay đổi trong phiên bản 3.4: Phương pháp find_spec() của công cụ tìm đường dẫn meta đã thay thế find_module(), hiện không được dùng nữa. Mặc dù nó sẽ tiếp tục hoạt động mà không có thay đổi, nhưng máy nhập sẽ chỉ thử nếu công cụ tìm không triển khai find_spec().

Thay đổi trong phiên bản 3.10: Việc sử dụng find_module() bởi hệ thống nhập hiện làm tăng ImportWarning.

Thay đổi trong phiên bản 3.12: find_module() đã bị xóa. Thay vào đó hãy sử dụng find_spec().

5.4. Đang tải

Nếu và khi tìm thấy thông số mô-đun, máy nhập sẽ sử dụng thông số đó (và bộ tải chứa trong đó) khi tải mô-đun. Đây là kết quả gần đúng về những gì xảy ra trong phần tải của quá trình nhập:

-đun = Không 
nếu spec.loader không phải  None  hasattr(spec.loader, 'create_module'):
    # It được giả định 'exec_module' cũng sẽ được xác định trên trình tải.
    module = spec.loader.create_module(spec)
nếu -đun  Không :
    -đun = Loại -đun (spec.name)
Các thuộc tính -đun liên quan đến nhập khẩu # The được đặt ở đây:
_init_module_attrs(thông số kỹ thuật, -đun)

nếu spec.loader  Không :
    # unsupported
    tăng lỗi nhập khẩu
nếu spec.origin  Không  spec.submodule_search_locations không phải  Không:
    gói # namespace
    sys.modules[spec.name] = -đun
Elif không phải hasattr(spec.loader, 'exec_module'):
    module = spec.loader.load_module(spec.name)
khác:
    sys.modules[spec.name] = -đun
    thử:
        spec.loader.exec_module(-đun)
    ngoại trừ BaseException:
        thử:
            del sys.modules[spec.name]
        ngoại trừ KeyError:
            vượt qua
        nâng cao
trả về sys.modules[spec.name]

Lưu ý các chi tiết sau:

  • Nếu có một đối tượng mô-đun hiện có có tên đã cho trong sys.modules, quá trình nhập sẽ trả về nó.

  • Mô-đun sẽ tồn tại trong sys.modules trước khi trình tải thực thi mã mô-đun. Điều này rất quan trọng vì mã mô-đun có thể tự nhập (trực tiếp hoặc gián tiếp); việc thêm nó vào sys.modules trước sẽ ngăn chặn đệ quy không giới hạn trong trường hợp xấu nhất và tải nhiều lần trong trường hợp tốt nhất.

  • Nếu tải không thành công, mô-đun bị lỗi -- và chỉ mô-đun bị lỗi -- sẽ bị xóa khỏi sys.modules. Bất kỳ mô-đun nào đã có trong bộ đệm sys.modules và bất kỳ mô-đun nào được tải thành công dưới dạng tác dụng phụ đều phải nằm trong bộ đệm. Điều này trái ngược với việc tải lại trong đó ngay cả mô-đun bị lỗi vẫn còn trong sys.modules.

  • Sau khi mô-đun được tạo nhưng trước khi thực thi, máy nhập sẽ đặt các thuộc tính mô-đun liên quan đến nhập ("_init_module_attrs" trong ví dụ mã giả ở trên), như được tóm tắt trong later section.

  • Việc thực thi mô-đun là thời điểm tải quan trọng trong đó không gian tên của mô-đun được điền vào. Việc thực thi được ủy quyền hoàn toàn cho trình tải, trình tải này sẽ quyết định nội dung nào được điền và cách thực hiện.

  • Mô-đun được tạo trong khi tải và chuyển tới exec_module() có thể không phải là mô-đun được trả về khi kết thúc quá trình nhập [2].

Thay đổi trong phiên bản 3.4: Hệ thống nhập khẩu đã đảm nhận trách nhiệm soạn sẵn của người tải. Những điều này trước đây được thực hiện bằng phương pháp importlib.abc.Loader.load_module().

5.4.1. Máy xúc

Trình tải mô-đun cung cấp chức năng quan trọng của việc tải: thực thi mô-đun. Bộ máy nhập gọi phương thức importlib.abc.Loader.exec_module() với một đối số duy nhất, đối tượng mô-đun để thực thi. Mọi giá trị được trả về từ exec_module() đều bị bỏ qua.

Máy xúc phải đáp ứng các yêu cầu sau:

  • Nếu mô-đun là mô-đun Python (trái ngược với mô-đun tích hợp hoặc tiện ích mở rộng được tải động), thì trình tải sẽ thực thi mã của mô-đun trong không gian tên chung của mô-đun (module.__dict__).

  • Nếu trình tải không thể thực thi mô-đun, nó sẽ đưa ra một ImportError, mặc dù bất kỳ ngoại lệ nào khác được đưa ra trong exec_module() sẽ được truyền bá.

Trong nhiều trường hợp, Finder và Loader có thể là cùng một đối tượng; trong những trường hợp như vậy, phương thức find_spec() sẽ chỉ trả về một thông số kỹ thuật với bộ tải được đặt thành self.

Trình tải mô-đun có thể chọn tham gia tạo đối tượng mô-đun trong khi tải bằng cách triển khai phương thức create_module(). Nó nhận một đối số, thông số mô-đun và trả về đối tượng mô-đun mới để sử dụng trong quá trình tải. create_module() không cần đặt bất kỳ thuộc tính nào trên đối tượng mô-đun. Nếu phương thức trả về None, máy nhập sẽ tự tạo mô-đun mới.

Added in version 3.4: Phương pháp create_module() của bộ tải.

Thay đổi trong phiên bản 3.4: Phương pháp load_module() đã được thay thế bằng exec_module() và máy móc nhập khẩu đảm nhận tất cả trách nhiệm tải mẫu.

Để tương thích với các trình tải hiện có, máy nhập sẽ sử dụng phương thức load_module() của các trình tải nếu nó tồn tại và trình tải cũng không triển khai exec_module(). Tuy nhiên, load_module() không được dùng nữa và thay vào đó, trình tải nên triển khai exec_module().

Phương thức load_module() phải triển khai tất cả chức năng tải bản soạn sẵn được mô tả ở trên ngoài việc thực thi mô-đun. Tất cả các ràng buộc tương tự đều được áp dụng, với một số giải thích rõ ràng hơn:

  • Nếu có một đối tượng mô-đun hiện có với tên đã cho trong sys.modules thì trình tải phải sử dụng mô-đun hiện có đó. (Nếu không, importlib.reload() sẽ không hoạt động chính xác.) Nếu mô-đun được đặt tên không tồn tại trong sys.modules, trình tải phải tạo một đối tượng mô-đun mới và thêm nó vào sys.modules.

  • Mô-đun must tồn tại trong sys.modules trước khi trình tải thực thi mã mô-đun, để ngăn chặn đệ quy không giới hạn hoặc tải nhiều lần.

  • Nếu tải không thành công, trình tải phải xóa mọi mô-đun mà nó đã chèn vào sys.modules, nhưng nó phải xóa only (các) mô-đun bị lỗi và chỉ khi chính trình tải đã tải (các) mô-đun đó một cách rõ ràng.

Thay đổi trong phiên bản 3.5: Một DeprecationWarning được nâng lên khi exec_module() được xác định nhưng create_module() thì không.

Thay đổi trong phiên bản 3.6: Một ImportError được nâng lên khi exec_module() được xác định nhưng create_module() thì không.

Thay đổi trong phiên bản 3.10: Sử dụng load_module() sẽ tăng ImportWarning.

5.4.2. mô-đun con

Khi một mô-đun con được tải bằng bất kỳ cơ chế nào (ví dụ: API importlib, câu lệnh import hoặc import-from hoặc __import__() tích hợp), một liên kết được đặt trong không gian tên của mô-đun mẹ với đối tượng mô-đun con. Ví dụ: nếu gói spam có mô-đun con foo, sau khi nhập spam.foo, spam sẽ có thuộc tính foo được liên kết với mô-đun con. Giả sử bạn có cấu trúc thư mục sau

thư rác/
    __init__.py
    foo.py

spam/__init__.py có dòng sau trong đó

từ .foo nhập Foo

sau đó thực hiện các ràng buộc đặt tên sau đây cho fooFoo trong mô-đun spam:

>>> nhập thư rác
>>> thư rác.foo
<mô-đun 'spam.foo' từ '/tmp/imports/spam/foo.py'>
>>> thư rác.Foo
<lớp 'spam.foo.Foo'>

Với các quy tắc ràng buộc tên quen thuộc của Python, điều này có vẻ đáng ngạc nhiên, nhưng thực ra đây là một tính năng cơ bản của hệ thống nhập. Điều bất biến là nếu bạn có sys.modules['spam']sys.modules['spam.foo'] (như bạn làm sau lần nhập ở trên), cái sau phải xuất hiện dưới dạng thuộc tính foo của cái trước.

5.4.3. Thông số mô-đun

Bộ máy nhập khẩu sử dụng nhiều thông tin khác nhau về từng mô-đun trong quá trình nhập, đặc biệt là trước khi tải. Hầu hết các thông tin là chung cho tất cả các mô-đun. Mục đích của thông số kỹ thuật của mô-đun là gói gọn thông tin liên quan đến nhập này trên cơ sở từng mô-đun.

Việc sử dụng thông số kỹ thuật trong quá trình nhập cho phép chuyển trạng thái giữa các thành phần hệ thống nhập, ví dụ: giữa công cụ tìm tạo thông số mô-đun và trình tải thực thi nó. Quan trọng nhất, nó cho phép máy móc nhập khẩu thực hiện các hoạt động tải mẫu sẵn, trong khi không có thông số kỹ thuật mô-đun thì bộ tải có trách nhiệm đó.

Thông số kỹ thuật của mô-đun được hiển thị dưới dạng module.__spec__. Việc đặt __spec__ một cách thích hợp cũng áp dụng như nhau cho modules initialized during interpreter startup. Một ngoại lệ là __main__, trong đó __spec__set to None in some cases.

Xem ModuleSpec để biết chi tiết về nội dung của thông số mô-đun.

Added in version 3.4.

5.4.4. __path__ thuộc tính trên mô-đun

Thuộc tính __path__ phải là một chuỗi sequence (có thể trống) liệt kê các vị trí sẽ tìm thấy các mô-đun con của gói. Theo định nghĩa, nếu một mô-đun có thuộc tính __path__ thì đó là package.

Thuộc tính __path__ của gói được sử dụng trong quá trình nhập các gói con của nó. Trong cơ chế nhập, nó hoạt động giống như sys.path, tức là cung cấp danh sách các vị trí để tìm kiếm mô-đun trong quá trình nhập. Tuy nhiên, __path__ thường bị hạn chế hơn nhiều so với sys.path.

Các quy tắc tương tự được sử dụng cho sys.path cũng áp dụng cho __path__ của gói. sys.path_hooks (được mô tả bên dưới) được tham khảo khi duyệt qua __path__ của gói.

Tệp __init__.py của gói có thể đặt hoặc thay đổi thuộc tính __path__ của gói và đây thường là cách các gói không gian tên được triển khai trước PEP 420. Với việc áp dụng PEP 420, các gói không gian tên không còn cần phải cung cấp các tệp __init__.py chỉ chứa mã thao tác __path__ nữa; máy nhập sẽ tự động đặt __path__ một cách chính xác cho gói không gian tên.

5.4.5. đại diện mô-đun

Theo mặc định, tất cả các mô-đun đều có một repr có thể sử dụng được, tuy nhiên tùy thuộc vào các thuộc tính được đặt ở trên và trong thông số kỹ thuật của mô-đun, bạn có thể kiểm soát rõ ràng hơn repr của các đối tượng mô-đun.

Nếu mô-đun có thông số kỹ thuật (__spec__), máy nhập sẽ cố gắng tạo ra một thông số kỹ thuật từ nó. Nếu điều đó không thành công hoặc không có thông số kỹ thuật, hệ thống nhập sẽ tạo một bản đại diện mặc định bằng cách sử dụng bất kỳ thông tin nào có sẵn trên mô-đun. Nó sẽ cố gắng sử dụng module.__name__, module.__file__module.__loader__ làm đầu vào cho repr, với các giá trị mặc định cho bất kỳ thông tin nào bị thiếu.

Dưới đây là các quy tắc chính xác được sử dụng:

  • Nếu mô-đun có thuộc tính __spec__ thì thông tin trong thông số kỹ thuật sẽ được sử dụng để tạo ra Repr. Các thuộc tính "name", "loader", "origin" và "has_location" được tham khảo.

  • Nếu mô-đun có thuộc tính __file__ thì thuộc tính này được sử dụng như một phần của mô-đun.

  • Nếu mô-đun không có __file__ nhưng có __loader__ không phải là None thì Repr của trình tải sẽ được sử dụng như một phần của Repr của mô-đun.

  • Nếu không, chỉ cần sử dụng __name__ của mô-đun trong tập tin repr.

Thay đổi trong phiên bản 3.12: Việc sử dụng module_repr(), không còn được dùng nữa kể từ Python 3.4, đã bị xóa trong Python 3.12 và không còn được gọi trong quá trình phân giải đại diện của mô-đun.

5.4.6. Vô hiệu hóa mã byte được lưu trong bộ nhớ đệm

Trước khi Python tải mã byte được lưu trong bộ nhớ đệm từ tệp .pyc, nó sẽ kiểm tra xem bộ đệm có được cập nhật với tệp .py nguồn hay không. Theo mặc định, Python thực hiện điều này bằng cách lưu trữ dấu thời gian và kích thước được sửa đổi lần cuối của nguồn trong tệp bộ đệm khi ghi nó. Khi chạy, hệ thống nhập sẽ xác thực tệp bộ đệm bằng cách kiểm tra siêu dữ liệu được lưu trữ trong tệp bộ đệm so với siêu dữ liệu của nguồn.

Python cũng hỗ trợ các tệp bộ đệm "dựa trên hàm băm", lưu trữ hàm băm của nội dung tệp nguồn thay vì siêu dữ liệu của nó. Có hai biến thể của tệp .pyc dựa trên hàm băm: đã chọn và không được chọn. Đối với các tệp .pyc dựa trên hàm băm đã được kiểm tra, Python xác thực tệp bộ đệm bằng cách băm tệp nguồn và so sánh hàm băm kết quả với hàm băm trong tệp bộ đệm. Nếu tệp bộ đệm dựa trên hàm băm đã kiểm tra được phát hiện là không hợp lệ, Python sẽ tạo lại nó và ghi một tệp bộ đệm dựa trên hàm băm đã kiểm tra mới. Đối với các tệp .pyc dựa trên hàm băm không được kiểm tra, Python chỉ cần giả sử tệp bộ đệm là hợp lệ nếu nó tồn tại. Hành vi xác thực tệp .pyc dựa trên hàm băm có thể bị ghi đè bằng cờ --check-hash-based-pycs.

Thay đổi trong phiên bản 3.7: Đã thêm tệp .pyc dựa trên hàm băm. Trước đây, Python chỉ hỗ trợ việc vô hiệu hóa bộ đệm mã byte dựa trên dấu thời gian.

5.5. Trình tìm kiếm dựa trên đường dẫn

Như đã đề cập trước đây, Python đi kèm với một số công cụ tìm đường dẫn meta mặc định. Một trong số đó, được gọi là path based finder (PathFinder), tìm kiếm import path, chứa danh sách path entries. Mỗi mục nhập đường dẫn đặt tên cho một vị trí để tìm kiếm mô-đun.

Bản thân công cụ tìm kiếm dựa trên đường dẫn không biết cách nhập bất cứ thứ gì. Thay vào đó, nó đi qua các mục nhập đường dẫn riêng lẻ, liên kết từng mục trong số chúng với một công cụ tìm mục nhập đường dẫn biết cách xử lý loại đường dẫn cụ thể đó.

Bộ công cụ tìm mục nhập đường dẫn mặc định triển khai tất cả ngữ nghĩa để tìm mô-đun trên hệ thống tệp, xử lý các loại tệp đặc biệt như mã nguồn Python (tệp .py), mã byte Python (tệp .pyc) và thư viện dùng chung (ví dụ: tệp .so). Khi được mô-đun zipimport hỗ trợ trong thư viện tiêu chuẩn, trình tìm mục nhập đường dẫn mặc định cũng xử lý việc tải tất cả các loại tệp này (trừ thư viện dùng chung) từ tệp zip.

Các mục nhập đường dẫn không cần phải giới hạn ở các vị trí hệ thống tệp. Họ có thể tham chiếu đến URL, truy vấn cơ sở dữ liệu hoặc bất kỳ vị trí nào khác có thể được chỉ định dưới dạng chuỗi.

Công cụ tìm kiếm dựa trên đường dẫn cung cấp các móc nối và giao thức bổ sung để bạn có thể mở rộng và tùy chỉnh các loại mục nhập đường dẫn có thể tìm kiếm. Ví dụ: nếu bạn muốn hỗ trợ các mục nhập đường dẫn dưới dạng URL mạng, bạn có thể viết một hook triển khai ngữ nghĩa HTTP để tìm các mô-đun trên web. Hook này (có thể gọi được) sẽ trả về một path entry finder hỗ trợ giao thức được mô tả bên dưới, sau đó được sử dụng để tải trình tải cho mô-đun từ web.

Lời cảnh báo: phần này và phần trước đều sử dụng thuật ngữ finder, phân biệt giữa chúng bằng cách sử dụng thuật ngữ meta path finderpath entry finder. Hai loại công cụ tìm này rất giống nhau, hỗ trợ các giao thức tương tự và hoạt động theo cách tương tự trong quá trình nhập, nhưng điều quan trọng cần lưu ý là chúng có sự khác biệt nhỏ. Cụ thể, các công cụ tìm đường dẫn meta hoạt động ở đầu quá trình nhập, như được khóa khi truyền tải sys.meta_path.

Ngược lại, các công cụ tìm mục nhập đường dẫn theo một nghĩa nào đó là một chi tiết triển khai của công cụ tìm kiếm dựa trên đường dẫn và trên thực tế, nếu công cụ tìm kiếm dựa trên đường dẫn bị xóa khỏi sys.meta_path thì sẽ không có ngữ nghĩa nào của công cụ tìm mục nhập đường dẫn được gọi.

5.5.1. Công cụ tìm mục nhập đường dẫn

Zz000zz chịu trách nhiệm tìm và tải các mô-đun và gói Python có vị trí được chỉ định bằng chuỗi path entry. Hầu hết các mục nhập đường dẫn đều đặt tên cho các vị trí trong hệ thống tệp, nhưng chúng không cần giới hạn ở điều này.

Là một công cụ tìm đường dẫn meta, path based finder triển khai giao thức find_spec() được mô tả trước đây, tuy nhiên, nó hiển thị các móc bổ sung có thể được sử dụng để tùy chỉnh cách tìm thấy và tải các mô-đun từ import path.

Ba biến được sử dụng bởi path based finder, sys.path, sys.path_hookssys.path_importer_cache. Thuộc tính __path__ trên các đối tượng gói cũng được sử dụng. Những điều này cung cấp những cách bổ sung mà máy móc nhập khẩu có thể được tùy chỉnh.

sys.path chứa danh sách các chuỗi cung cấp vị trí tìm kiếm cho các mô-đun và gói. Nó được khởi tạo từ biến môi trường PYTHONPATH và nhiều giá trị mặc định dành riêng cho cài đặt và triển khai khác. Các mục trong sys.path có thể đặt tên cho các thư mục trên hệ thống tệp, tệp zip và các "vị trí" tiềm năng khác (xem mô-đun site) cần được tìm kiếm cho các mô-đun, chẳng hạn như URL hoặc truy vấn cơ sở dữ liệu. Chỉ có các chuỗi trên sys.path; tất cả các loại dữ liệu khác đều bị bỏ qua.

path based findermeta path finder, do đó, máy nhập bắt đầu tìm kiếm import path bằng cách gọi phương thức find_spec() của công cụ tìm kiếm dựa trên đường dẫn như được mô tả trước đây. Khi đối số path cho find_spec() được đưa ra, nó sẽ là danh sách các đường dẫn chuỗi để duyệt qua - thường là thuộc tính __path__ của gói để nhập trong gói đó. Nếu đối số pathNone, điều này cho biết quá trình nhập cấp cao nhất và sys.path được sử dụng.

Trình tìm kiếm dựa trên đường dẫn lặp lại mọi mục nhập trong đường dẫn tìm kiếm và đối với mỗi mục nhập này, hãy tìm một path entry finder (PathEntryFinder) thích hợp cho mục nhập đường dẫn. Bởi vì đây có thể là một hoạt động tốn kém (ví dụ: có thể có chi phí cuộc gọi stat() cho tìm kiếm này), công cụ tìm kiếm dựa trên đường dẫn sẽ duy trì các mục nhập đường dẫn ánh xạ bộ đệm tới các công cụ tìm mục nhập đường dẫn. Bộ đệm này được duy trì trong sys.path_importer_cache (mặc dù tên như vậy, bộ đệm này thực sự lưu trữ các đối tượng tìm kiếm thay vì bị giới hạn ở các đối tượng importer). Bằng cách này, việc tìm kiếm tốn kém cho path entry finder của một vị trí path entry cụ thể chỉ cần được thực hiện một lần. Mã người dùng có thể tự do xóa các mục nhập trong bộ đệm khỏi sys.path_importer_cache, buộc công cụ tìm kiếm dựa trên đường dẫn phải thực hiện lại tìm kiếm mục nhập đường dẫn.

Nếu mục nhập đường dẫn không có trong bộ đệm, trình tìm kiếm dựa trên đường dẫn sẽ lặp lại mọi lệnh gọi trong sys.path_hooks. Mỗi path entry hooks trong danh sách này được gọi bằng một đối số duy nhất, mục nhập đường dẫn cần tìm kiếm. Lệnh gọi này có thể trả về một path entry finder có thể xử lý mục nhập đường dẫn hoặc nó có thể tăng ImportError. Một ImportError được công cụ tìm kiếm dựa trên đường dẫn sử dụng để báo hiệu rằng hook không thể tìm thấy path entry finder cho path entry đó. Ngoại lệ bị bỏ qua và quá trình lặp import path tiếp tục. Móc phải mong đợi một đối tượng chuỗi hoặc byte; việc mã hóa các đối tượng byte tùy thuộc vào hook (ví dụ: nó có thể là mã hóa hệ thống tệp, UTF-8 hoặc thứ gì khác) và nếu hook không thể giải mã đối số, thì nó sẽ tăng ImportError.

Nếu quá trình lặp sys.path_hooks kết thúc mà không có path entry finder nào được trả về thì phương thức find_spec() của công cụ tìm kiếm dựa trên đường dẫn sẽ lưu trữ None trong sys.path_importer_cache (để cho biết rằng không có công cụ tìm kiếm nào cho mục nhập đường dẫn này) và trả về None, cho biết rằng meta path finder này không thể tìm thấy mô-đun.

Nếu path entry finder is được trả về bởi một trong các lệnh gọi path entry hook trên sys.path_hooks thì giao thức sau sẽ được sử dụng để yêu cầu công cụ tìm kiếm thông số mô-đun, sau đó được sử dụng khi tải mô-đun.

Thư mục làm việc hiện tại -- được biểu thị bằng một chuỗi trống -- được xử lý hơi khác so với các mục nhập khác trên sys.path. Đầu tiên, nếu không thể xác định được thư mục làm việc hiện tại hoặc không tồn tại thì không có giá trị nào được lưu trong sys.path_importer_cache. Thứ hai, giá trị cho thư mục làm việc hiện tại được tra cứu mới cho mỗi lần tra cứu mô-đun. Thứ ba, đường dẫn được sử dụng cho sys.path_importer_cache và được importlib.machinery.PathFinder.find_spec() trả về sẽ là thư mục làm việc hiện tại thực tế chứ không phải chuỗi trống.

5.5.2. Giao thức tìm đường dẫn

Để hỗ trợ nhập mô-đun và gói khởi tạo cũng như đóng góp các phần cho gói không gian tên, trình tìm mục nhập đường dẫn phải triển khai phương thức find_spec().

find_spec() nhận hai đối số: tên đủ điều kiện của mô-đun đang được nhập và mô-đun đích (tùy chọn). find_spec() trả về thông số kỹ thuật được điền đầy đủ cho mô-đun. Thông số kỹ thuật này sẽ luôn được đặt "trình tải" (với một ngoại lệ).

Để cho bộ máy nhập biết rằng thông số kỹ thuật đại diện cho vùng tên portion, trình tìm mục nhập đường dẫn sẽ đặt submodule_search_locations vào danh sách chứa phần đó.

Thay đổi trong phiên bản 3.4: find_spec() đã thay thế find_loader()find_module(), cả hai đều hiện không được dùng nữa nhưng sẽ được sử dụng nếu find_spec() không được xác định.

Các công cụ tìm mục nhập đường dẫn cũ hơn có thể triển khai một trong hai phương pháp không được dùng nữa này thay vì find_spec(). Các phương pháp vẫn được tôn trọng vì tính tương thích ngược. Tuy nhiên, nếu find_spec() được triển khai trên công cụ tìm mục nhập đường dẫn thì các phương thức cũ sẽ bị bỏ qua.

find_loader() nhận một đối số, tên đầy đủ của mô-đun đang được nhập. find_loader() trả về 2 bộ dữ liệu trong đó mục đầu tiên là trình tải và mục thứ hai là không gian tên portion.

Để có khả năng tương thích ngược với các triển khai khác của giao thức nhập, nhiều công cụ tìm mục nhập đường dẫn cũng hỗ trợ phương thức find_module() truyền thống tương tự mà các công cụ tìm đường dẫn meta hỗ trợ. Tuy nhiên, các phương thức find_module() của công cụ tìm mục nhập đường dẫn không bao giờ được gọi với đối số path (chúng phải ghi lại thông tin đường dẫn thích hợp từ lệnh gọi ban đầu đến hook hook).

Phương thức find_module() trên các công cụ tìm mục nhập đường dẫn không được dùng nữa vì nó không cho phép công cụ tìm mục nhập đường dẫn đóng góp các phần cho các gói không gian tên. Nếu cả find_loader()find_module() đều tồn tại trên công cụ tìm mục nhập đường dẫn, hệ thống nhập sẽ luôn gọi find_loader() ưu tiên hơn find_module().

Thay đổi trong phiên bản 3.10: Các lệnh gọi tới find_module()find_loader() bằng hệ thống nhập sẽ tăng ImportWarning.

Thay đổi trong phiên bản 3.12: find_module()find_loader() đã bị xóa.

5.6. Thay thế hệ thống nhập khẩu tiêu chuẩn

Cơ chế đáng tin cậy nhất để thay thế toàn bộ hệ thống nhập là xóa nội dung mặc định của sys.meta_path, thay thế chúng hoàn toàn bằng móc đường dẫn meta tùy chỉnh.

Nếu có thể chấp nhận chỉ thay đổi hành vi của câu lệnh nhập mà không ảnh hưởng đến các API khác truy cập vào hệ thống nhập thì việc thay thế hàm __import__() dựng sẵn có thể là đủ.

Để ngăn chặn có chọn lọc việc nhập sớm một số mô-đun từ một hook trên đường dẫn meta (thay vì vô hiệu hóa hoàn toàn hệ thống nhập tiêu chuẩn), việc tăng ModuleNotFoundError trực tiếp từ find_spec() thay vì trả về None là đủ. Cái sau chỉ ra rằng việc tìm kiếm đường dẫn meta sẽ tiếp tục, trong khi việc đưa ra một ngoại lệ sẽ chấm dứt nó ngay lập tức.

5.7. Nhập khẩu tương đối gói

Nhập khẩu tương đối sử dụng dấu chấm ở đầu. Một dấu chấm ở đầu biểu thị quá trình nhập tương đối, bắt đầu từ gói hiện tại. Hai hoặc nhiều dấu chấm ở đầu biểu thị mức nhập tương đối đối với (các) gói gốc của gói hiện tại, một cấp trên mỗi dấu chấm sau gói đầu tiên. Ví dụ: đưa ra cách bố trí gói sau

gói/
    __init__.py
    gói con1/
        __init__.py
        -đunX.py
        -đunY.py
    gói con2/
        __init__.py
        moduleZ.py
    moduleA.py

Trong subpackage1/moduleX.py hoặc subpackage1/__init__.py, các mục sau đây là các mục nhập tương đối hợp lệ:

từ .moduleY nhập thư rác
từ .moduleY nhập thư rác dưới dạng ham
từ . nhập -đun Y
từ -đun nhập ..subpackage1Y
từ ..subpackage2.moduleZ nhập trứng
từ ..moduleA nhập foo

Nhập tuyệt đối có thể sử dụng cú pháp import <> hoặc from <> import <>, nhưng nhập tương đối chỉ có thể sử dụng dạng thứ hai; lý do cho điều này là:

nhập XXX.YYY.ZZZ

sẽ hiển thị XXX.YYY.ZZZ dưới dạng biểu thức có thể sử dụng được, nhưng .moduleY không phải là biểu thức hợp lệ.

5.8. Những cân nhắc đặc biệt cho __main__

Mô-đun __main__ là trường hợp đặc biệt liên quan đến hệ thống nhập của Python. Như elsewhere đã lưu ý, mô-đun __main__ được khởi tạo trực tiếp khi khởi động trình thông dịch, giống như sysbuiltins. Tuy nhiên, không giống như hai cái đó, nó không đủ tiêu chuẩn để trở thành một mô-đun tích hợp. Điều này là do cách khởi tạo __main__ phụ thuộc vào cờ và các tùy chọn khác mà trình thông dịch được gọi.

5.8.1. __chính__.__spec__

Tùy thuộc vào cách __main__ được khởi tạo, __main__.__spec__ được đặt phù hợp hoặc thành None.

Khi Python được khởi động với tùy chọn -m, __spec__ được đặt thành thông số mô-đun của mô-đun hoặc gói tương ứng. __spec__ cũng được điền khi mô-đun __main__ được tải như một phần của việc thực thi một thư mục, tệp zip hoặc mục nhập sys.path khác.

Trong the remaining cases __main__.__spec__ được đặt thành None, vì mã được sử dụng để điền __main__ không tương ứng trực tiếp với mô-đun có thể nhập:

  • lời nhắc tương tác

  • tùy chọn -c

  • chạy từ stdin

  • chạy trực tiếp từ tệp nguồn hoặc mã byte

Lưu ý rằng __main__.__spec__ luôn là None trong trường hợp cuối cùng, thay vào đó, even if tệp có thể được nhập trực tiếp dưới dạng mô-đun. Sử dụng khóa chuyển -m nếu muốn có siêu dữ liệu mô-đun hợp lệ trong __main__.

Cũng lưu ý rằng ngay cả khi __main__ tương ứng với một mô-đun có thể nhập và __main__.__spec__ được đặt tương ứng, chúng vẫn được coi là mô-đun distinct. Điều này là do thực tế là các khối được bảo vệ bởi kiểm tra if __name__ == "__main__": chỉ thực thi khi mô-đun được sử dụng để điền vào không gian tên __main__ chứ không phải trong quá trình nhập thông thường.

5.9. Tài liệu tham khảo

Cơ chế nhập khẩu đã phát triển đáng kể kể từ những ngày đầu của Python. Zz000zz ban đầu vẫn có sẵn để đọc, mặc dù một số chi tiết đã thay đổi kể từ khi viết tài liệu đó.

Thông số kỹ thuật ban đầu cho sys.meta_pathPEP 302, với phần mở rộng tiếp theo là PEP 420.

PEP 420 đã giới thiệu namespace packages cho Python 3.3. PEP 420 cũng giới thiệu giao thức find_loader() thay thế cho find_module().

PEP 366 mô tả việc bổ sung thuộc tính __package__ để nhập tương đối rõ ràng trong các mô-đun chính.

PEP 328 đã giới thiệu tính năng nhập tương đối tuyệt đối và rõ ràng, đồng thời ban đầu đề xuất __name__ cho ngữ nghĩa, PEP 366 cuối cùng sẽ chỉ định cho __package__.

PEP 338 định nghĩa các mô-đun thực thi dưới dạng tập lệnh.

PEP 451 bổ sung tính năng đóng gói trạng thái nhập trên mỗi mô-đun trong các đối tượng thông số kỹ thuật. Nó cũng chuyển hầu hết các trách nhiệm soạn sẵn của người bốc hàng trở lại máy móc nhập khẩu. Những thay đổi này cho phép ngừng sử dụng một số API trong hệ thống nhập, đồng thời bổ sung các phương thức mới cho trình tìm kiếm và trình tải.

Chú thích cuối trang