vượt quá lỗi gói cấp cao nhất trong nhập khẩu tương đối


316

Có vẻ như đã có khá nhiều câu hỏi ở đây về việc nhập tương đối vào python 3, nhưng sau khi trải qua nhiều câu hỏi, tôi vẫn không tìm thấy câu trả lời cho vấn đề của mình. Vì vậy, đây là câu hỏi.

Tôi có một gói được hiển thị dưới đây

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

và tôi có một dòng duy nhất trong test.py:

from ..A import foo

Bây giờ, tôi đang ở trong thư mục của packagevà tôi chạy

python -m test_A.test

Tôi nhận được tin nhắn

"ValueError: attempted relative import beyond top-level package"

nhưng nếu tôi ở trong thư mục mẹ của package, ví dụ: tôi chạy:

cd ..
python -m package.test_A.test

mọi thứ đều ổn.

Bây giờ câu hỏi của tôi là: khi tôi ở trong thư mục packagevà tôi chạy mô-đun bên trong gói phụ test_A test_A.test, dựa trên sự hiểu biết của tôi, ..Achỉ tăng một cấp, vẫn nằm trong packagethư mục, tại sao nó lại đưa ra thông báo beyond top-level package. Chính xác lý do gây ra thông báo lỗi này là gì?


49
bài đăng đó không giải thích được lỗi "vượt cấp cao nhất" của tôi
tạm trú

4
Tôi có một suy nghĩ ở đây, vì vậy khi chạy thử nghiệm cấp độ bạn nhập gói.
trú ẩn

2
Tôi hứa bạn sẽ hiểu mọi thứ về nhập khẩu tương đối sau khi xem câu trả lời này stackoverflow.com/a/14132912/8682868 .
pzjz Lý do

xem ValueError: đã cố gắng nhập tương đối ngoài gói cấp cao nhất để được giải thích chi tiết về vấn đề này.
napuzba

Có cách nào để tránh nhập khẩu tương đối? Chẳng hạn như cách PyDev trong Eclipse nhìn thấy tất cả các gói trong <PydevProject> / src?
Mushu909

Câu trả lời:


172

EDIT: Có câu trả lời tốt hơn / mạch lạc hơn cho câu hỏi này trong các câu hỏi khác:


Tại sao nó không hoạt động? Đó là vì python không ghi lại nơi gói được tải từ đó. Vì vậy, khi bạn làm python -m test_A.test, về cơ bản nó chỉ loại bỏ kiến ​​thức test_A.testthực sự được lưu trữ trong package(tức packagelà không được coi là một gói). Cố gắng from ..A import foolà cố gắng truy cập thông tin mà nó không còn nữa (ví dụ: các thư mục anh chị em của một vị trí được tải). Nó có khái niệm tương tự như cho phép from ..os import pathtrong một tập tin math. Điều này sẽ là xấu bởi vì bạn muốn các gói là khác biệt. Nếu họ cần sử dụng thứ gì đó từ gói khác, thì họ nên tham khảo chúng trên toàn cầu from os import pathvà để python tìm ra nơi đó với $PATH$PYTHONPATH.

Khi bạn sử dụng python -m package.test_A.test, sau đó sử dụng from ..A import foogiải quyết tốt vì nó theo dõi những gì trong packagevà bạn chỉ truy cập vào một thư mục con của một vị trí được tải.

Tại sao python không coi thư mục làm việc hiện tại là một gói? KHÔNG CLUE , nhưng nó sẽ hữu ích.


2
Tôi đã chỉnh sửa câu trả lời của mình để đề cập đến một câu trả lời tốt hơn cho một câu hỏi tương tự như vậy. Chỉ có cách giải quyết. Điều duy nhất tôi thực sự thấy công việc là những gì OP đã làm, đó là sử dụng -mcờ và chạy từ thư mục trên.
Multihunter

1
Cần lưu ý rằng câu trả lời này , từ liên kết được đưa ra bởi Multihunter, không liên quan đến sys.pathhack, nhưng việc sử dụng setuptools , theo tôi thì thú vị hơn nhiều.
Angelo Cardellicchio

157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Thử cái này. Đã làm cho tôi.


10
Umm ... làm thế nào woudl công việc này? Mỗi tập tin thử nghiệm sẽ có điều này?
George Mauer

Vấn đề ở đây là nếu, ví dụ, A/bar.pytồn tại và trong foo.pybạn from .bar import X.
dùng1834164

9
Tôi đã phải xóa .. khỏi "từ ..A nhập ..." sau khi thêm sys.path.append ("..")
Jake OPJ

2
Nếu tập lệnh được thực thi từ bên ngoài thư mục, nó sẽ không hoạt động. Thay vào đó, bạn phải điều chỉnh câu trả lời này để chỉ định đường dẫn tuyệt đối của tập lệnh đã nói .
Manavalan Gajapathy

đây là tùy chọn tốt nhất, ít phức tạp nhất
Alex R

43

Giả định:
Nếu bạn đang ở trong packagethư mục, Atest_Alà các gói riêng biệt.

Kết luận:
..Anhập khẩu chỉ được phép trong một gói.

Lưu ý thêm:
Làm cho việc nhập tương đối chỉ khả dụng trong các gói là hữu ích nếu bạn muốn buộc các gói đó có thể được đặt trên bất kỳ đường dẫn nào nằm trên đó sys.path.

BIÊN TẬP:

Tôi có phải là người duy nhất nghĩ rằng điều này là điên rồ không!? Tại sao trên thế giới thư mục làm việc hiện tại không được coi là một gói? - Đa tay

Thư mục làm việc hiện tại thường nằm trong sys.path. Vì vậy, tất cả các tập tin có thể nhập khẩu. Đây là hành vi kể từ Python 2 khi các gói chưa tồn tại. Làm cho thư mục đang chạy trở thành một gói sẽ cho phép nhập các mô-đun là "nhập .A" và là "nhập A", sau đó sẽ là hai mô-đun khác nhau. Có lẽ đây là một sự không nhất quán để xem xét.


86
Tôi có phải là người duy nhất nghĩ rằng điều này là điên rồ không!? Tại sao trên thế giới thư mục đang chạy không được coi là một gói?
Multihunter

13
Không chỉ là điên rồ, điều này là vô ích ... vậy làm thế nào để bạn chạy thử nghiệm sau đó? Rõ ràng điều OP đã hỏi và tại sao tôi chắc chắn nhiều người cũng ở đây.
George Mauer

Thư mục đang chạy thường nằm trong sys.path. Vì vậy, tất cả các tập tin có thể nhập khẩu. Đây là hành vi kể từ Python 2 khi các gói chưa tồn tại. - chỉnh sửa câu trả lời.
Người dùng

Tôi không tuân theo sự mâu thuẫn. Hành vi của python -m package.test_A.testdường như làm những gì được mong muốn, và lập luận của tôi là đó phải là mặc định. Vì vậy, bạn có thể cho tôi một ví dụ về sự không nhất quán này?
Multihunter

Tôi thực sự đang suy nghĩ, có một yêu cầu tính năng cho điều này? Điều này thực sự điên rồ. Phong cách C / C ++ #includesẽ rất hữu ích!
Nicholas Humphrey

29

Không có giải pháp nào trong số này làm việc cho tôi trong 3.6, với cấu trúc thư mục như:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

Mục tiêu của tôi là nhập từ module1 vào module2. Điều cuối cùng làm việc cho tôi là, thật kỳ lạ:

import sys
sys.path.append(".")

Lưu ý dấu chấm đơn trái ngược với các giải pháp hai dấu chấm được đề cập cho đến nay.


Chỉnh sửa: Sau đây đã giúp làm rõ điều này cho tôi:

import os
print (os.getcwd())

Trong trường hợp của tôi, thư mục làm việc (bất ngờ) là gốc của dự án.


2
nó hoạt động cục bộ nhưng không hoạt động trên ví dụ aws ec2, nó có ý nghĩa gì không?
thebeancount

Điều này cũng làm việc cho tôi - trong trường hợp của tôi, thư mục làm việc cũng giống như root dự án. Tôi đã sử dụng một phím tắt chạy từ một trình soạn thảo lập trình (TextMate)
JeremyDoulass

@thebeancount Cùng! Hoạt động cục bộ trên máy mac của tôi nhưng không hoạt động trên ec2, sau đó tôi nhận ra rằng tôi đang chạy lệnh trong một thư mục con trên ec2 và chạy nó tại root cục bộ. Khi tôi chạy nó từ root trên ec2, nó hoạt động.
Logan Dương

Điều này cũng làm việc cho tôi nhiều đánh giá cao. Từ phương thức sys đó, giờ đây tôi có thể gọi gói mà không cần ".."
RamWill

sys.path.append(".")làm việc vì bạn đang gọi nó trong thư mục mẹ, lưu ý rằng .luôn đại diện cho thư mục nơi bạn chạy lệnh python.
KevinZhou

13

from package.A import foo

Tôi nghĩ nó rõ ràng hơn

import sys
sys.path.append("..")

4
chắc chắn nó dễ đọc hơn nhưng vẫn cần sys.path.append(".."). thử nghiệm trên python 3.6
MFA

Tương tự như câu trả lời cũ hơn
nrofis

12

Như câu trả lời phổ biến nhất cho thấy, về cơ bản là vì PYTHONPATHhoặc sys.pathbao gồm .nhưng không phải đường dẫn đến gói của bạn. Và nhập tương đối liên quan đến thư mục làm việc hiện tại của bạn, không phải tệp nơi nhập xảy ra; kỳ quặc

Bạn có thể khắc phục điều này bằng cách trước tiên thay đổi nhập tương đối của bạn thành tuyệt đối và sau đó bắt đầu bằng:

PYTHONPATH=/path/to/package python -m test_A.test

HOẶC buộc đường dẫn python khi được gọi theo cách này, bởi vì:

Với python -m test_A.testbạn đang thực hiệntest_A/test.py với __name__ == '__main__'__file__ == '/absolute/path/to/test_A/test.py'

Điều đó có nghĩa là trong test.pybạn có thể sử dụng importbán bảo vệ tuyệt đối của mình trong điều kiện trường hợp chính và cũng thực hiện một số thao tác đường dẫn Python một lần:

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())

8

Chỉnh sửa: 2020-05-08: Có vẻ như trang web tôi trích dẫn không còn được kiểm soát bởi người đã viết lời khuyên, vì vậy tôi đang xóa liên kết đến trang web. Cảm ơn đã cho tôi biết baxx.


Nếu ai đó vẫn đang vật lộn một chút sau khi đã có câu trả lời tuyệt vời, tôi đã tìm thấy lời khuyên trên một trang web không còn khả dụng.

Trích dẫn cần thiết từ trang web tôi đã đề cập:

"Điều tương tự có thể được chỉ định theo chương trình theo cách này:

nhập khẩu hệ thống

sys.path.append ('..')

Tất nhiên mã ở trên phải được viết trước khi nhập khác câu lệnh .

Rõ ràng là nó phải theo cách này, suy nghĩ về nó sau khi thực tế. Tôi đã cố gắng sử dụng sys.path.append ('..') trong các thử nghiệm của mình, nhưng gặp phải vấn đề được đăng bởi OP. Bằng cách thêm định nghĩa nhập và sys.path trước các lần nhập khác, tôi đã có thể giải quyết vấn đề.


liên kết bạn đăng là chết.
baxx

Cảm ơn vì đã cho tôi biết. Có vẻ như tên miền không còn được kiểm soát bởi cùng một người. Tôi đã xóa liên kết.
Mierpo

5

nếu bạn có một __init__.pythư mục phía trên, bạn có thể khởi tạo quá trình nhập như import file/path as aliastrong tệp init đó. Sau đó, bạn có thể sử dụng nó trên các tập lệnh thấp hơn như:

import alias

0

Theo ý kiến ​​khiêm tốn của tôi, tôi hiểu câu hỏi này theo cách này:

[CASE 1] Khi bạn bắt đầu nhập tuyệt đối như

python -m test_A.test

hoặc là

import test_A.test

hoặc là

from test_A import test

thực tế, bạn đang đặt neo nhập khẩu thành test_A, nói cách khác, gói cấp cao nhất là test_A. Vì vậy, khi chúng tôi có test.py làmfrom ..A import xxx , bạn sẽ thoát khỏi neo và Python không cho phép điều này.

[CASE 2] Khi bạn làm

python -m package.test_A.test

hoặc là

from package.test_A import test

neo của bạn trở thành package, do đó, package/test_A/test.pyviệc thực from ..A import xxxhiện không thoát khỏi neo (vẫn trong packagethư mục) và Python vui vẻ chấp nhận điều này.

Nói ngắn gọn:

  • Nhập tuyệt đối thay đổi neo hiện tại (= xác định lại gói cấp cao nhất là gì);
  • Nhập khẩu tương đối không thay đổi neo mà chỉ giới hạn ở nó.

Hơn nữa, chúng ta có thể sử dụng tên mô-đun đủ điều kiện (FQMN) để kiểm tra vấn đề này.

Kiểm tra FQMN trong từng trường hợp:

  • [CASE2] test.__name__=package.test_A.test
  • [CASE1] test.__name__=test_A.test

Vì vậy, đối với CASE2, một from .. import xxxkết quả sẽ tạo ra một mô-đun mới với FQMN =package.xxx , có thể chấp nhận được.

Trong khi đối với CASE1, ..từ bên trong from .. import xxxsẽ nhảy ra khỏi nút bắt đầu (neo) của test_Avà điều này KHÔNG được Python cho phép.


2
Đây là cách phức tạp hơn nó cần phải được. Quá nhiều cho Zen của Python.
AtilioA

0

Không chắc chắn trong python 2.x nhưng trong python 3.6, giả sử bạn đang cố chạy toàn bộ bộ, bạn chỉ cần sử dụng -t

-t, --top-level-thư mục thư mục Thư mục cấp cao nhất của dự án (mặc định để bắt đầu thư mục)

Vì vậy, trên một cấu trúc như

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

Ví dụ, người ta có thể sử dụng:

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

Và vẫn nhập my_module.my_classmà không có bộ phim truyền hình lớ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.