Nhập khẩu tương đối lần thứ một tỷ


716

Tôi đã từng ở đây:

và rất nhiều URL mà tôi đã không sao chép, một số trên SO, một số trên các trang web khác, khi tôi nghĩ rằng tôi có giải pháp nhanh chóng.

Câu hỏi lặp đi lặp lại là đây: Với Windows 7, Python 32.3 32 bit, làm cách nào để giải quyết thông báo "Đã nhập tương đối trong gói không cố định" này? Tôi đã tạo một bản sao chính xác của gói trên pep-0328:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

Việc nhập khẩu đã được thực hiện từ bàn điều khiển.

Tôi đã thực hiện các chức năng có tên là thư rác và trứng trong các mô-đun thích hợp của chúng. Đương nhiên, nó không hoạt động. Câu trả lời rõ ràng là trong URL thứ 4 tôi liệt kê, nhưng tất cả đều là cựu sinh viên đối với tôi. Có phản hồi này trên một trong những URL tôi đã truy cập:

Nhập khẩu tương đối sử dụng thuộc tính tên của mô-đun để xác định vị trí của mô-đun đó trong phân cấp gói. Nếu tên của mô-đun không chứa bất kỳ thông tin gói nào (ví dụ: nó được đặt thành 'chính') thì việc nhập tương đối được giải quyết như thể mô-đun là mô-đun cấp cao nhất, bất kể mô-đun thực sự nằm ở đâu trên hệ thống tệp.

Phản hồi trên có vẻ đầy hứa hẹn, nhưng tất cả đều là chữ tượng hình đối với tôi. Vì vậy, câu hỏi của tôi, làm cách nào để Python không trả lại cho tôi "Đã nhập tương đối trong gói không"? có một câu trả lời liên quan đến -m, được cho là.

Ai đó có thể vui lòng cho tôi biết lý do Python đưa ra thông báo lỗi đó không, ý nghĩa của "không gói" là gì, tại sao và làm thế nào để bạn xác định một "gói" và câu trả lời chính xác đủ dễ hiểu cho một người mẫu giáo .


5
Làm thế nào bạn đang cố gắng sử dụng các tập tin bạn hiển thị? Mã bạn đang chạy là gì?
BrenBarn

Xem python.org/dev/peps/pep-0328 . Tôi đã sử dụng định dạng gói tôi mô tả trong bài viết của mình. Các tập tin init .py trống. moduleY.py có def spam(): pass, moduleA.py có def eggs(): pass. Tôi đã cố thực hiện một vài lệnh "từ .s Something nhập một cái gì đó", nhưng chúng không hoạt động. Một lần nữa, xem pep-0328.

6
Xem câu trả lời của tôi. Bạn vẫn chưa làm rõ hoàn toàn những gì bạn đang làm, nhưng nếu bạn đang cố gắng thực hiện from .something import somethingtrong trình thông dịch tương tác, điều đó sẽ không hiệu quả. Nhập khẩu tương đối chỉ có thể được sử dụng trong các mô-đun, không tương tác.
BrenBarn

105
Thực tế là "hàng tỷ" người - ok 83.136 theo nhận xét này - đang gặp khó khăn với hàng nhập khẩu để tìm ra câu hỏi này; chúng tôi chỉ có thể kết luận rằng nhập khẩu trăn là phản trực giác đối với nhiều người, nếu không phải hầu hết các lập trình viên. Guido, có lẽ bạn nên chấp nhận điều này và yêu cầu một ủy ban thiết kế lại cơ chế nhập khẩu. Tối thiểu, cú pháp này phải hoạt động nếu x.py và z.py nằm trong cùng một thư mục. Cụ thể là nếu x.py có câu lệnh, "từ .z nhập MyZebraClass" x nên nhập z NGAY nếu nó được chạy dưới dạng chính ! Sao chuyện đó lại khó khăn đến thế?
Steve L

4
Sau khi đọc qua nhiều chủ đề này, mặc dù không có câu trả lời cho câu hỏi, "chỉ cần sử dụng nhập khẩu tuyệt đối" dường như là giải pháp ...
CodeJ Racer 20/11/18

Câu trả lời:


1043

Kịch bản so với mô-đun

Đây là một lời giải thích. Phiên bản ngắn là có một sự khác biệt lớn giữa trực tiếp chạy tệp Python và nhập tệp đó từ nơi khác. Chỉ cần biết tập tin nằm trong thư mục nào không xác định gói Python nghĩ là gì. Ngoài ra, điều đó còn phụ thuộc vào cách bạn tải tệp vào Python (bằng cách chạy hoặc bằng cách nhập).

Có hai cách để tải tệp Python: dưới dạng tập lệnh cấp cao nhất hoặc dưới dạng mô-đun. Ví dụ, một tệp được tải dưới dạng tập lệnh cấp cao nhất nếu bạn thực thi trực tiếp bằng cách nhập python myfile.pyvào dòng lệnh. Nó được tải dưới dạng một mô-đun nếu bạn thực hiện python -m myfilehoặc nếu nó được tải khi gặp một importcâu lệnh bên trong một số tệp khác. Mỗi lần chỉ có thể có một kịch bản cấp cao nhất; tập lệnh cấp cao nhất là tệp Python bạn đã chạy để bắt đầu mọi thứ.

Đặt tên

Khi một tập tin được tải, nó được đặt tên (được lưu trong __name__thuộc tính của nó ). Nếu nó được tải dưới dạng tập lệnh cấp cao nhất, tên của nó là __main__. Nếu nó được tải dưới dạng một mô-đun, tên của nó là tên tệp, trước tên của bất kỳ gói / gói con nào mà nó là một phần, được phân tách bằng dấu chấm.

Vì vậy, ví dụ trong ví dụ của bạn:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

nếu bạn đã nhập moduleX(lưu ý: đã nhập , không được thực thi trực tiếp), tên của nó sẽ là package.subpackage1.moduleX. Nếu bạn nhập moduleA, tên của nó sẽ là package.moduleA. Tuy nhiên, nếu bạn trực tiếp chạy moduleX từ dòng lệnh, thay vào đó tên của nó sẽ là __main__, và nếu bạn trực tiếp chạy moduleAtừ dòng lệnh, tên của nó sẽ là __main__. Khi một mô-đun được chạy dưới dạng tập lệnh cấp cao nhất, nó sẽ mất tên bình thường và thay vào đó là tên của nó __main__.

Truy cập một mô-đun KHÔNG thông qua gói chứa nó

Có một nếp nhăn bổ sung: tên của mô-đun phụ thuộc vào việc nó được nhập "trực tiếp" từ thư mục mà nó nằm trong hoặc được nhập qua gói. Điều này chỉ tạo ra sự khác biệt nếu bạn chạy Python trong một thư mục và cố gắng nhập một tệp trong cùng thư mục đó (hoặc thư mục con của nó). Chẳng hạn, nếu bạn khởi động trình thông dịch Python trong thư mục package/subpackage1và sau đó thực hiện import moduleX, tên của moduleXsẽ chỉ là moduleX, và không package.subpackage1.moduleX. Điều này là do Python thêm thư mục hiện tại vào đường dẫn tìm kiếm của nó khi khởi động; nếu nó tìm thấy mô-đun được nhập trong thư mục hiện tại, nó sẽ không biết rằng thư mục đó là một phần của gói và thông tin gói sẽ không trở thành một phần của tên mô-đun.

Một trường hợp đặc biệt là nếu bạn chạy trình thông dịch một cách tương tác (ví dụ: chỉ cần gõ pythonvà bắt đầu nhập mã Python một cách nhanh chóng). Trong trường hợp này tên của phiên tương tác đó là __main__.

Bây giờ đây là điều cốt yếu cho thông báo lỗi của bạn: nếu tên của mô-đun không có dấu chấm, thì nó không được coi là một phần của gói . Nó không quan trọng nơi tập tin thực sự nằm trên đĩa. Tất cả vấn đề là tên của nó là gì và tên của nó phụ thuộc vào cách bạn tải nó.

Bây giờ hãy nhìn vào trích dẫn bạn đưa vào câu hỏi của bạn:

Nhập khẩu tương đối sử dụng thuộc tính tên của mô-đun để xác định vị trí của mô-đun đó trong phân cấp gói. Nếu tên của mô-đun không chứa bất kỳ thông tin gói nào (ví dụ: nó được đặt thành 'chính') thì việc nhập tương đối được giải quyết như thể mô-đun là mô-đun cấp cao nhất, bất kể mô-đun thực sự nằm ở đâu trên hệ thống tệp.

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

Nhập khẩu tương đối sử dụng tên của mô-đun để xác định vị trí của gói trong gói. Khi bạn sử dụng nhập tương đối from .. import foo, các dấu chấm biểu thị để tăng một số cấp trong phân cấp gói. Ví dụ, nếu tên mô-đun hiện tại của bạn là package.subpackage1.moduleX, thì ..moduleAcó nghĩa là package.moduleA. Để from .. importlàm việc, tên của mô-đun phải có ít nhất nhiều dấu chấm như trong importcâu lệnh.

... chỉ tương đối trong một gói

Tuy nhiên, nếu tên mô-đun của bạn là __main__, nó không được coi là trong một gói. Tên của nó không có dấu chấm, và do đó bạn không thể sử dụng các from .. importcâu lệnh bên trong nó. Nếu bạn cố gắng làm như vậy, bạn sẽ gặp lỗi "nhập tương đối trong gói không".

Tập lệnh không thể nhập tương đối

Những gì bạn có thể đã làm là bạn đã cố chạy moduleXhoặc tương tự từ dòng lệnh. Khi bạn thực hiện việc này, tên của nó được đặt thành __main__, điều đó có nghĩa là nhập khẩu tương đối trong đó sẽ không thành công, vì tên của nó không tiết lộ rằng nó nằm trong một gói. Lưu ý rằng điều này cũng sẽ xảy ra nếu bạn chạy Python từ cùng thư mục có mô-đun, sau đó thử nhập mô-đun đó, vì như mô tả ở trên, Python sẽ tìm thấy mô-đun trong thư mục hiện tại "quá sớm" mà không nhận ra đó là mô-đun một phần của gói

Cũng nên nhớ rằng khi bạn chạy trình thông dịch tương tác, "tên" của phiên tương tác đó luôn luôn __main__. Do đó, bạn không thể thực hiện nhập khẩu tương đối trực tiếp từ một phiên tương tác . Nhập khẩu tương đối chỉ được sử dụng trong các tập tin mô-đun.

Hai giải pháp:

  1. Nếu bạn thực sự muốn chạy moduleXtrực tiếp, nhưng bạn vẫn muốn nó được coi là một phần của gói, bạn có thể làm python -m package.subpackage1.moduleX. Việc -mbảo Python tải nó dưới dạng một mô-đun, không phải là tập lệnh cấp cao nhất.

  2. Hoặc có lẽ bạn không thực sự muốn chạy moduleX , bạn chỉ muốn chạy một số tập lệnh khác, giả sử myfile.py, sử dụng các chức năng bên trong moduleX. Nếu đó là trường hợp, đặt myfile.py một nơi khác - không phải trong packagethư mục - và chạy nó. Nếu bên trong myfile.pybạn làm những việc như thế from package.moduleA import spam, nó sẽ hoạt động tốt.

Ghi chú

  • Đối với một trong những giải pháp này, thư mục gói ( packagetrong ví dụ của bạn) phải có thể truy cập được từ đường dẫn tìm kiếm mô-đun Python ( sys.path). Nếu không, bạn sẽ không thể sử dụng bất cứ thứ gì trong gói một cách đáng tin cậy cả.

  • Kể từ Python 2.6, "tên" của mô-đun cho các mục đích phân giải gói được xác định không chỉ bởi các __name__thuộc tính của nó mà còn bởi __package__thuộc tính. Đó là lý do tại sao tôi tránh sử dụng biểu tượng rõ ràng __name__để chỉ "tên" của mô-đun. Vì Python 2.6, "tên" của mô-đun có hiệu quả __package__ + '.' + __name__hoặc chỉ là __name__nếu __package__None.)


62
Its name has no dots, and therefore you cannot use from .. import statements inside it. If you try to do so, you will get the "relative-import in non-package" error.Điều này về cơ bản là đáng lo ngại. Có gì khó khăn khi nhìn vào thư mục hiện tại? Python nên có khả năng này. Điều này có cố định trong phiên bản 3x không?

7
@Stopforgettingmyaccounts ...: PEP 366 cho thấy cách thức hoạt động. Trong một tập tin, bạn có thể làm __package__ = 'package.subpackage1'hoặc tương tự. Sau đó , tập tin đó sẽ luôn được coi là một phần của gói đó ngay cả khi chạy trực tiếp. Nếu bạn có câu hỏi khác về __package__bạn có thể muốn hỏi một câu hỏi riêng vì chúng tôi đang giải quyết vấn đề của câu hỏi ban đầu của bạn ở đây.
BrenBarn

108
Đây phải là câu trả lời cho tất cả các câu hỏi nhập khẩu tương đối của Python. Điều này nên có trong các tài liệu, thậm chí.
edsioufi

10
Xem python.org/dev/peps/pep-0366 - "Lưu ý rằng bản tóm tắt này chỉ đủ nếu gói cấp cao nhất có thể truy cập qua sys.path. Cần thêm mã điều khiển sys.path để thực thi trực tiếp để làm việc mà không có gói cấp cao nhất đã được nhập khẩu. " - đây là bit đáng lo ngại nhất đối với tôi vì "mã bổ sung" này thực sự khá dài và không thể được lưu trữ ở nơi khác trong gói để chạy dễ dàng.
Michael Scott Cuthbert

14
Câu trả lời này hiện đang tắt trên một vài chi tiết quan trọng liên quan đến __name__sys.path. Cụ thể, với python -m pkg.mod, __name__được đặt thành __main__, không pkg.mod; nhập khẩu tương đối được giải quyết bằng cách sử dụng __package__chứ không phải __name__trong trường hợp này. Ngoài ra, Python thêm thư mục của tập lệnh chứ không phải thư mục hiện tại sys.pathkhi chạy python path/to/script.py; nó thêm thư mục hiện tại vào sys.pathkhi chạy hầu hết các cách khác, bao gồm python -m pkg.mod.
user2357112 hỗ trợ Monica

42

Đây thực sự là một vấn đề trong python. Nguồn gốc của sự nhầm lẫn là mọi người nhầm lẫn nhập nhập tương đối làm đường dẫn tương đối mà không phải là.

Ví dụ: khi bạn viết trong faa.py :

from .. import foo

Điều này chỉ có ý nghĩa nếu faa.py được xác định và tải bởi python, trong khi thực thi, là một phần của gói. Trong trường hợp đó, tên của mô-đun cho faa.py sẽ là ví dụ some_packagename.faa . Nếu tệp được tải chỉ vì nó nằm trong thư mục hiện tại, khi python được chạy, thì tên của nó sẽ không tham chiếu đến bất kỳ gói nào và cuối cùng việc nhập tương đối sẽ thất bại.

Một giải pháp đơn giản để tham chiếu các mô-đun trong thư mục hiện tại, là sử dụng điều này:

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo

6
Giải pháp chính xác là from __future__ import absolute_importvà buộc người dùng sử dụng mã của bạn một cách chính xác ... để bạn luôn có thể làmfrom . import foo
Giacomo Alzetta 16/07/18

@Giacomo: câu trả lời hoàn toàn đúng cho vấn đề của tôi. Cảm ơn!
Fábio

8

Đây là một công thức chung, được sửa đổi để phù hợp làm ví dụ, hiện tôi đang sử dụng để xử lý các thư viện Python được viết dưới dạng các gói, có chứa các tệp phụ thuộc lẫn nhau, nơi tôi muốn có thể kiểm tra các phần của chúng. Hãy gọi nó lib.foovà nói rằng nó cần quyền truy cập lib.fileAcho các chức năng f1f2, và lib.fileBcho lớp Class3.

Tôi đã bao gồm một vài printcuộc gọi để giúp minh họa cách thức hoạt động của nó. Trong thực tế, bạn sẽ muốn loại bỏ chúng (và có thể cả from __future__ import print_functiondòng).

Ví dụ cụ thể này quá đơn giản để hiển thị khi chúng ta thực sự cần chèn một mục vào sys.path. (Xem Lars' câu trả lời cho một trường hợp mà chúng ta làm cần đến nó, khi chúng tôi có hai hoặc nhiều mức độ thư mục gói, và sau đó chúng tôi sử dụng os.path.dirname(os.path.dirname(__file__))-Nhưng nó không thực sự bị tổn thương ở đây cả.) Nó cũng đủ an toàn để làm điều này mà không có sự if _i in sys.pathkiểm tra. Tuy nhiên, nếu mỗi tệp được nhập sẽ chèn cùng một đường dẫn, ví dụ, nếu cả hai fileAfileBmuốn nhập các tiện ích từ gói thì điều này sẽ lặp lại sys.pathvới cùng một đường dẫn nhiều lần, vì vậy thật tuyệt khi có if _i not in sys.pathphần soạn sẵn.

from __future__ import print_function # only when showing how this works

if __package__:
    print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
    from .fileA import f1, f2
    from .fileB import Class3
else:
    print('Not a package; __name__ is {!r}'.format(__name__))
    # these next steps should be used only with care and if needed
    # (remove the sys.path manipulation for simple cases!)
    import os, sys
    _i = os.path.dirname(os.path.abspath(__file__))
    if _i not in sys.path:
        print('inserting {!r} into sys.path'.format(_i))
        sys.path.insert(0, _i)
    else:
        print('{!r} is already in sys.path'.format(_i))
    del _i # clean up global name space

    from fileA import f1, f2
    from fileB import Class3

... all the code as usual ...

if __name__ == '__main__':
    import doctest, sys
    ret = doctest.testmod()
    sys.exit(0 if ret.failed == 0 else 1)

Ý tưởng ở đây là điều này (và lưu ý rằng tất cả các chức năng này đều giống nhau trên python2.7 và python 3.x):

  1. Nếu chạy như import libhoặc from lib import foolà một nhập khẩu trọn gói thường xuyên từ mã bình thường, __packagelib__name__lib.foo. Chúng tôi đi theo con đường mã đầu tiên, nhập từ .fileA, v.v.

  2. Nếu chạy như python lib/foo.py, __package__sẽ là Không và __name__sẽ __main__.

    Chúng tôi đi theo con đường mã thứ hai. Thư mục libsẽ có trong sys.pathđó nên không cần thêm nó. Chúng tôi nhập khẩu fileA, vv

  3. Nếu chạy trong libthư mục như python foo.py, hành vi tương tự như trường hợp 2.

  4. Nếu chạy trong libthư mục dưới dạng python -m foo, hành vi tương tự như trường hợp 2 và 3. Tuy nhiên, đường dẫn đến libthư mục không nằm trong sys.path, vì vậy chúng tôi thêm nó trước khi nhập. Điều tương tự cũng áp dụng nếu chúng ta chạy Python và sau đó import foo.

    (Vì . trong sys.path, chúng tôi thực sự không cần thêm phiên bản tuyệt đối của đường dẫn ở đây. Đây là nơi cấu trúc lồng gói sâu hơn, nơi chúng tôi muốn làm from ..otherlib.fileC import ..., tạo sự khác biệt. Nếu bạn không làm điều này, bạn có thể bỏ qua tất cả các sys.paththao tác hoàn toàn.)

Ghi chú

Vẫn còn một sự ngớ ngẩn. Nếu bạn chạy toàn bộ điều này từ bên ngoài:

$ python2 lib.foo

hoặc là:

$ python3 lib.foo

hành vi phụ thuộc vào nội dung của lib/__init__.py. Nếu điều đó tồn tại và trống rỗng , tất cả đều tốt:

Package named 'lib'; __name__ is '__main__'

Nhưng nếu lib/__init__.py chính nó nhập routineđể nó có thể xuất routine.nametrực tiếp lib.name, bạn sẽ nhận được:

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'

Đó là, mô-đun được nhập hai lần, một lần qua gói và sau đó một lần nữa __main__để nó chạy mainmã của bạn . Python 3.6 và sau đó cảnh báo về điều này:

$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
  warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'

Các cảnh báo là mới, nhưng hành vi cảnh báo-về không. Nó là một phần của cái mà một số người gọi là bẫy nhập khẩu kép . (Để biết thêm chi tiết, xem vấn đề 27487. ) Nick Coghlan nói:

Cái bẫy tiếp theo này tồn tại trong tất cả các phiên bản hiện tại của Python, bao gồm 3.3 và có thể được tóm tắt trong hướng dẫn chung sau: "Không bao giờ thêm thư mục gói hoặc bất kỳ thư mục nào trong gói, trực tiếp vào đường dẫn Python".

Lưu ý rằng trong khi chúng tôi vi phạm quy tắc đó ở đây, chúng tôi chỉ thực hiện khi tệp được tải không được tải như một phần của gói và sửa đổi của chúng tôi được thiết kế riêng để cho phép chúng tôi truy cập các tệp khác trong gói đó. (Và, như tôi đã lưu ý, có lẽ chúng ta hoàn toàn không nên làm điều này đối với các gói cấp đơn.) Nếu chúng ta muốn làm sạch thêm, chúng ta có thể viết lại điều này như, ví dụ:

    import os, sys
    _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    if _i not in sys.path:
        sys.path.insert(0, _i)
    else:
        _i = None

    from sub.fileA import f1, f2
    from sub.fileB import Class3

    if _i:
        sys.path.remove(_i)
    del _i

Đó là, chúng tôi sửa đổi sys.pathđủ lâu để đạt được nhập khẩu của mình, sau đó đưa nó trở lại như cũ (xóa một bản sao _inếu và chỉ khi chúng tôi thêm một bản sao _i).


7

Vì vậy, sau khi quan tâm đến vấn đề này cùng với nhiều người khác, tôi đã bắt gặp một ghi chú được đăng bởi Dorian B trong bài viết này đã giải quyết vấn đề cụ thể mà tôi gặp phải khi tôi sẽ phát triển các mô-đun và các lớp để sử dụng với một dịch vụ web, nhưng tôi cũng muốn trở thành có thể kiểm tra chúng khi tôi đang mã hóa, sử dụng các tiện ích gỡ lỗi trong PyCharm. Để chạy thử nghiệm trong một lớp độc lập, tôi sẽ bao gồm các phần sau vào cuối tệp lớp của mình:

if __name__ == '__main__':
   # run test code here...

nhưng nếu tôi muốn nhập các lớp hoặc mô-đun khác trong cùng một thư mục, thì tôi sẽ phải thay đổi tất cả các câu lệnh nhập của mình từ ký hiệu tương đối sang tham chiếu cục bộ (nghĩa là xóa dấu chấm (.)) Nhưng sau khi đọc đề xuất của Dorian, tôi đã thử ' một lớp lót 'và nó đã làm việc! Bây giờ tôi có thể kiểm tra trong PyCharm và để lại mã kiểm tra của mình khi tôi sử dụng lớp trong một lớp khác đang thử nghiệm hoặc khi tôi sử dụng nó trong dịch vụ web của mình!

# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
    from codex import Codex # these are in same folder as module under test!
    from dblogger import DbLogger
else:
    from .codex import Codex
    from .dblogger import DbLogger

Câu lệnh if kiểm tra xem liệu chúng ta đang chạy mô-đun này là chính hay nếu nó được sử dụng trong một mô-đun khác đang được thử nghiệm là chính . Có lẽ điều này là hiển nhiên, nhưng tôi cung cấp lưu ý này ở đây trong trường hợp bất kỳ ai khác thất vọng bởi các vấn đề nhập khẩu tương đối ở trên có thể sử dụng nó.


1
Điều đó thực sự giải quyết nó. Nhưng nó thực sự khó chịu. Tại sao đây không phải là hành vi mặc định?!
lo tolmencre

4

Đây là một giải pháp mà tôi không khuyến nghị, nhưng có thể hữu ích trong một số trường hợp đơn giản là các mô-đun không được tạo:

import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()

2

Tôi gặp vấn đề tương tự khi tôi không muốn thay đổi đường dẫn tìm kiếm mô-đun Python và cần tải mô-đun tương đối từ tập lệnh (mặc dù "tập lệnh không thể nhập tương đối với tất cả" như BrenBarn đã giải thích độc đáo ở trên).

Vì vậy, tôi đã sử dụng hack sau đây. Thật không may, nó dựa vào impmô-đun đã bị phản đối kể từ phiên bản 3.4 bị loại bỏ theo hướng có lợi importlib. (Điều này có thể xảy ra với importlibquá không? Tôi không biết.) Tuy nhiên, hack hiện hoạt động.

Ví dụ để truy cập vào các thành viên của moduleXtrong subpackage1từ một kịch bản cư trú trong subpackage2thư mục:

#!/usr/bin/env python3

import inspect
import imp
import os

def get_script_dir(follow_symlinks=True):
    """
    Return directory of code defining this very function.
    Should work from a module as well as from a script.
    """
    script_path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        script_path = os.path.realpath(script_path)
    return os.path.dirname(script_path)

# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)

# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST

Một cách tiếp cận sạch hơn dường như là sửa đổi sys.path được sử dụng để tải các mô-đun như được đề cập bởi Federico.

#!/usr/bin/env python3

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    # __file__ should be defined in this case
    PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
   sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *

Điều đó có vẻ tốt hơn ... quá tệ, nó vẫn yêu cầu bạn phải nhúng tên của thư mục mẹ vào tệp ... có lẽ điều đó có thể được cải thiện với importlib. Có thể importlib thậm chí có thể được ghép lại để nhập tương đối "chỉ hoạt động" cho các trường hợp sử dụng đơn giản. Tôi sẽ có một vết nứt tại nó.
Andrew Wagner

Tôi đang sử dụng python 2.7,14 mặc dù. Một cái gì đó như thế này vẫn còn hoạt động?
user3474042

Tôi vừa thử nghiệm cả hai cách tiếp cận trên python 2.7.10 và chúng hoạt động tốt với tôi. Nếu thực tế, bạn không gặp vấn đề về mô-đun imp không dùng nữa trong 2.7, vì vậy tất cả đều tốt hơn.
Lars

2

__name__ thay đổi tùy thuộc vào việc mã được đề cập có được chạy trong không gian tên toàn cầu hay là một phần của mô-đun được nhập.

Nếu mã không chạy trong không gian toàn cầu, __name__sẽ là tên của mô-đun. Nếu nó đang chạy trong không gian tên toàn cầu - ví dụ: nếu bạn nhập nó vào bàn điều khiển hoặc chạy mô-đun dưới dạng tập lệnh sử dụng python.exe yourscriptnamehere.pythì __name__sẽ trở thành "__main__".

Bạn sẽ thấy rất nhiều mã python if __name__ == '__main__'được sử dụng để kiểm tra xem mã có đang được chạy từ không gian tên toàn cầu hay không - cho phép bạn có một mô-đun nhân đôi như một tập lệnh.

Bạn đã thử làm những nhập khẩu từ bảng điều khiển?


À, vậy bạn nhắc đến -m. Điều đó làm cho mô-đun của bạn thực thi dưới dạng tập lệnh - nếu bạn dán if __name__ == '__main__' trong đó bạn sẽ thấy rằng đó là '__main__' vì -m. Hãy thử chỉ nhập mô-đun của bạn vào một mô-đun khác để nó không phải là cấp cao nhất ... điều đó sẽ cho phép bạn thực hiện nhập tương đối
theodox

Tôi đã cố gắng thực hiện các thao tác nhập này từ bảng điều khiển, với tệp hoạt động là mô-đun chính xác.

@Stopforgettingmyaccounts ...: "Tập tin hoạt động" nghĩa là gì?
BrenBarn

Tôi sử dụng Pyscripter. Tôi đã ở trong moduleX.py khi tôi chạy các nhập khẩu này: từ .moduleY nhập thư rác từ. nhập mô-đun.

Không nhập .moduleY theo sau bởi moduleY.spam ()?
theodox

2

Câu trả lời của @ BrenBarn đã nói lên tất cả, nhưng nếu bạn giống tôi thì có thể mất một lúc để hiểu. Đây là trường hợp của tôi và câu trả lời của @ BrenBarn áp dụng cho nó, có lẽ nó sẽ giúp bạn.

Trường hợp

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

Sử dụng ví dụ quen thuộc của chúng tôi và thêm vào đó, moduleX.py có một lần nhập tương đối vào ..moduleA. Cho rằng tôi đã thử viết một tập lệnh thử nghiệm trong thư mục subpackage1 đã nhập moduleX, nhưng sau đó đã nhận được lỗi đáng sợ được mô tả bởi OP.

Giải pháp

Di chuyển tập lệnh thử nghiệm đến cùng cấp độ với gói và nhập gói.subpackage1.moduleX

Giải trình

Như đã giải thích, nhập khẩu tương đối được thực hiện liên quan đến tên hiện tại. Khi tập lệnh kiểm tra của tôi nhập moduleX từ cùng thư mục, thì tên mô-đun bên trong moduleX là moduleX. Khi nó gặp một lần nhập tương đối, trình thông dịch không thể sao lưu cấu trúc phân cấp gói vì nó đã ở trên cùng

Khi tôi nhập moduleX từ phía trên, thì tên bên trong moduleX là pack.subpackage1.moduleX và có thể tìm thấy nhập tương đối


Hy vọng bạn có thể hướng dẫn tôi về điều này. Trong liên kết sau, nếu bạn đi đến Trường hợp 3, nó cho biết giải pháp 1 là không thể. Xin vui lòng bạn có thể kiểm tra điều này và cho tôi biết. Nó sẽ giúp tôi rất nhiều. chrisyeh96.github.io/2017/08/08/
biến

@variable có một lỗi đánh máy trong liên kết và tôi không được phép chỉnh sửa. Đã xem xét trường hợp 3 và không làm theo chính xác những gì bạn đang làm. Khi tôi thử ví dụ đó trong python 2, không có vấn đề gì khiến tôi nghĩ rằng tôi đã bỏ lỡ điều gì đó. Có lẽ bạn nên đăng một câu hỏi mới nhưng cần cung cấp một ví dụ rõ ràng hơn. Trường hợp 4 chạm vào những gì tôi đang nói trong câu trả lời của tôi ở đây: bạn không thể truy cập một thư mục để nhập tương đối KHÔNG GIỚI HẠN trình thông dịch bắt đầu trong một thư mục mẹ
Brad Dre

Cảm ơn tôi đang đề cập đến python 3 và ở đây câu hỏi stackoverflow.com/questions/58577767/iêu
biến

1

Nhập khẩu tương đối sử dụng thuộc tính tên của mô-đun để xác định vị trí của mô-đun đó trong phân cấp gói. Nếu tên của mô-đun không chứa bất kỳ thông tin gói nào (ví dụ: nó được đặt thành 'chính') thì việc nhập tương đối được giải quyết như thể mô-đun là mô-đun cấp cao nhất, bất kể mô-đun thực sự nằm ở đâu trên hệ thống tệp.

Đã viết một gói python nhỏ cho PyPi có thể giúp người xem câu hỏi này. Gói hoạt động như một cách giải quyết nếu một người muốn có thể chạy các tệp python chứa các tệp nhập có chứa các gói cấp cao hơn trong một gói / dự án mà không được trực tiếp trong thư mục của tệp nhập. https://pypi.org/project/import-anywhere/


-2

Để làm cho Python không trả lại cho tôi "Đã cố nhập tương đối trong gói không". gói /

init .py subpackage1 / init .py moduleX.py moduleY.txt subpackage2 / init .py moduleZ.py moduleA.py

Lỗi này chỉ xảy ra nếu bạn đang áp dụng nhập tương đối vào tệp cha. Ví dụ: tệp cha đã trả về chính sau khi bạn mã "print ( name )" trong moduleA.py .so Tệp NÀY đã là chínhnó không thể trả lại bất kỳ gói cha nào hơn nữa. nhập khẩu tương đối được yêu cầu trong các tệp của gói subpackage1 và subpackage2 bạn có thể sử dụng ".." để tham khảo thư mục mẹ hoặc mô-đun. Nhưng cha mẹ là nếu gói đã ở cấp cao nhất thì nó không thể đi xa hơn thư mục mẹ (gói) đó. Các tệp như vậy nơi bạn đang áp dụng nhập tương đối cho cha mẹ chỉ có thể hoạt động với ứng dụng nhập tuyệt đối. Nếu bạn sẽ sử dụng NHẬP KHẨU TUYỆT VỜI TRONG GÓI PHỤ HUYNH, KHÔNG CÓ LRI nào sẽ xuất hiện vì python biết ai ở cấp cao nhất của gói ngay cả khi tệp của bạn nằm trong gói phụ vì khái niệm PYTHON PATH xác định cấp cao nhất của dự án

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.