Nhập khẩu tương đối trong Python 3


714

Tôi muốn nhập một hàm từ một tệp khác trong cùng thư mục.

Đôi khi nó hoạt động với tôi from .mymodule import myfunctionnhưng đôi khi tôi nhận được:

SystemError: Parent module '' not loaded, cannot perform relative import

Đôi khi nó hoạt động với from mymodule import myfunction, nhưng đôi khi tôi cũng nhận được:

SystemError: Parent module '' not loaded, cannot perform relative import

Tôi không hiểu logic ở đây và tôi không thể tìm thấy bất kỳ lời giải thích nào. Điều này trông hoàn toàn ngẫu nhiên.

Ai đó có thể giải thích cho tôi những gì logic đằng sau tất cả điều này?


76
Điều này có nghĩa là bạn đang chạy một mô-đun bên trong gói dưới dạng tập lệnh. Chỉ chạy tập lệnh từ bên ngoài gói.
Martijn Pieters

3
Có lẽ bạn nên xác định các điều kiện bạn có 'đôi khi' bạn đề cập. Tôi hiểu bạn không có nghĩa là bạn có lỗi ngẫu nhiên.
joaquin

15
@MartijnPieters: đôi khi, thật không may, mô-đun này cần phải nằm trong gói và đôi khi nó cũng cần được chạy như một tập lệnh. Bất cứ ý tưởng làm thế nào tôi có thể đạt được điều đó?
John Smith Tùy chọn

22
@JohnSmithOptional: Trộn các tập lệnh bên trong các gói là khó khăn và nên tránh nếu có thể. Thay vào đó, hãy sử dụng tập lệnh bao bọc nhập gói và chạy chức năng 'scripty' của bạn.
Martijn Pieters

3
Có vẻ không may. Tôi đã tạo một mô-đun lõi với các lớp / phương thức có thể phân tích / phân tích một loại tệp nhất định và tôi cũng có (chủ yếu là cho riêng mình) các mô-đun thứ cấp và các tập lệnh nhập nó - chúng có thể xoa bóp / chuyển đổi các tệp đó. Nhưng tôi cũng muốn có thể trao tập tin lõi đơn đó (không phải toàn bộ gói phức tạp) cho người dùng cuối để họ có thể dễ dàng đặt nó bên cạnh tệp của họ và chạy nó. Trong "chế độ tập lệnh" đó, nó phân tích và phân tích tệp và mã hóa, kiểm tra các trường / giá trị / ký tự đặc biệt khác nhau và đưa ra báo cáo. Nhưng nó không thực sự sửa đổi các tập tin. Chống hoa văn?
Jon Coombs

Câu trả lời:


528

thật không may, đôi khi mô-đun này cần phải nằm trong gói và đôi khi nó cũng cần được chạy như một tập lệnh. Bất cứ ý tưởng làm thế nào tôi có thể đạt được điều đó?

Nó khá phổ biến để có một bố cục như thế này ...

main.py
mypackage/
    __init__.py
    mymodule.py
    myothermodule.py

... với một cái mymodule.pynhư thế này ...

#!/usr/bin/env python3

# Exported function
def as_int(a):
    return int(a)

# Test function for module  
def _test():
    assert as_int('1') == 1

if __name__ == '__main__':
    _test()

... myothermodule.pynhư thế này ...

#!/usr/bin/env python3

from .mymodule import as_int

# Exported function
def add(a, b):
    return as_int(a) + as_int(b)

# Test function for module  
def _test():
    assert add('1', '1') == 2

if __name__ == '__main__':
    _test()

... và main.pynhư thế này ...

#!/usr/bin/env python3

from mypackage.myothermodule import add

def main():
    print(add('1', '1'))

if __name__ == '__main__':
    main()

... hoạt động tốt khi bạn chạy main.pyhoặc mypackage/mymodule.py, nhưng không thành công mypackage/myothermodule.py, do nhập tương đối ...

from .mymodule import as_int

Cách bạn chạy nó là ...

python3 -m mypackage.myothermodule

... nhưng nó hơi dài dòng và không kết hợp tốt với dòng shebang như thế nào #!/usr/bin/env python3.

Cách khắc phục đơn giản nhất trong trường hợp này, giả sử tên mymodulenày là duy nhất trên toàn cầu, sẽ là tránh sử dụng nhập khẩu tương đối và chỉ sử dụng ...

from mymodule import as_int

... mặc dù, nếu nó không phải là duy nhất hoặc cấu trúc gói của bạn phức tạp hơn, bạn sẽ cần bao gồm thư mục chứa thư mục gói của bạn PYTHONPATHvà thực hiện như thế này ...

from mypackage.mymodule import as_int

... hoặc nếu bạn muốn nó hoạt động "ngoài luồng", trước tiên bạn có thể điền PYTHONPATHmã trong mã này ...

import sys
import os

PACKAGE_PARENT = '..'
SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))
sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))

from mypackage.mymodule import as_int

Đó là một nỗi đau, nhưng có một manh mối về lý do tại sao trong một email được viết bởi một Guido van Rossum nào đó ...

Tôi -1 về điều này và trên bất kỳ twiddlings đề xuất khác của __main__ máy móc. Trường hợp sử dụng duy nhất dường như đang chạy các tập lệnh xảy ra trong thư mục của mô-đun, thứ mà tôi luôn thấy là một phản mẫu. Để khiến tôi thay đổi suy nghĩ, bạn phải thuyết phục tôi rằng không phải vậy.

Cho dù chạy các kịch bản bên trong một gói phần mềm là một antipattern hoặc không phải là chủ quan, nhưng cá nhân tôi thấy nó thực sự hữu ích trong một gói Tôi có chứa một số wxPython widget tùy chỉnh, vì vậy tôi có thể chạy các kịch bản cho bất kỳ tập tin nguồn để hiển thị một wx.Framechỉ chứa phụ tùng đó cho mục đích thử nghiệm.


7
Một cách tốt hơn để nhận SCRIPTDIR được đưa ra trong một nhận xét về Nhập mô-đun từ một đường dẫn tương đối như os.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe())))thể bạn tự tin rằng mô-đun của bạn luôn luôn phù hợp filemà bạn cũng có thể sử dụng os.path.realpath(os.path.dirname(__file__)).
marcz

2
Bạn có thể mở rộng PYTHONPATH của mình bằng cách áp dụng đoạn mã ngắn hơn và dễ đọc hơn: sys.path.append( os.path.join( os.path.dirname(__file__), os.path.pardir ) )
Alex-Bogdanov

12
...which I've always seen as an antipattern.Tôi không thấy đó là một kiểu chống ... Có vẻ như nó sẽ cực kỳ tiện lợi khi chỉ cần nhập khẩu tương đối làm việc theo trực giác. Tôi chỉ muốn có thể nhập những thứ mà tôi biết là trong cùng một thư mục. Tôi tự hỏi lý do của anh ấy là gì
YungGun

9
Guido đình công một lần nữa: những thứ vô dụng có thể hữu ích. Vâng, điều đó sẽ không xảy ra nữa.
javadba

4
Đây là điều đáng buồn nhất tôi từng thấy về Python.
AtilioA

263

Giải trình

Từ PEP 328

Nhập khẩu tương đối sử dụng thuộc tính __name__ 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 '__main__') 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.

Tại một số điểm PEP 338 đã xung đột với PEP 328 :

... nhập khẩu tương đối dựa vào __name__ để xác định vị trí của mô-đun hiện tại trong phân cấp gói. Trong một mô-đun chính, giá trị của __name__ luôn là '__main__' , do đó, nhập khẩu tương đối rõ ràng sẽ luôn thất bại (vì chúng chỉ hoạt động cho một mô-đun bên trong một gói)

và để giải quyết vấn đề, PEP 366 đã giới thiệu biến cấp cao nhất __package__:

Bằng cách thêm thuộc tính cấp mô-đun mới, PEP này cho phép nhập tương đối tự động hoạt động nếu mô-đun được thực thi bằng cách sử dụng công tắc -m . Một lượng nhỏ mẫu soạn sẵn trong mô-đun sẽ cho phép nhập tương đối hoạt động khi tệp được thực thi theo tên. [...] Khi có [thuộc tính], nhập khẩu tương đối sẽ dựa trên thuộc tính này thay vì thuộc tính __name__ của mô-đun . [...] Khi mô-đun chính được chỉ định bởi tên tệp của nó, thì thuộc tính __package__ sẽ được đặt thành Không có . [...] Khi hệ thống nhập gặp một lần nhập tương đối rõ ràng trong mô-đun không có __package__ được đặt (hoặc được đặt thành Không), nó sẽ tính toán và lưu trữ giá trị chính xác (__name __. rpartition ('.') [0] cho các mô-đun thông thường__name__ cho các mô-đun khởi tạo gói)

(nhấn mạnh của tôi)

Nếu __name__'__main__', __name__.rpartition('.')[0]trả về chuỗi rỗng. Đây là lý do tại sao có chuỗi ký tự trống trong mô tả lỗi:

SystemError: Parent module '' not loaded, cannot perform relative import

Phần có liên quan của PyImport_ImportModuleLevelObjectchức năng của CPython :

if (PyDict_GetItem(interp->modules, package) == NULL) {
    PyErr_Format(PyExc_SystemError,
            "Parent module %R not loaded, cannot perform relative "
            "import", package);
    goto error;
}

CPython tăng ngoại lệ này nếu không thể tìm thấy package(tên của gói) trong interp->modules(có thể truy cập dưới dạng sys.modules). Vì sys.modules"một từ điển ánh xạ tên mô-đun thành các mô-đun đã được tải" , nên giờ đây rõ ràng mô-đun mẹ phải được nhập tuyệt đối rõ ràng trước khi thực hiện nhập tương đối .

Lưu ý: Bản vá từ vấn đề 18018 đã thêm một ifkhối khác , sẽ được thực hiện trước mã ở trên:

if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
    PyErr_SetString(PyExc_ImportError,
            "attempted relative import with no known parent package");
    goto error;
} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
    ...
*/

Nếu package(giống như trên) là chuỗi rỗng, thông báo lỗi sẽ là

ImportError: attempted relative import with no known parent package

Tuy nhiên, bạn sẽ chỉ thấy điều này trong Python 3.6 hoặc mới hơn.

Giải pháp số 1: Chạy tập lệnh của bạn bằng cách sử dụng -m

Hãy xem xét một thư mục (là một gói Python ):

.
├── package
│   ├── __init__.py
│   ├── module.py
│   └── standalone.py

Tất cả các tệp trong gói bắt đầu với cùng 2 dòng mã:

from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())

Tôi bao gồm hai dòng sau chỉ để làm cho trật tự của hoạt động rõ ràng. Chúng ta có thể bỏ qua chúng hoàn toàn, vì chúng không ảnh hưởng đến việc thực hiện.

__init__.pymodule.py chỉ chứa hai dòng đó (nghĩa là chúng hoàn toàn trống).

độc lập thêm vào các nỗ lực để nhập mô-đun thông qua nhập tương đối:

from . import module  # explicit relative import

Chúng tôi nhận thức rõ rằng /path/to/python/interpreter package/standalone.pysẽ thất bại. Tuy nhiên, chúng ta có thể chạy mô-đun với -mtùy chọn dòng lệnh sẽ "tìm kiếm sys.pathmô-đun được đặt tên và thực hiện nội dung của nó dưới dạng __main__mô-đun" :

vaultah@base:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/module.py
>>> __file__
'/home/vaultah/package/standalone.py'
>>> __package__
'package'
>>> # The __package__ has been correctly set and module.py has been imported.
... # What's inside sys.modules?
... import sys
>>> sys.modules['__main__']
<module 'package.standalone' from '/home/vaultah/package/standalone.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>

-mthực hiện tất cả các công cụ nhập cho bạn và tự động thiết lập __package__, nhưng bạn có thể tự làm điều đó trong

Giải pháp số 2: Đặt __package__ theo cách thủ công

Vui lòng coi nó như một bằng chứng về khái niệm hơn là một giải pháp thực tế. Nó không phù hợp để sử dụng trong mã thế giới thực.

PEP 366 có một cách giải quyết cho vấn đề này, tuy nhiên, nó chưa hoàn chỉnh, vì chỉ thiết lập __package__một mình là không đủ. Bạn sẽ cần nhập ít nhất N gói trước trong phân cấp mô-đun, trong đó N là số lượng thư mục mẹ (liên quan đến thư mục của tập lệnh) sẽ được tìm kiếm cho mô-đun được nhập.

Như vậy

  1. Thêm thư mục mẹ của tiền thân thứ N của mô-đun hiện tại vàosys.path

  2. Xóa thư mục của tệp hiện tại khỏi sys.path

  3. Nhập mô đun mẹ của mô đun hiện tại bằng tên đủ điều kiện

  4. Đặt __package__thành tên đủ điều kiện từ 2

  5. Thực hiện nhập tương đối

Tôi sẽ mượn các tệp từ Giải pháp số 1 và thêm một số gói con khác:

package
├── __init__.py
├── module.py
└── subpackage
    ├── __init__.py
    └── subsubpackage
        ├── __init__.py
        └── standalone.py

Lần này, độc lập sẽ nhập mô-đun từ gói gói bằng cách nhập tương đối sau

from ... import module  # N = 3

Chúng ta sẽ cần đặt trước dòng đó với mã soạn sẵn để làm cho nó hoạt động.

import sys
from pathlib import Path

if __name__ == '__main__' and __package__ is None:
    file = Path(__file__).resolve()
    parent, top = file.parent, file.parents[3]

    sys.path.append(str(top))
    try:
        sys.path.remove(str(parent))
    except ValueError: # Already removed
        pass

    import package.subpackage.subsubpackage
    __package__ = 'package.subpackage.subsubpackage'

from ... import module # N = 3

Nó cho phép chúng tôi thực hiện độc lập bằng tên tệp:

vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py

Một giải pháp tổng quát hơn được bọc trong một chức năng có thể được tìm thấy ở đây . Ví dụ sử dụng:

if __name__ == '__main__' and __package__ is None:
    import_parents(level=3) # N = 3

from ... import module
from ...module.submodule import thing

Giải pháp số 3: Sử dụng nhập khẩu tuyệt đối và setuptools

Các bước là -

  1. Thay thế nhập khẩu tương đối rõ ràng bằng nhập khẩu tuyệt đối tương đương

  2. Cài đặt packageđể làm cho nó có thể nhập được

Ví dụ, cấu trúc thư mục có thể như sau

.
├── project
│   ├── package
│   │   ├── __init__.py
│   │   ├── module.py
│   │   └── standalone.py
│   └── setup.py

nơi setup.py

from setuptools import setup, find_packages
setup(
    name = 'your_package_name',
    packages = find_packages(),
)

Phần còn lại của các tệp được mượn từ Giải pháp số 1 .

Cài đặt sẽ cho phép bạn nhập gói bất kể thư mục làm việc của bạn (giả sử sẽ không có vấn đề về đặt tên).

Chúng tôi có thể sửa đổi độc lập để sử dụng lợi thế này (bước 1):

from package import module  # absolute import

Thay đổi thư mục làm việc của bạn thành projectvà chạy /path/to/python/interpreter setup.py install --user( --usercài đặt gói trong thư mục gói trang web của bạn ) (bước 2):

vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user

Chúng ta hãy xác minh rằng giờ đây có thể chạy độc lập dưới dạng tập lệnh:

vaultah@base:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>

Lưu ý : Nếu bạn quyết định đi theo tuyến đường này, tốt hơn hết bạn nên sử dụng các môi trường ảo để cài đặt các gói riêng lẻ.

Giải pháp số 4: Sử dụng nhập khẩu tuyệt đối và một số mã soạn sẵn

Thành thật mà nói, việc cài đặt là không cần thiết - bạn có thể thêm một số mã soạn sẵn vào tập lệnh của mình để làm cho việc nhập tuyệt đối hoạt động.

Tôi sẽ mượn các tệp từ Giải pháp số 1 và thay đổi độc lập :

  1. Thêm thư mục mẹ của gói vào sys.path trước khi thử nhập bất cứ thứ gì từ gói bằng cách nhập tuyệt đối:

    import sys
    from pathlib import Path # if you haven't already done so
    file = Path(__file__).resolve()
    parent, root = file.parent, file.parents[1]
    sys.path.append(str(root))
    
    # Additionally remove the current file's directory from sys.path
    try:
        sys.path.remove(str(parent))
    except ValueError: # Already removed
        pass
  2. Thay thế nhập tương đối bằng nhập tuyệt đối:

    from package import module  # absolute import

độc lập chạy mà không có vấn đề:

vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>

Tôi cảm thấy rằng tôi nên cảnh báo bạn: cố gắng không làm điều này, đặc biệt nếu dự án của bạn có cấu trúc phức tạp.


Lưu ý phụ, PEP 8 khuyến nghị sử dụng nhập khẩu tuyệt đối, nhưng nói rằng trong một số trường hợp, nhập khẩu tương đối rõ ràng có thể chấp nhận được:

Nhập khẩu tuyệt đối được khuyến nghị, vì chúng thường dễ đọc hơn và có xu hướng hoạt động tốt hơn (hoặc ít nhất là đưa ra thông báo lỗi tốt hơn). [...] Tuy nhiên, nhập khẩu tương đối rõ ràng là một thay thế chấp nhận được đối với nhập tuyệt đối, đặc biệt là khi xử lý các bố cục gói phức tạp trong đó sử dụng nhập tuyệt đối sẽ không cần thiết.


3
Có thể đặt __package__thủ công nếu tên là __main__để giải quyết vấn đề?
Paulo Scardine

Cảm ơn, câu trả lời tốt đẹp! Tôi đã có thể tải mô-đun bằng cách sử dụng impmô-đun và thiết lập __package__tương ứng nhưng kết quả rõ ràng là một mô hình chống.
Paulo Scardine

Tôi nhận được lỗi AttributeError: 'PosixPath' object has no attribute 'path'.
người dùng

Cảm ơn đã trả lời nhanh chóng. Tôi đang sử dụng gói nltk, tôi đang gặp lỗi: `File" /usr/local/lib/python3.5/dist-packages/nltk/__init__.py ", dòng 115, trong <module> từ nltk.decorators nhập trang trí, ghi nhớ tệp "/usr/local/lib/python3.5/dist-packages/nltk/decorators.py", dòng 23, trong <module> sys.path = [p cho p trong sys.path nếu "nltk "không có trong p] Tệp" /usr/local/lib/python3.5/dist-packages/nltk/decorators.py ", dòng 23, trong <listcomp> sys.path = [p cho p trong sys.path if" nltk "không có trong p] TypeError: đối số của loại 'PosixPath' không thể lặp lại`
người dùng

1
Bạn cũng có thể nhập tệp theo đường dẫn tệp (cũng tương đối): docs.python.org/3/l Library / Khăn
Ctrl-C

86

Đặt cái này trong tệp __init__.py của gói của bạn :

# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))

Giả sử gói của bạn là như thế này:

├── project
   ├── package
      ├── __init__.py
      ├── module1.py
      └── module2.py
   └── setup.py

Bây giờ sử dụng nhập khẩu thường xuyên trong gói của bạn, như:

# in module2.py
from module1 import class1

Điều này hoạt động trong cả python 2 và 3.


1
nó có hoạt động không nếu chúng ta gói nó dưới dạng weel
Alex Punnen

1
Tôi cũng nghĩ rằng điều này xứng đáng nhiều phiếu hơn. Đặt điều này trong mỗi __init__.pycơ bản sẽ giải quyết tất cả các lỗi nhập tương đối.
frankliuao

3
Tôi không thể nói cho người khác, nhưng tôi có xu hướng tránh sửa đổi sys.pathvì tôi lo ngại nó có thể ảnh hưởng đến mã khác. (Một phần điều này là do tôi không biết những rắc rối về cách thức hoạt động của nó.)
pianoJames

@pianoJames Tôi biết ý của bạn là gì, điều này (dường như, sau rất nhiều lần vặn vẹo) việc sửa chữa ma thuật có vẻ hơi quá dễ dàng. Nhưng nó đã có tác dụng. Sẽ được quan tâm không biết từ những người biết rằng nếu điều này có tác dụng phụ tiêu cực.
Jon

Tôi đang sử dụng cái này bây giờ: và cho đến nay rất tốt.
javadba

37

Tôi gặp vấn đề này. Một cách giải quyết hack đang nhập thông qua một khối if / khác như sau:

#!/usr/bin/env python3
#myothermodule

if __name__ == '__main__':
    from mymodule import as_int
else:
    from .mymodule import as_int


# Exported function
def add(a, b):
    return as_int(a) + as_int(b)

# Test function for module  
def _test():
    assert add('1', '1') == 2

if __name__ == '__main__':
    _test()

29
Đó không phải là một giải pháp tốt đẹp. Ngoài ra, trần except:là xấu. sử dụng except ImportError:thay thế!
ThiefMaster

6
SystemErrorở đây (Py 3,4)
Avi

8
Đây không phải là một ý tưởng tồi tệ, nhưng sẽ tốt hơn nếu phát hiện nhập khẩu nào sẽ sử dụng thay vì thử / ngoại trừ. Một cái gì đó như if __name__ == '__main__': from mymod import as_int; else: from .mymod import as_int.
Perkins

@Perkins Chà ... trong hầu hết các trường hợp thì không . Tôi nghĩ rằng nhập khẩu tương đối có thể là ngoại lệ mặc dù.
wizzwizz4

8

Hy vọng rằng, điều này sẽ có giá trị với ai đó ngoài kia - tôi đã xem qua nửa tá bài đăng stackoverflow cố gắng tìm ra nhập khẩu tương đối tương tự như những gì được đăng ở trên. Tôi thiết lập mọi thứ theo đề nghị nhưng tôi vẫn nhấnModuleNotFoundError: No module named 'my_module_name'

Vì tôi chỉ đang phát triển cục bộ và chơi xung quanh, tôi đã không tạo / chạy một setup.pytệp. Tôi dường như cũng không đặt PYTHONPATH.

Tôi nhận ra rằng khi tôi chạy mã của mình như khi tôi kiểm tra trong cùng thư mục với mô-đun, tôi không thể tìm thấy mô-đun của mình:

$ python3 test/my_module/module_test.py                                                                                                               2.4.0
Traceback (most recent call last):
  File "test/my_module/module_test.py", line 6, in <module>
    from my_module.module import *
ModuleNotFoundError: No module named 'my_module'

Tuy nhiên, khi tôi chỉ định rõ ràng đường dẫn mọi thứ bắt đầu hoạt động:

$ PYTHONPATH=. python3 test/my_module/module_test.py                                                                                                  2.4.0
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s

OK

Vì vậy, trong trường hợp bất kỳ ai đã thử một vài đề xuất, hãy tin rằng mã của họ được cấu trúc chính xác và vẫn thấy mình trong tình huống tương tự như tôi thử một trong hai cách sau nếu bạn không xuất thư mục hiện tại sang PYTHONPATH của mình:

  1. Chạy mã của bạn và bao gồm rõ ràng đường dẫn như vậy: $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. Để tránh gọi PYTHONPATH=., hãy tạo một setup.pytệp có nội dung như sau và chạy python setup.py developmentđể thêm các gói vào đường dẫn:
# setup.py
from setuptools import setup, find_packages

setup(
    name='sample',
    packages=find_packages()
)

6

Tôi cần chạy python3 từ thư mục dự án chính để làm cho nó hoạt động.

Ví dụ, nếu dự án có cấu trúc sau:

project_demo/
├── main.py
├── some_package/
   ├── __init__.py
   └── project_configs.py
└── test/
    └── test_project_configs.py

Giải pháp

Tôi sẽ chạy python3 trong thư mục project_demo / và sau đó thực hiện một

from some_package import project_configs

4

Để khắc phục vấn đề này, tôi đã nghĩ ra một giải pháp với gói đóng gói lại , nó đã làm việc cho tôi một thời gian. Nó thêm thư mục trên vào đường dẫn lib:

import repackage
repackage.up()
from mypackage.mymodule import myfunction

Đóng gói lại có thể thực hiện nhập khẩu tương đối hoạt động trong nhiều trường hợp, sử dụng chiến lược thông minh (kiểm tra ngăn xếp cuộc gọi).


Cho đến nay giải pháp dễ nhất! Cảm ơn bạn!
CodingInCircles

1
Cảm ơn! Thay vì cố gắng đưa ra câu trả lời tốt nhất, tôi đã cố gắng đưa ra một câu trả lời có thể sử dụng :-)
fralau

3

nếu cả hai gói đều nằm trong đường dẫn nhập của bạn (sys.path) và mô-đun / lớp bạn muốn nằm trong example / example.py, thì để truy cập lớp mà không cần nhập tương đối:

from example.example import fkt

1

Tôi nghĩ giải pháp tốt nhất là tạo một gói cho mô-đun của bạn: Dưới đây là thông tin thêm về cách thực hiện.

Khi bạn có một gói bạn không cần phải lo lắng về việc nhập tương đối, bạn chỉ có thể thực hiện nhập tuyệt đối.


0

Tôi gặp một vấn đề tương tự: Tôi cần một dịch vụ Linux và plugin cgi sử dụng các hằng số chung để hợp tác. Cách 'tự nhiên' để làm điều này là đặt chúng trong init .py của gói, nhưng tôi không thể khởi động plugin cgi với tham số -m.

Giải pháp cuối cùng của tôi tương tự như Giải pháp số 2 ở trên:

import sys
import pathlib as p
import importlib

pp = p.Path(sys.argv[0])
pack = pp.resolve().parent

pkg = importlib.import_module('__init__', package=str(pack))

Nhược điểm là bạn phải tiền tố các hằng số (hoặc các hàm phổ biến) với pkg:

print(pkg.Glob)
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.