it-swarm-vi.tech

Làm thế nào để nhập khẩu tương đối trong Python?

Hãy tưởng tượng cấu trúc thư mục này:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

Tôi đang mã hóa mod1 và tôi cần nhập một cái gì đó từ mod2. Tôi nên làm thế nào?

Tôi đã thử from ..sub2 import mod2 nhưng tôi nhận được "Đã nhập tương đối trong gói không".

Tôi đã đi vòng quanh nhưng chỉ thấy các bản hack "sys.path". Có cách nào sạch không?


Chỉnh sửa: tất cả __init__.py của tôi hiện đang trống

Chỉnh sửa 2: Tôi đang cố gắng thực hiện việc này vì sub2 chứa các lớp được chia sẻ trên các gói phụ (sub1, subX, v.v.).

Edit3: Hành vi tôi đang tìm kiếm giống như được mô tả trong PEP 366 (cảm ơn John B)

487
Joril

Mọi người dường như muốn nói với bạn những gì bạn nên làm hơn là chỉ trả lời câu hỏi.

Vấn đề là bạn đang chạy mô-đun dưới dạng '__main__' bằng cách chuyển mod1.py làm đối số cho trình thông dịch.

Từ PEP 328 :

Nhập khẩu tương đối sử dụng thuộc tính __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ụ: tê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 nằm ở đâu trên hệ thống tệp.

Trong Python 2.6, họ đang thêm khả năng tham chiếu các mô-đun so với mô-đun chính. PEP 366 mô tả sự thay đổi.

Cập nhật : Theo Nick Coghlan, giải pháp thay thế được đề xuất là chạy mô-đun bên trong gói bằng cách sử dụng công tắc -m.

306
John B
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Bạn chạy python main.py.
  2. main.py hiện: import app.package_a.module_a
  3. module_a.py không import app.package_b.module_b

Hoặc 2 hoặc 3 có thể sử dụng: from app.package_a import module_a

Điều đó sẽ hoạt động miễn là bạn có app trong PYTHONPATH của bạn. main.py có thể ở bất cứ đâu sau đó.

Vì vậy, bạn viết setup.py để sao chép (cài đặt) toàn bộ gói ứng dụng và gói phụ vào các thư mục python của hệ thống đích và main.py để nhắm mục tiêu các thư mục script của hệ thống.

116
nosklo

Đây là giải pháp phù hợp với tôi:

Tôi thực hiện nhập tương đối dưới dạng from ..sub2 import mod2 và sau đó, nếu tôi muốn chạy mod1.py thì tôi chuyển đến thư mục mẹ của app và chạy mô-đun bằng cách sử dụng chuyển đổi python là python -m app.sub1.mod1.

Lý do thực sự tại sao vấn đề này xảy ra với nhập khẩu tương đối, là nhập tương đối hoạt động bằng cách lấy thuộc tính __name__ của mô-đun. Nếu mô-đun đang được chạy trực tiếp, thì __name__ được đặt thành __main__ và nó không chứa bất kỳ thông tin nào về cấu trúc gói. Và, đó là lý do tại sao python phàn nàn về lỗi relative import in non-package.

Vì vậy, bằng cách sử dụng công tắc -m, bạn cung cấp thông tin cấu trúc gói cho python, thông qua đó nó có thể giải quyết việc nhập tương đối thành công.

Tôi đã gặp vấn đề này nhiều lần trong khi nhập khẩu tương đối. Và, sau khi đọc tất cả các câu trả lời trước đó, tôi vẫn không thể tìm ra cách giải quyết nó, một cách sạch sẽ, mà không cần phải đặt mã soạn sẵn trong tất cả các tệp. (Mặc dù một số ý kiến ​​thực sự hữu ích, cảm ơn @ncoghlan và @XiongChiamiov)

Hy vọng điều này sẽ giúp ai đó đang chiến đấu với vấn đề nhập khẩu tương đối, bởi vì trải qua PEP thực sự không vui.

114
Pankaj

"Guido xem các tập lệnh đang chạy trong một gói dưới dạng chống mẫu" (bị từ chối PEP-3122 )

Tôi đã dành rất nhiều thời gian để cố gắng tìm giải pháp, đọc các bài đăng liên quan ở đây trên Stack Overflow và tự nhủ "phải có cách tốt hơn!". Hình như không có.

46
lesnik

Điều này đã được giải quyết 100%:

  • ứng dụng/[.___.]
    • chính
  • cài đặt/[.___.]
    • local_setings.py

Nhập cài đặt/local_setting.py trong ứng dụng/main.py:

chính:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')
27
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

Tôi đang sử dụng đoạn mã này để nhập các mô-đun từ các đường dẫn, hy vọng điều đó sẽ giúp

24
iElectric

giải thích về câu trả lời nosklo's với các ví dụ

lưu ý: tất cả các tệp __init__.py đều trống.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

ứng dụng/gói_a/fun_a.py

def print_a():
    print 'This is a function in dir package_a'

ứng dụng/gói_b/fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

chính

from app.package_b import fun_b
fun_b.print_b()

nếu bạn chạy $ python main.py thì nó trả về:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.txt không: from app.package_b import fun_b
  • fun_b.py không from app.package_a.fun_a import print_a

vì vậy tệp trong thư mục package_b tệp được sử dụng trong thư mục package_a, đó là những gì bạn muốn. Đúng??

21
suhailvs

Thật không may, đây là một hack sys.path, nhưng nó hoạt động khá tốt.

Tôi gặp vấn đề này với một lớp khác: Tôi đã có một mô-đun của tên được chỉ định, nhưng đó là mô-đun sai.

những gì tôi muốn làm là như sau (mô-đun tôi đang làm việc là mô-đun3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Lưu ý rằng tôi đã cài đặt mymodule, nhưng trong cài đặt của tôi, tôi không có "mymodule1"

và tôi sẽ nhận được một ImportError vì nó đang cố gắng nhập từ các mô-đun đã cài đặt của tôi.

Tôi đã thử làm một sys.path.append và điều đó không hiệu quả. Những gì đã làm là một sys.path.insert

if __== '__main__':
    sys.path.insert(0, '../..')

Vì vậy, một loại hack, nhưng có tất cả để làm việc! Vì vậy, hãy ghi nhớ, nếu bạn muốn quyết định của mình ghi đè các đường dẫn khác thì bạn cần sử dụng sys.path.insert (0, tên đường dẫn) để làm cho nó hoạt động! Đây là một điểm dính rất khó chịu đối với tôi, mọi người nói rằng sử dụng chức năng "chắp thêm" vào sys.path, nhưng nó không hoạt động nếu bạn đã có một mô-đun được xác định (tôi thấy đó là hành vi rất lạ)

11
Garrett Berg

Hãy để tôi đặt nó ở đây để tham khảo của riêng tôi. Tôi biết rằng đó không phải là mã Python tốt, nhưng tôi cần một kịch bản cho một dự án tôi đang thực hiện và tôi muốn đặt tập lệnh vào thư mục scripts.

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
10
milkypostman

Như @EvgeniSergeev nói trong các nhận xét cho OP, bạn có thể nhập mã từ tệp .py tại một vị trí tùy ý với:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

Cái này được lấy từ cái này SO answer .

8
LondonRob

Hãy xem http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports . Bạn có thể làm

from .mod1 import stuff
4
mossplix

Từ tài liệu Python ,

Trong Python 2.5, bạn có thể chuyển hành vi nhập khẩu thành nhập tuyệt đối bằng cách sử dụ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 string sẽ luôn tìm thấy phiên bản thư viện tiêu chuẩn. Nó có đề xuất rằng người dùng nên bắt đầu sử dụng nhập tuyệt đối càng nhiều càng tốt, vì vậy, LÊNH nên bắt đầu viết from pkg import string trong mã của bạn

2
jung rhew

Trên hết những gì John B đã nói, có vẻ như việc đặt biến __package__ sẽ giúp ích, thay vì thay đổi __main__ có thể làm hỏng những thứ khác. Nhưng theo như tôi có thể kiểm tra, nó không hoàn toàn hoạt động như bình thường.

Tôi có cùng một vấn đề và cả PEP 328 hoặc 366 đều không giải quyết được hoàn toàn vấn đề, vì cả hai, vào cuối ngày, cần có phần đầu của gói được đưa vào sys.path, theo như tôi có thể hiểu.

Tôi cũng nên đề cập rằng tôi đã không tìm thấy cách định dạng chuỗi nên đi vào các biến đó. Có phải là "package_head.subfolder.module_name" hay không?

1
Gabriel

Tôi thấy việc đặt biến môi trường "PYTHONPATH" dễ dàng hơn vào thư mục trên cùng:

bash$ export PYTHONPATH=/PATH/TO/APP

sau đó:

import sub1.func1
#...more import

tất nhiên, PYTHONPATH là "toàn cầu", nhưng nó chưa gây rắc rối cho tôi.

1
Andrew_1510