2016-03-02 11 views
6

Đây là cấu trúc thư mục của tôi:Làm thế nào để cấu trúc dự án Python của tôi để cho phép các module tên được nhập khẩu từ các thư mục phụ

Projects 
    + Project_1 
    + Project_2 
    - Project_3 
     - Lib1 
      __init__.py # empty 
      moduleA.py 
     - Tests 
      __init__.py # empty 
      foo_tests.py 
      bar_tests.py 
      setpath.py 
     __init__.py  # empty 
     foo.py 
     bar.py 

Mục tiêu:

  1. Có một cấu trúc dự án có tổ chức
  2. Hãy có thể chạy độc lập từng tệp .py khi cần thiết
  3. Có thể tham chiếu/nhập cả hai anh chị em và anh em họ mô-đun
  4. Giữ tất cả các lệnh nhập/từ ở đầu mỗi tệp.

tôi đã đạt đượC# 1 bằng cách sử dụng cấu trúc trên

Tôi đã chủ yếu đạt được 2, 3 và 4 bằng cách làm như sau (theo khuyến cáo của this excellent guide)

Trong bất kỳ gói mà cần phải truy cập cha mẹ hoặc anh em họ module (ví dụ như thư mục thử nghiệm ở trên) tôi bao gồm một tập tin gọi là setpath.py trong đó có đoạn mã sau:

import os 
import sys 
sys.path.insert(0, os.path.abspath('..')) 

sys.path.insert(0, os.path.abspath('.')) 
sys.path.insert(0, os.path.abspath('...')) 

sau đó, trong mỗi module mà cần truy cập phụ huynh/anh em họ, như foo_tests.py, tôi có thể viết một danh sách sạch đẹp hàng nhập khẩu như sau:

import setpath  # Annoyingly, PyCharm warns me that this is an unused import statement 
import foo.py 

Bên setpath.py, chèn thứ hai và thứ ba là không thực sự cần thiết cho ví dụ này, nhưng được đưa vào như một bước xử lý sự cố .

Vấn đề của tôi là thao tác này chỉ hoạt động đối với các lần nhập trực tiếp tham chiếu tên mô-đun và không áp dụng cho các mục nhập tham chiếu đến gói. Ví dụ: bên trong bar_tests.py, cả hai câu lệnh bên dưới đều không hoạt động khi chạy bar_tests.py trực tiếp.

import setpath 

import Project_3.foo.py # Error 
from Project_3 import foo # Error 

Tôi nhận được lỗi "ImportError: No module named 'Project_3'".

Điều kỳ lạ là tôi có thể chạy tệp trực tiếp từ bên trong PyCharm và nó hoạt động tốt. Tôi biết rằng PyCharm đang thực hiện một số phép thuật đằng sau hậu trường với biến số Python Path để làm mọi thứ hoạt động, nhưng tôi không thể hiểu được nó là gì. Khi PyCharm đơn giản chạy python.exe và thiết lập một số biến môi trường, có thể sao chép hành vi này từ bên trong một tập lệnh Python.

Vì lý do không thực sự nảy mầm đối với câu hỏi này, tôi phải tham chiếu bar bằng cách sử dụng vòng loại Project_3.

Tôi đang mở cho bất kỳ giải pháp nào hoàn thành các giải pháp nêu trên trong khi vẫn đáp ứng các mục tiêu trước đó của tôi. Tôi cũng mở một cấu trúc thư mục thay thế nếu có một cấu trúc hoạt động tốt hơn. Tôi đã đọc số Python doc on imports và các gói nhưng vẫn bị mất. Tôi nghĩ rằng một đại lộ có thể có thể được đặt theo cách thủ công biến số __path__, nhưng tôi không chắc chắn một biến nào cần được thay đổi hoặc những gì cần thiết để thay đổi.

+0

Mặc dù tôi hoàn toàn đồng ý với câu trả lời @Blckknght (và trước khi anh ấy xuất bản, thậm chí bắt đầu viết tương tự trong cùng một giai điệu). Nó có thể đáng nói đến, trừ khi đó là một lỗi đánh máy khác, mô hình hiện tại của bạn có thể không hoạt động do sys.path.insert (0, os.path.abspath ('...')), như '...' không phải là một bộ chọn đường dẫn gốc hợp lệ. Ở đây bạn nên sử dụng ký hiệu hệ thống tập tin '../ ..' thông thường không phải là mô-đun cha của Python. – RobertT

+0

@RobertT Không thể tin được!Vì vậy, đơn giản, nhưng nó sửa chữa vấn đề và có vẻ như là điều này cho phép tôi thiết lập rễ tương đối cho cả hai gói và mô-đun. Tôi vẫn nghĩ rằng một cái gì đó mạnh mẽ hơn có thể được thực hiện bằng cách sử dụng các thư viện nhập khẩu, nhưng điều này không trả lời questio của tôi, đạt được các mục tiêu trên. Làm cho nó một câu trả lời xin vui lòng và tôi sẽ xem xét nó cho giải thưởng tiền thưởng. – BrianHVB

Trả lời

2

Các loại câu hỏi đó đủ điều kiện là "chủ yếu dựa trên ý kiến", vì vậy hãy để tôi chia sẻ ý kiến ​​của mình về cách tôi sẽ làm điều đó. Đầu tiên "có thể chạy độc lập từng tệp .py khi cần thiết": hoặc tệp là một mô-đun, vì vậy nó không nên được gọi trực tiếp hoặc thực thi độc lập, sau đó nhập phụ thuộc của nó bắt đầu từ cấp cao nhất (bạn có thể tránh nó trong mã hoặc thay vì di chuyển nó đến nơi phổ biến, bằng cách sử dụng setup.py entry_points, nhưng sau đó thực thi trước đây của bạn có hiệu quả chuyển đổi thành một mô-đun). Và có, nó là một trong những điểm yếu của mô hình mô-đun Python, gây ra hiểu lầm.

Thứ hai, sử dụng virtualenv (hoặc venv trong Python3) và đặt từng Project_x của bạn vào một riêng biệt. Tên dự án theo cách này sẽ không nằm trong đường dẫn của mô-đun Python.

Thứ ba, liên kết mà bạn đã cung cấp đề cập đến setup.py - bạn có thể sử dụng nó. Đặt mã tùy chỉnh của bạn vào Project_x/src/mylib1, tạo src/mylib1/setup.py và cuối cùng là các mô đun của bạn vào src/mylib1/mylib1/module.py. Sau đó, bạn có thể cài đặt mã của bạn bằng pip như bất kỳ gói nào khác (hoặc pip -e để bạn có thể làm việc trực tiếp trên mã mà không cần cài đặt lại mã, mặc dù không may có một số hạn chế).

Và cuối cùng, như bạn đã xác nhận trong nhận xét đã có;). Vấn đề với mô hình hiện tại của bạn là trong sys.path.insert(0, os.path.abspath('...')) bạn đã nhầm lẫn sử dụng ký pháp của mô-đun Python, không đúng cho đường dẫn hệ thống và phải được thay thế bằng '../..' để hoạt động như mong đợi.

1

Tôi nghĩ rằng mục tiêu của bạn không hợp lý.Cụ thể, mục tiêu số 2 là một vấn đề:

  1. Có thể chạy độc lập mỗi file py khi cần thiết

này không hoạt động tốt cho các module trong một gói . Ít nhất, không phải nếu bạn đang chạy các tệp .py một cách ngây thơ (ví dụ: với python foo_tests.py trên dòng lệnh). Khi bạn chạy các tệp theo cách đó, Python không thể biết được vị trí của hệ thống phân cấp gói.

Có hai lựa chọn thay thế có thể hoạt động. Tùy chọn đầu tiên là chạy tập lệnh của bạn từ thư mục cấp cao nhất (ví dụ: projects) bằng cách sử dụng cờ -m cho trình thông dịch để cung cấp cho nó đường dẫn rải rác đến mô-đun chính và sử dụng nhập khẩu tương đối rõ ràng để có các mô-đun anh chị em họ. Vì vậy, thay vì chạy trực tiếp python foo_tests.py, hãy chạy python -m project_3.tests.foo_tests từ thư mục projects (hoặc python -m tests.foo_tests từ trong vòng project_3 có lẽ) và có foo_tests.py sử dụng from .. import foo.

Tùy chọn khác (ít tốt) là thêm thư mục cấp cao nhất vào đường dẫn tìm kiếm mô-đun cài đặt Python trên cơ sở rộng hệ thống (ví dụ: thêm thư mục projects vào biến môi trường PYTHON_PATH) và sau đó sử dụng nhập tuyệt đối cho tất cả các mô-đun của bạn (ví dụ: import project3.foo). Đây chính là điều mà mô-đun setpath của bạn thực hiện, nhưng làm cho hệ thống rộng như một phần của cấu hình hệ thống của bạn, thay vì ở thời gian chạy, nó sạch hơn nhiều. Nó cũng tránh được nhiều tên mà setpath sẽ cho phép bạn sử dụng để nhập mô-đun (ví dụ: thử import foo_tests, tests.foo_tests và bạn sẽ nhận được hai riêng biệt bản sao của cùng một mô-đun).

+1

Cảm ơn bạn đã phản hồi, nhưng cả hai giải pháp đều yêu cầu các bước được thực hiện bên ngoài các tập lệnh python. Nói cách khác, không có cách nào để thực hiện chương trình một trong hai giải pháp từ bên trong một kịch bản Python, và nó có vẻ như phải có. Bạn nói rằng "Khi bạn chạy các tập tin theo cách đó, Python không thể biết được hệ thống phân cấp gói nên bắt đầu ở đâu." Nhưng nó sẽ có thể làm chính xác điều này, để cho Python biết một gói bắt đầu từ bên trong một kịch bản lệnh. Tôi nghĩ rằng đây là chính xác những gì các mô-đun nhập khẩu được cho, tôi chỉ không thể tìm ra cách sử dụng chúng để có được hành vi mong muốn. – BrianHVB

Các vấn đề liên quan