Cách khắc phục lỗi Đã cố gắng nhập tương đối trong gói không có gói ngay cả với __init__.py


744

Tôi đang cố gắng làm theo PEP 328 , với cấu trúc thư mục sau:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

Trong core_test.pytôi có báo cáo nhập khẩu sau đây

from ..components.core import GameLoopEvents

Tuy nhiên, khi tôi chạy, tôi gặp lỗi sau:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

Tìm kiếm xung quanh tôi thấy " đường dẫn tương đối không hoạt động ngay cả với __init__.py " và " Nhập mô-đun từ đường dẫn tương đối " nhưng chúng không giúp ích.

Có điều gì tôi đang thiếu ở đây?


17
Tôi cũng rất bối rối bởi các cách cấu trúc unittestdự án khác nhau , vì vậy tôi đã viết dự án mẫu khá toàn diện này bao gồm việc lồng sâu các mô-đun, nhập khẩu tương đối và tuyệt đối (nơi làm việc và không), và tham chiếu tương đối và tuyệt đối từ bên trong gói, cũng như nhập các lớp đơn, đôi và cấp gói. Đã giúp mọi thứ rõ ràng ngay cho tôi!
cod3monk3y

1
Tôi không thể làm bài kiểm tra của bạn để làm việc. Tiếp tục nhận được no module named myimports.fookhi tôi chạy chúng.
Blairg23

@ Blairg23 Tôi đoán rằng việc gọi dự định là cdvào PyImportsvà chạy python -m unittest tests.test_abs, chẳng hạn.
duozmo

7
Tôi đồng ý với Gene. Tôi ước có một cơ chế để gỡ lỗi quá trình nhập có ích hơn một chút. Trong trường hợp của tôi, tôi có hai tệp trong cùng một thư mục. Tôi đang cố gắng nhập một tệp vào tệp khác. Nếu tôi có tệp init .py trong thư mục đó, tôi nhận được ValueError: Đã cố nhập tương đối trong lỗi không gói. Nếu tôi xóa tệp init .py, thì tôi gặp lỗi không có mô-đun có tên 'NAME'.
dùng1928764

Trong trường hợp của tôi, tôi có hai tệp trong cùng một thư mục. Tôi đang cố gắng nhập một tệp vào tệp khác. Nếu tôi có tệp init .py trong thư mục đó, tôi nhận được ValueError: Đã cố nhập tương đối trong lỗi không gói. Nếu tôi xóa tệp init .py, thì tôi gặp lỗi không có mô-đun có tên 'NAME'. Điều thực sự bực bội là tôi đã làm việc này và sau đó tôi đã tự bắn vào chân mình bằng cách xóa tệp .bashrc, điều này đặt PYTHONPATH thành một cái gì đó, và bây giờ nó không hoạt động.
dùng1928764

Câu trả lời:


443

Đúng. Bạn không sử dụng nó như một gói.

python -m pkg.tests.core_test

51
Một gotcha: Lưu ý rằng không có '.py' ở cuối!
trí

497
Tôi không phải là một trong những người downvoters, nhưng tôi cảm thấy điều này có thể sử dụng chi tiết hơn một chút , với sự phổ biến của câu hỏi và câu trả lời này. Lưu ý những thứ như từ thư mục nào để thực thi lệnh shell ở trên, thực tế là bạn cần __init__.pyhết thời __package__gian và thủ thuật -modifying (được mô tả bên dưới bởi BrenBarn) cần để cho phép những lần nhập này cho các tập lệnh thực thi (ví dụ như khi sử dụng shebang và làm ./my_script.pytại shell Unix) tất cả sẽ hữu ích. Toàn bộ vấn đề này khá khó khăn đối với tôi để tìm ra hoặc tìm tài liệu ngắn gọn và dễ hiểu.
Mark Amery

16
Lưu ý: bạn cần ở ngoài thư mục pkgtại điểm bạn gọi dòng này từ CLI. Sau đó, nó sẽ làm việc như mong đợi. Nếu bạn ở trong pkgvà bạn gọi python -m tests.core_test, nó sẽ không hoạt động. Ít nhất nó đã không cho tôi.
Blairg23

94
Nghiêm túc mà nói, bạn có thể giải thích những gì đang xảy ra trong câu trả lời của bạn?
Pinocchio

18
@MarkAmery Tôi gần như mất trí khi cố gắng tìm hiểu xem tất cả những thứ này hoạt động như thế nào, nhập tương đối trong một dự án với các thư mục con với các tệp py có __init__.pytệp mà bạn vẫn gặp ValueError: Attempted relative import in non-packagelỗi. Tôi sẽ trả tiền thực sự tốt cho ai đó, ở đâu đó, để cuối cùng giải thích bằng tiếng Anh đơn giản về cách tất cả những thứ này hoạt động.
AdjuncProfigatorFalcon

635

Để giải thích về câu trả lời của Ignacio Vazquez-Abrams :

Cơ chế nhập Python hoạt động liên quan đến __name__tệp hiện tại. Khi bạn thực thi một tệp trực tiếp, nó không có tên thông thường mà "__main__"thay vào đó là tên của nó. Vì vậy, nhập khẩu tương đối không hoạt động.

Bạn có thể, như Igancio đề xuất, thực hiện nó bằng cách sử dụng -mtùy chọn. Nếu bạn có một phần của gói có nghĩa là được chạy dưới dạng tập lệnh, bạn cũng có thể sử dụng __package__thuộc tính để nói với tệp đó tên mà nó phải có trong phân cấp gói.

Xem http://www.python.org/dev/peps/pep-0366/ để biết chi tiết.


55
Mất một lúc để tôi nhận ra bạn không thể chạy python -m core_testtừ trong teststhư mục con - nó phải từ cha mẹ hoặc bạn phải thêm cha mẹ vào đường dẫn.
Aram Kocharyan

3
@DannyStaple: Không chính xác. Bạn có thể sử dụng __package__để đảm bảo các tệp script thực thi có thể nhập tương đối các mô-đun khác từ trong cùng một gói. Không có cách nào để nhập tương đối từ "toàn bộ hệ thống". Tôi thậm chí không chắc chắn tại sao bạn muốn làm điều này.
BrenBarn

2
Ý tôi là nếu __package__biểu tượng được đặt thành "Parent.child" thì bạn có thể nhập "Parent.other_child". Có lẽ tôi đã không diễn đạt nó rất tốt.
Daniel Staple

5
@DannyStaple: Chà, cách thức hoạt động được mô tả trong tài liệu được liên kết. Nếu bạn có một kịch bản script.pytrong gói pack.subpack, sau đó thiết lập nó __package__để pack.subpacksẽ cho phép bạn làm from ..module import somethingmột cái gì đó nhập khẩu từ pack.module. Lưu ý rằng, như tài liệu nói, bạn vẫn phải có gói cấp cao nhất trên đường dẫn hệ thống. Đây đã là cách mọi thứ hoạt động cho các mô-đun nhập khẩu. Điều duy nhất __package__làm là cho phép bạn sử dụng hành vi đó cho các tập lệnh được thực thi trực tiếp.
BrenBarn

3
Tôi sử dụng __package__trong tập lệnh được thực thi trực tiếp nhưng thật không may, tôi gặp phải lỗi sau: "Mô-đun mẹ 'xxx' không được tải, không thể thực hiện nhập tương đối"
mononoke

202

Bạn có thể sử dụng import components.coretrực tiếp nếu bạn thêm thư mục hiện tại vào sys.path:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))này cũng sẽ làm việc
Ajay

26
from os import sysTrông như gian lận :)
cừu bay

3
@Piotr: Nó có thể được coi là tốt hơn bởi vì nó hơi hiển thị rõ hơn những gì đang được thêm vào sys.path- cha mẹ của thư mục tệp hiện tại.
martineau

8
@flyingsheep: Đồng ý, tôi chỉ sử dụng thông thường import sys, os.path as path.
martineau

10
FYI, để sử dụng điều này trong một máy tính xách tay ipython, tôi đã điều chỉnh câu trả lời này thành : import os; os.sys.path.append(os.path.dirname(os.path.abspath('.'))). Sau đó, một import components.corecông việc thẳng cho tôi, nhập từ thư mục mẹ của máy tính xách tay như mong muốn.
Đua nòng nọc

195

Nó phụ thuộc vào cách bạn muốn khởi chạy tập lệnh của mình.

Nếu bạn muốn khởi chạy UnitTest của mình từ dòng lệnh theo cách cổ điển, đó là:

python tests/core_test.py

Sau đó, vì trong trường hợp này , 'các thành phần''các thử nghiệm' là các thư mục anh chị em, bạn có thể nhập mô-đun tương đối bằng cách sử dụng phương thức chèn hoặc nối thêm của mô-đun sys.path . Cái gì đó như:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

Mặt khác, bạn có thể khởi chạy tập lệnh của mình với đối số '-m' (lưu ý rằng trong trường hợp này, chúng tôi đang nói về một gói, và do đó bạn không được đưa ra phần mở rộng '.py' ), đó là:

python -m pkg.tests.core_test

Trong trường hợp như vậy, bạn chỉ cần sử dụng nhập tương đối như bạn đang làm:

from ..components.core import GameLoopEvents

Cuối cùng, bạn có thể kết hợp hai cách tiếp cận, để kịch bản của bạn hoạt động cho dù nó được gọi như thế nào. Ví dụ:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents

3
Tôi nên làm gì nếu tôi đang cố gắng sử dụng pdb để gỡ lỗi? kể từ khi bạn sử dụng python -m pdb myscript.pyđể khởi chạy phiên gỡ lỗi.
danny

1
@dannynjust - Đó là một câu hỏi hay vì bạn không thể có 2 mô-đun chính. Nói chung khi gỡ lỗi, tôi thích thả vào trình gỡ lỗi theo cách thủ công tại điểm đầu tiên mà tôi muốn bắt đầu gỡ lỗi. Bạn có thể làm điều đó bằng cách chèn một import pdb; pdb.set_trace()mã vào (nội tuyến).
mgilson

3
Nó là tốt hơn để sử dụng insertthay vì append? Đó là,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine

2
Sử dụng insert là một kết hợp tốt hơn cho ngữ nghĩa nhập tương đối, trong đó tên gói cục bộ được ưu tiên hơn các gói đã cài đặt. Đặc biệt đối với các thử nghiệm, bạn thường muốn thử nghiệm phiên bản cục bộ, không phải phiên bản đã cài đặt (trừ khi cơ sở hạ tầng thử nghiệm của bạn cài đặt mã đang thử nghiệm, trong trường hợp đó, việc nhập tương đối là không cần thiết và bạn sẽ không gặp phải vấn đề này).
Alex Dupuy

1
bạn cũng nên đề cập rằng bạn không thể có trong thư mục chứa core_test khi bạn chạy như một mô-đun (điều đó quá dễ dàng)
Joseph Garvin

25

Trong core_test.py, hãy làm như sau:

import sys
sys.path.append('../components')
from core import GameLoopEvents

10

Nếu trường hợp sử dụng của bạn là để chạy thử nghiệm, và nó xử lý đúng như vậy, thì bạn có thể làm như sau. Thay vì chạy tập lệnh thử nghiệm của bạn như python core_test.pysử dụng một khung kiểm tra như pytest. Sau đó, trên dòng lệnh bạn có thể nhập

$$ py.test

Điều đó sẽ chạy các bài kiểm tra trong thư mục của bạn. Điều này xoay quanh vấn đề __name__tồn tại __main__được chỉ ra bởi @BrenBarn. Tiếp theo, đặt một __init__.pytập tin trống vào thư mục kiểm tra của bạn, điều này sẽ làm cho thư mục kiểm tra một phần của gói của bạn. Sau đó, bạn sẽ có thể làm

from ..components.core import GameLoopEvents

Tuy nhiên, nếu bạn chạy tập lệnh thử nghiệm của mình như một chương trình chính thì mọi thứ sẽ thất bại một lần nữa. Vì vậy, chỉ cần sử dụng người chạy thử. Có thể điều này cũng hoạt động với những người chạy thử nghiệm khác như nosetestsnhưng tôi chưa kiểm tra nó. Hi vọng điêu nay co ich.


9

Cách khắc phục nhanh của tôi là thêm thư mục vào đường dẫn:

import sys
sys.path.insert(0, '../components/')

6
Cách tiếp cận của bạn sẽ không hoạt động trong mọi trường hợp vì phần '../' được giải quyết từ thư mục mà bạn chạy tập lệnh của mình (core_test.py). Với cách tiếp cận của bạn, bạn buộc phải cd để 'kiểm tra' trước khi chạy scritp core_test.py.
xyman

7

Vấn đề là với phương pháp thử nghiệm của bạn,

bạn đã thử python core_test.py

sau đó bạn sẽ gặp lỗi này ValueError: Đã cố nhập tương đối trong gói không

Lý do: bạn đang kiểm tra bao bì của mình từ nguồn không đóng gói.

để kiểm tra mô-đun của bạn từ nguồn gói.

nếu đây là cấu trúc dự án của bạn,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

cd pkg

python -m tests.core_test # dont use .py

hoặc từ pkg bên ngoài /

python -m pkg.tests.core_test

đơn .nếu bạn muốn nhập từ thư mục trong cùng thư mục. cho mỗi bước trở lại thêm một.

hi/
  hello.py
how.py

trong how.py

from .hi import hello

Nếu bạn muốn nhập như thế nào từ hello.py

from .. import how

1
Câu trả lời tốt hơn được chấp nhận
GabrielBB

Trong ví dụ này from .. import how, làm thế nào để bạn nhập một lớp / phương thức cụ thể từ tệp 'how'. Khi tôi làm tương đương với from ..how import foosau đó tôi nhận được "cố gắng nhập tương đối ngoài gói cấp cao nhất"
James Hulse

3

Chủ đề cũ. Tôi phát hiện ra rằng việc thêm một tệp __all__= ['submodule', ...]vào __init__.py và sau đó sử dụng from <CURRENT_MODULE> import *trong mục tiêu hoạt động tốt.


3

Bạn có thể sử dụng from pkg.components.core import GameLoopEvents, ví dụ tôi sử dụng pycharm, bên dưới là hình ảnh cấu trúc dự án của tôi, tôi chỉ cần nhập từ gói gốc, sau đó nó hoạt động:

nhập mô tả hình ảnh ở đây


3
Điều này đã không làm việc cho tôi. Bạn đã phải thiết lập đường dẫn trong cấu hình của bạn?
Mohammad Mahjoub

3

Như Paolo đã nói, chúng tôi có 2 phương thức gọi:

1) python -m tests.core_test
2) python tests/core_test.py

Một sự khác biệt giữa chúng là chuỗi sys.path [0]. Vì phiên dịch sẽ tìm kiếm sys.path khi thực hiện nhập , chúng ta có thể thực hiện với tests/core_test.py:

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

Và hơn thế nữa, chúng ta có thể chạy core_test.py bằng các phương thức khác:

cd tests
python core_test.py
python -m core_test
...

Lưu ý, chỉ thử nghiệm py36.


3

Cách tiếp cận này hiệu quả với tôi và ít lộn xộn hơn một số giải pháp:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

Thư mục mẹ nằm trong PYTHONPATH của tôi và có __init__.pycác tệp trong thư mục mẹ và thư mục này.

Cái trên luôn hoạt động trong python 2, nhưng python 3 đôi khi chạm vào ImportError hoặc ModuleNotFoundError (cái sau mới trong python 3.6 và một lớp con của ImportError), vì vậy, tinh chỉnh sau hoạt động với tôi trong cả python 2 và 3:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents


1

Nếu ai đó đang tìm kiếm một cách giải quyết, tôi tình cờ gặp một người. Đây là một chút bối cảnh. Tôi muốn thử nghiệm một trong những phương pháp tôi có trong một tệp. Khi tôi chạy nó từ bên trong

if __name__ == "__main__":

nó luôn luôn phàn nàn về hàng nhập khẩu tương đối. Tôi đã thử áp dụng các giải pháp trên, nhưng không hoạt động, vì có nhiều tệp lồng nhau, mỗi tệp có nhiều lần nhập.

Đây là những gì tôi đã làm. Tôi vừa tạo một launcher, một chương trình bên ngoài sẽ nhập các phương thức cần thiết và gọi chúng. Mặc dù, không phải là một giải pháp tuyệt vời, nó hoạt động.


0

Đây là một cách sẽ làm phiền mọi người nhưng làm việc khá tốt. Trong các thử nghiệm chạy:

ln -s ../components components

Sau đó, chỉ cần nhập các thành phần như bạn thường làm.


0

Điều này rất khó hiểu và nếu bạn đang sử dụng IDE như pycharm, thì sẽ khó hiểu hơn một chút. Điều gì làm việc cho tôi: 1. Thực hiện cài đặt dự án pycharm (nếu bạn đang chạy python từ VE hoặc từ thư mục python) 2. Không có cách nào sai theo cách bạn đã xác định. đôi khi nó hoạt động với từ lớp nhập thư mục1.file1

nếu nó không hoạt động, sử dụng thư mục nhập1.file1 3. Biến môi trường của bạn phải được đề cập chính xác trong hệ thống hoặc cung cấp nó trong đối số dòng lệnh của bạn.


-2

Vì mã của bạn chứa if __name__ == "__main__", không được nhập dưới dạng gói, bạn nên sử dụng sys.path.append()để giải quyết vấn đề tốt hơn .


Tôi không nghĩ rằng có if __name__ == "__main__"trong tệp của bạn tạo ra sự khác biệt cho bất cứ điều gì liên quan đến nhập khẩu.
dùng48956
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.