Những gì từ __future__ nhập perfect_import thực sự làm gì?


163

Tôi đã trả lời một câu hỏi liên quan đến nhập khẩu tuyệt đối trong Python, điều mà tôi nghĩ rằng tôi đã hiểu dựa trên việc đọc thay đổi Python 2.5PEP kèm theo . Tuy nhiên, khi cài đặt Python 2.5 và cố gắng tạo một ví dụ về việc sử dụng đúng cách from __future__ import absolute_import, tôi nhận ra mọi thứ không quá rõ ràng.

Ngay từ thay đổi được liên kết ở trên, tuyên bố này đã tóm tắt chính xác sự hiểu biết của tôi về thay đổi nhập khẩu tuyệt đối:

Giả sử bạn có một thư mục gói như thế này:

pkg/
pkg/__init__.py
pkg/main.py
pkg/string.py

Điều này định nghĩa một gói có tên pkgchứa pkg.mainvà các pkg.stringmô hình con.

Hãy xem xét mã trong mô-đun main.txt. Điều gì xảy ra nếu nó thực hiện tuyên bố import string? Trong Python 2.4 trở về trước, trước tiên, nó sẽ tìm trong thư mục của gói để thực hiện nhập tương đối, tìm pkg / string.py, nhập nội dung của tệp đó dưới dạng pkg.stringmô-đun và mô-đun đó được liên kết với tên "string"trong pkg.mainkhông gian tên của mô-đun.

Vì vậy, tôi đã tạo cấu trúc thư mục chính xác này:

$ ls -R
.:
pkg/

./pkg:
__init__.py  main.py  string.py

__init__.pystring.pytrống rỗng main.pychứa mã sau đây:

import string
print string.ascii_uppercase

Như mong đợi, việc chạy này với Python 2.5 không thành công với AttributeError:

$ python2.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

Tuy nhiên, xa hơn trong 2,5 thay đổi, chúng tôi tìm thấy điều này (nhấn mạnh thêm):

Trong Python 2.5, bạn có thể chuyển importhành vi của mình sang nhập tuyệt đối bằng lệnh from __future__ import absolute_import. Hành vi nhập tuyệt đối này sẽ trở thành mặc định trong phiên bản tương lai (có thể là Python 2.7). Khi nhập tuyệt đối là mặc định, import stringsẽ luôn tìm thấy phiên bản của thư viện chuẩn.

Do đó pkg/main2.py, tôi đã tạo , giống hệt main.pynhưng với chỉ thị nhập khẩu trong tương lai. Bây giờ nó trông như thế này:

from __future__ import absolute_import
import string
print string.ascii_uppercase

Chạy cái này với Python 2.5, tuy nhiên ... không thành công với AttributeError:

$ python2.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

Điều này khá mâu thuẫn với tuyên bố import stringsẽ luôn tìm thấy phiên bản std-lib với tính năng nhập tuyệt đối. Hơn nữa, bất chấp cảnh báo rằng nhập tuyệt đối được lên lịch để trở thành hành vi "mặc định mới", tôi gặp vấn đề tương tự khi sử dụng cả Python 2.7, có hoặc không có lệnh __future__:

$ python2.7 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

$ python2.7 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print string.ascii_uppercase
AttributeError: 'module' object has no attribute 'ascii_uppercase'

cũng như Python 3.5, có hoặc không (giả sử printcâu lệnh được thay đổi trong cả hai tệp):

$ python3.5 pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 2, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

$ python3.5 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 3, in <module>
    print(string.ascii_uppercase)
AttributeError: module 'string' has no attribute 'ascii_uppercase'

Tôi đã thử nghiệm các biến thể khác của điều này. Thay vì string.py, tôi đã tạo ra một module rỗng - một thư mục có tên stringchứa duy nhất một sản phẩm nào __init__.py- và thay vì phát hành nhập khẩu từ main.py, tôi có cd'd pkgvà chạy hàng nhập khẩu trực tiếp từ REPL. Cả hai biến thể này (cũng không phải là sự kết hợp của chúng) đã thay đổi kết quả ở trên. Tôi không thể dung hòa điều này với những gì tôi đã đọc về __future__nhập khẩu chỉ thị và tuyệt đối.

Dường như với tôi rằng điều này có thể dễ dàng khám phá bằng cách sau (đây là từ tài liệu Python 2 nhưng câu lệnh này vẫn không thay đổi trong cùng một tài liệu cho Python 3):

sys.path

(...)

Như được khởi tạo khi khởi động chương trình, mục đầu tiên của danh sách này path[0], là thư mục chứa tập lệnh được sử dụng để gọi trình thông dịch Python. Nếu thư mục tập lệnh không khả dụng (ví dụ: nếu trình thông dịch được gọi tương tác hoặc nếu tập lệnh được đọc từ đầu vào tiêu chuẩn), thì đó path[0]là chuỗi trống, điều hướng Python đến các mô-đun tìm kiếm trong thư mục hiện tại trước tiên.

Vậy tôi còn thiếu gì? Tại sao __future__tuyên bố dường như không làm những gì nó nói, và giải quyết mâu thuẫn này giữa hai phần tài liệu này, cũng như giữa hành vi được mô tả và thực tế là gì?


Câu trả lời:


104

Các thay đổi được nói chậm chạp. from __future__ import absolute_importkhông quan tâm đến việc một cái gì đó có phải là một phần của thư viện chuẩn hay import stringkhông và sẽ không luôn cung cấp cho bạn mô-đun thư viện chuẩn với nhập tuyệt đối.

from __future__ import absolute_importcó nghĩa là nếu bạn import string, Python sẽ luôn tìm kiếm một stringmô-đun cấp cao nhất chứ không phải current_package.string. Tuy nhiên, nó không ảnh hưởng đến logic mà Python sử dụng để quyết định tập tin nào là stringmô-đun. Khi bạn làm

python pkg/script.py

pkg/script.pykhông giống như một phần của gói với Python. Theo các thủ tục thông thường, pkgthư mục được thêm vào đường dẫn và tất cả .pycác tệp trong pkgthư mục trông giống như các mô-đun cấp cao nhất. import stringtìm thấy pkg/string.pykhông phải vì nó thực hiện nhập tương đối, mà vì pkg/string.pydường như là mô-đun cấp cao nhất string. Thực tế rằng đây không phải là stringmô-đun thư viện tiêu chuẩn không xuất hiện.

Để chạy tệp như một phần của pkggói, bạn có thể làm

python -m pkg.script

Trong trường hợp này, pkgthư mục sẽ không được thêm vào đường dẫn. Tuy nhiên, thư mục hiện tại sẽ được thêm vào đường dẫn.

Bạn cũng có thể thêm một số mẫu soạn sẵn pkg/script.pyđể làm cho Python coi nó như một phần của pkggói ngay cả khi chạy dưới dạng tệp:

if __name__ == '__main__' and __package__ is None:
    __package__ = 'pkg'

Tuy nhiên, điều này sẽ không ảnh hưởng sys.path. Bạn sẽ cần một số xử lý bổ sung để xóa pkgthư mục khỏi đường dẫn và nếu pkgthư mục mẹ không nằm trên đường dẫn, bạn cũng sẽ cần phải dán thư mục đó trên đường dẫn.


2
OK, ý tôi là, tôi hiểu rồi. Đó chính xác là hành vi mà bài viết của tôi đang ghi lại. Tuy nhiên, đối mặt với điều đó, có hai câu hỏi: (1.) Nếu "điều đó không chính xác", tại sao các tài liệu lại nói một cách cụ thể? và, (2.) Làm thế nào, sau đó, làm bạn import stringnếu bạn vô tình che khuất nó, ít nhất là không có súng trường xuyên qua sys.modules. Đây không phải from __future__ import absolute_importlà những gì được dự định để ngăn chặn? Nó làm gì? (PS, tôi không phải là người hạ cấp.)
Nhà giả kim hai bit

14
Vâng, đó là tôi (downvote cho 'không hữu ích', không phải vì 'sai'). Rõ ràng từ phần dưới cùng, OP hiểu cách thức sys.pathhoạt động và câu hỏi thực tế chưa được giải quyết. Đó là, những gì from __future__ import absolute_importthực sự làm gì?
wim

5
@ Two-BitAlchemist: 1) Thay đổi là từ lỏng lẻo và không quy tắc. 2) Bạn ngừng phủ bóng lên nó. Ngay cả việc bắn xuyên qua sys.modulessẽ không giúp bạn có được stringmô-đun thư viện tiêu chuẩn nếu bạn che giấu nó bằng mô-đun cấp cao nhất của riêng bạn. from __future__ import absolute_importkhông có nghĩa là ngăn các mô-đun cấp cao nhất che giấu các mô-đun cấp cao nhất; nên dừng các mô-đun bên trong gói khỏi các mô-đun cấp cao nhất. Nếu bạn chạy tệp như một phần của pkggói, các tệp bên trong của gói sẽ ngừng hiển thị ở cấp cao nhất.
user2357112 hỗ trợ Monica

@ Two-BitAlchemist: Trả lời sửa đổi. Là phiên bản này hữu ích hơn?
user2357112 hỗ trợ Monica

1
@storen: Giả sử pkglà một gói trên đường dẫn tìm kiếm nhập, nên có python -m pkg.main. -mcần một tên mô-đun, không phải là một đường dẫn tập tin.
user2357112 hỗ trợ Monica

44

Sự khác biệt giữa nhập tuyệt đối và tương đối chỉ phát huy khi bạn nhập mô-đun từ gói và mô-đun đó nhập mô hình con khác từ gói đó. Thấy sự khác biệt:

$ mkdir pkg
$ touch pkg/__init__.py
$ touch pkg/string.py
$ echo 'import string;print(string.ascii_uppercase)' > pkg/main1.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pkg/main1.py", line 1, in <module>
    import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
>>> 
$ echo 'from __future__ import absolute_import;import string;print(string.ascii_uppercase)' > pkg/main2.py
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 

Đặc biệt:

$ python2 pkg/main2.py
Traceback (most recent call last):
  File "pkg/main2.py", line 1, in <module>
    from __future__ import absolute_import;import string;print(string.ascii_uppercase)
AttributeError: 'module' object has no attribute 'ascii_uppercase'
$ python2
Python 2.7.9 (default, Dec 13 2014, 18:02:08) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ
>>> 
$ python2 -m pkg.main2
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Lưu ý rằng python2 pkg/main2.pycó một hành vi khác sau đó khởi chạy python2và sau đó nhập pkg.main2(tương đương với việc sử dụng -mchuyển đổi).

Nếu bạn muốn chạy một mô hình con của gói luôn sử dụng công -mtắc ngăn trình thông dịch để xâu chuỗi sys.pathdanh sách và xử lý chính xác ngữ nghĩa của mô hình con.

Ngoài ra, tôi rất thích sử dụng nhập khẩu tương đối rõ ràng cho các mô hình con gói vì chúng cung cấp nhiều ngữ nghĩa hơn và thông báo lỗi tốt hơn trong trường hợp thất bại.


Vì vậy, về cơ bản nó chỉ hoạt động cho một trường hợp hẹp mà bạn đã tránh được vấn đề "thư mục hiện tại"? Đó dường như là một triển khai yếu hơn nhiều so với mô tả của PEP 328 và thay đổi 2.5. Bạn có tin rằng tài liệu không chính xác?
Nhà giả kim hai bit

@ Two-BitAlchemist Trên thực tế những gì bạn đang làm là "trường hợp hẹp". Bạn chỉ khởi chạy một tệp python duy nhất để được thực thi, nhưng điều này có thể kích hoạt hàng trăm lần nhập. Các mô hình con của gói đơn giản là không nên được thực thi, chỉ vậy thôi.
Bakuriu

Tại sao python2 pkg/main2.pycó một hành vi khác sau đó khởi chạy python2 và sau đó nhập pkg.main2?
repositoryen

1
@storen Đó là vì hành vi với nhập khẩu tương đối thay đổi. Khi bạn khởi chạy pkg/main2.pypython (phiên bản 2) không coi pkglà một gói. Trong khi sử dụng python2 -m pkg.main2hoặc nhập nó, hãy tính đến đó pkglà một gói.
Bakuriu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.