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


527

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 viết mã mod1và 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 mod2nhưng tôi nhận được "Đã nhập tương đối trong gói không".

Tôi googled xung quanh nhưng chỉ tìm thấy sys.pathhack "thao túng". Có cách nào sạch không?


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

Edit2: Tôi đang cố gắng để làm điều này vì sub2 chứa các lớp được chia sẻ trên các gói phụ ( sub1, subX, vv).

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


8
Tôi khuyên bạn nên cập nhật câu hỏi của mình để làm rõ hơn rằng bạn đang mô tả vấn đề được giải quyết trong PEP 366.
John B

2
Đó là một lời giải thích dài dòng nhưng kiểm tra ở đây: stackoverflow.com/a/10713254/1267156 Tôi đã trả lời một câu hỏi rất giống nhau. Tôi đã có vấn đề tương tự cho đến tối qua.
Sevvy325

3
Đối với những người muốn tải một mô-đun nằm ở một đường dẫn tùy ý, hãy xem điều này: stackoverflow.com/questions/67631/
Kẻ

2
Trên một lưu ý liên quan, Python 3 sẽ thay đổi cách xử lý nhập mặc định thành tuyệt đối theo mặc định; nhập khẩu tương đối sẽ phải được xác định rõ ràng.
Ross

Câu trả lời:


337

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 __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.

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.


2
Câu trả lời ở đây liên quan đến việc gây rối với sys.path tại mọi điểm vào chương trình của bạn. Tôi đoán đó là cách duy nhất để làm điều đó.
Nick Retallack

76
Giải pháp thay thế được đề xuất là chạy các mô-đun bên trong các gói bằng cách sử dụng công -mtắc, thay vì chỉ định trực tiếp tên tệp của chúng.
ncoghlan

127
Tôi không hiểu: câu trả lời ở đây là ở đâu? Làm thế nào có thể nhập các mô-đun trong một cấu trúc thư mục như vậy?
Tom

27
@Tom: Trong trường hợp này, mod1 sẽ from sub2 import mod2. Sau đó, để chạy mod1, từ trong ứng dụng, hãy làm python -m sub1.mod1.
Xiong Chiamiov

11
@XiongChiamiov: điều này có nghĩa là bạn không thể làm điều đó nếu con trăn của bạn được nhúng trong một ứng dụng, vì vậy bạn không có quyền truy cập vào các chuyển đổi dòng lệnh python?
LarsH

130

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

Tôi thực hiện nhập tương đối from ..sub2 import mod2 và sau đó, nếu tôi muốn chạy mod1.pythì tôi vào thư mục mẹ của appvà chạy mô-đun bằng cách sử dụng chuyển đổi python -m như 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 khẩu tương đối hoạt động bằng cách lấy thuộc __name__tính của mô-đun. Nếu mô-đun đang được chạy trực tiếp, thì nó __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ề relative import in non-packagelỗi này.

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.


9
Câu trả lời hay nhất IMHO: không chỉ giải thích tại sao OP gặp vấn đề, mà còn tìm ra cách giải quyết mà không thay đổi cách các mô-đun của anh ấy nhập khẩu . Sau đó, nhập khẩu tương đối của OP là tốt. Thủ phạm là sự thiếu quyền truy cập vào các gói bên ngoài khi trực tiếp chạy dưới dạng tập lệnh, một cái gì đó -mđược thiết kế để giải quyết.
MestreLion

26
Cũng lưu ý: câu trả lời này là 5 năm sau câu hỏi. Những tính năng này không có sẵn tại thời điểm đó.
JeremyKun

1
Nếu bạn muốn nhập một mô-đun từ cùng một thư mục bạn có thể làm from . import some_module.
Rotareti

124
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 đi python main.py.
  2. main.py làm: import app.package_a.module_a
  3. module_a.py làm 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ó apptrong PYTHONPATH của bạn. main.pycó thể là bất cứ nơi nào sau đó.

Vì vậy, bạn viết một setup.pybản sao để cài đặt (cài đặt) toàn bộ gói ứng dụng và các gói con 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.


3
Câu trả lời tuyệt vời. Có cách nào để nhập theo cách đó mà không cần cài đặt gói trong PYTHONPATH không?
auraham


6
sau đó, một ngày, cần thay đổi tên của ứng dụng thành test_app. chuyện gì sẽ xảy ra? Bạn sẽ cần thay đổi tất cả các mã nguồn, nhập app.package_b.module_b -> test_app.package_b.module_b. đây hoàn toàn là cách thực hành BAD ... Và chúng ta nên thử sử dụng nhập tương đối trong gói.
Spybdai

49

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

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ó.


10
Lưu ý: Đã đề cập đến pep-366 (được tạo cùng thời gian với pep-3122 ) cung cấp các khả năng tương tự nhưng sử dụng cách triển khai tương thích ngược khác nhau, tức là nếu bạn muốn chạy mô-đun bên trong gói dưới dạng tập lệnh sử dụng nhập khẩu tương đối rõ ràng sau đó bạn có thể chạy nó bằng cách sử dụng -mswitch: python -m app.sub1.mod1hoặc gọi app.sub1.mod1.main()từ tập lệnh cấp cao nhất (ví dụ: được tạo từ entry_point của setuptools được xác định trong setup.py).
jfs

+1 để sử dụng setuptools và điểm nhập cảnh - đó là một cách thích hợp để thiết lập các tập lệnh sẽ được chạy từ bên ngoài, ở một vị trí được xác định rõ, trái ngược với việc hack PYTHONPATH
RecencyEffect

38

Đ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')

2
cảm ơn bạn! tất cả các ppl đã buộc tôi chạy kịch bản của mình một cách khác nhau thay vì nói cho tôi cách giải quyết nó trong kịch bản. Nhưng tôi đã phải thay đổi mã để sử dụng sys.path.insert(0, "../settings")và sau đófrom local_settings import *
Vit Bernatik

25
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


2
Tôi đang sử dụng đoạn mã này, kết hợp với mô-đun imp (như được giải thích ở đây [1]) để có hiệu quả tuyệt vời. [1]: stackoverflow.com/questions/1096216/ Cách
Xiong Chiamiov

7
Có lẽ, nên thay thế sys.path.append (path) bằng sys.path.insert (0, path) và sys.path [-1] bằng sys.path [0]. Nếu không, hàm sẽ nhập sai mô-đun, nếu đã có một mô-đun có cùng tên trong đường dẫn tìm kiếm. Ví dụ: nếu có "some.py" trong thư mục hiện tại, import_path ("/ import / some.py") sẽ nhập sai tệp.
Alex Che

Tôi đồng ý! Đôi khi nhập khẩu tương đối khác sẽ làm ưu tiên. Sử dụng sys.path.insert
iElectric

Làm thế nào bạn có thể sao chép hành vi của từ x nhập y (hoặc *)?
levesque

Không rõ ràng, vui lòng chỉ định sử dụng đầy đủ tập lệnh này để giải quyết vấn đề OP.
mrgloom

21

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

lưu ý: tất cả __init__.pycác tập tin đề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.pynó sẽ 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 tin trong thư mục package_bsử dụng tập tin trong thư mục package_a, đó là những gì bạn muốn. Đúng??


12

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 phải 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 __name__ == '__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 nhớ rằng, 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ạ)


sys.path.append('../')hoạt động tốt với tôi (Python 3.5.2)
Nister

Tôi nghĩ rằng điều này là tốt vì nó bản địa hóa hack để thực thi và không ảnh hưởng đến các mô-đun khác có thể phụ thuộc vào các gói của bạn.
Tom Russell

10

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 mà tôi đang thực hiện và tôi muốn đặt tập lệnh vào một scriptsthư mục.

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

9

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

import imp

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

Điều này được lấy từ câu trả lời SO này .



2

Từ tài liệu Python ,

Trong Python 2.5, bạn có thể chuyển hành vi nhập khẩu sang nhập tuyệt đối bằ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 stringsẽ luôn tìm thấy phiên bản của thư viện chuẩn. Chúng tôi khuyên 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 tốt nhất nên bắt đầu viết from pkg import stringmã của bạn


1

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.


Đây thực chất là cách virtualenvcho phép bạn quản lý báo cáo nhập khẩu của mình.
byxor

1

Trên hết những gì John B đã nói, có vẻ như việc đặt __package__biến 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 hay 366 đều không giải quyết được hoàn toàn vấn đề, vì cả hai, vào cuối ngày, đều cần người đứng đầu gói hàng đượ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 đó. Là nó "package_head.subfolder.module_name"hay là gì?


0

Bạn phải nối đường dẫn của mô-đun vào PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"

1
Điều này gần giống như thao túng sys.path, vì sys.pathđược khởi tạo từPYTHONPATH
Joril

@Joril Điều đó đúng nhưng sys.pathcần được mã hóa cứng trong mã nguồn trái ngược với PYTHONPATHbiến môi trường và có thể được xuất.
Giorgos Myrianthous
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.