2017-04-13 26 views
8

Tôi đang cố gắng đóng gói dự án của mình để phân phối, nhưng tôi đang nhấn RuntimeWarning khi tôi chạy mô-đun.Cấu trúc dự án Python 3,6 dẫn đến RuntimeWarning

Tôi đã tìm thấy báo cáo lỗi trên Python mailing list cho biết rằng RuntimeWarning là hành vi mới được giới thiệu trong Python 3.5.2.

Đọc qua báo cáo lỗi, có vẻ như có một lần nhập đôi xảy ra và RuntimeWarning này chính xác khi cảnh báo người dùng. Tuy nhiên, tôi không thấy những thay đổi mà tôi cần phải thực hiện cho cấu trúc dự án của riêng mình để tránh vấn đề này.

Đây là dự án đầu tiên mà tôi đã cố gắng cấu trúc "chính xác". Tôi muốn có một bố trí gọn gàng cho khi tôi đẩy mã, và một cấu trúc dự án có thể được nhân bản và chạy dễ dàng bởi những người khác.

Tôi có cấu trúc của mình chủ yếu dựa trên http://docs.python-guide.org/en/latest/writing/structure/.

Tôi đã thêm chi tiết về ví dụ hoạt động tối thiểu bên dưới.

Để tái tạo vấn đề, tôi chạy file chính với python -m:

(py36) X:\test_proj>python -m proj.proj 
C:\Users\Matthew\Anaconda\envs\py36\lib\runpy.py:125: RuntimeWarning: 
'proj.proj' found in sys.modules after import of package 'proj', but prior 
to execution of 'proj.proj'; this may result in unpredictable behaviour 
    warn(RuntimeWarning(msg)) 
This is a test project.` 

Chạy thử nghiệm của tôi cũng tốt:

(py36) X:\test_proj>python -m unittest tests.test_proj 
This is a test project. 
. 
---------------------------------------------------------------------- 
Ran 1 test in 0.000s 

OK 

Một cấu trúc dự án để tái tạo vấn đề này như sau:

myproject/ 
    proj/ 
     __init__.py 
     proj.py 
    tests/ 
     __init__.py 
     context.py 
     test_proj.py 

Trong tệp proj/proj.py:

def main(): 
    print('This is a test project.') 
    raise ValueError 

if __name__ == '__main__': 
    main() 

Trong proj/__init__.py:

from .proj import main 

Trong tests/context.py:

import os 
import sys 
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 
import proj 

Cuối cùng, trong tests/test_proj.py:

import unittest 

from .context import proj 


class SampleTestCase(unittest.TestCase): 
    """Test case for this sample project""" 
    def test_raise_error(self): 
     """Test that we correctly raise an error.""" 
     with self.assertRaises(ValueError): 
      proj.main() 


if __name__ == '__main__': 
    unittest.main() 

bất cứ ai có thể giúp tôi sửa cấu trúc dự án của tôi để AV trong bối cảnh nhập khẩu kép này? Bất kỳ sự giúp đỡ này sẽ được đánh giá rất nhiều.

Trả lời

4

Nếu bạn có một cái nhìn tại double import trap bạn sẽ thấy điều này:

bẫy tiếp theo này tồn tại trong tất cả các phiên bản hiện tại của Python, bao gồm 3.3, và có thể được tóm tắt trong phương châm chung sau đây: “Không bao giờ thêm thư mục gói hoặc bất kỳ thư mục nào bên trong một gói, trực tiếp vào đường dẫn Python”.

Lý do đây là vấn đề là mỗi mô-đun trong thư mục đó tại là khả năng tiếp cận dưới hai tên gọi khác nhau: như là một mô-đun cấp cao nhất (từ thư mục trên sys.path) và như là một submodule của gói (nếu thư mục cấp cao hơn chứa gói cũng chính là trên sys.path).

Trong tests/context.py

remove: sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

mà có lẽ gây ra vấn đề và mã của bạn vẫn hoạt động như mong đợi.


Sửa do bình luận:

Bạn có thể thử và thay đổi một số bộ phận trong mã của bạn:

  1. proj/__init__.py Có thể hoàn toàn trống rỗng
  2. On test_proj.py nên thay đổi khẩu như sau:

    import unittest 
    
    from proj import proj 
    

PS: Tôi đã không thể tái tạo các cảnh báo trên Linux với mã ban đầu của bạn hoặc với các đề xuất của tôi trong hai.

+0

Cảm ơn câu trả lời của bạn. Thật không may, điều đó không giải quyết được vấn đề. Mặc dù gợi ý của bạn có ý nghĩa hoàn hảo nhưng tôi vẫn thấy thông báo chính xác sau khi xóa dòng 'sys.path.insert'. – Matthew

+0

Tôi đã thêm một đề xuất khác vào câu trả lời, có một cái nhìn. –

7

Đối với trường hợp đặc biệt này, các cảnh báo nhập khẩu tăng gấp đôi là do dòng này trong proj/__init__.py:

from .proj import main 

gì dòng đó có nghĩa là vào thời điểm thực hiện -m chuyển đổi kết thúc bước import proj, proj.projđã được nhập dưới dạng tác dụng phụ của việc nhập gói gốc.

Tránh cảnh báo

Để tránh các cảnh báo, bạn cần phải tìm một cách để đảm bảo rằng việc nhập gói phụ huynh không mặc nhiên nhập khẩu các gói được thực hiện với các -m switch.

Hai tùy chọn chính cho việc giải quyết đó là:

  1. Thả dòng from .proj import main (như @ John Moutafis gợi ý), giả định rằng có thể được thực hiện mà không vi phạm đảm bảo khả năng tương thích API; hoặc
  2. Xóa khối if __name__ == "__main__": từ proj submodule và thay thế bằng một file riêng biệt proj/__main__.py rằng chỉ cần làm:

    from .proj import main 
    main() 
    

Nếu bạn đi với phương án 2, sau đó dòng lệnh gọi trình cũng sẽ thay đổi chỉ là python -m proj, thay vì tham chiếu một mô-đun con.

Một biến thể tương thích ngược hơn của phương án 2 là thêm __main__.py mà không xóa các khối CLI từ submodule hiện tại, và đó có thể là một cách tiếp cận đặc biệt tốt khi kết hợp với DeprecationWarning:

if __name__ == "__main__": 
    import warnings 
    warnings.warn("use 'python -m proj', not 'python -m proj.proj'", DeprecationWarning) 
    main() 

Nếu proj/__main__.py đã được sử dụng cho một số mục đích khác, sau đó bạn cũng có thể làm những việc như thay thế python -m proj.proj với python -m proj.proj_cli, nơi proj/proj_cli.py trông giống như:

if __name__ != "__main__": 
    raise RuntimeError("Only for use with the -m switch, not as a Python API") 
from .proj import main 
main() 

Tại sao cảnh báo lại tồn tại?

Cảnh báo này được phát ra khi thực hiện -m chuyển đổi là về để đi và chạy mã một module đã nhập khẩu của lại trong module __main__, có nghĩa là bạn sẽ có hai bản sao riêng biệt của tất cả mọi thứ nó xác định - các lớp học, chức năng, vùng chứa, v.v.

Tùy thuộc vào chi tiết cụ thể của ứng dụng, điều này có thể hoạt động tốt (đó là lý do cảnh báo thay vì lỗi) hoặc có thể dẫn đến hành vi kỳ lạ như sửa đổi trạng thái cấp mô-đun không được chia sẻ như mong đợi , hoặc thậm chí các ngoại lệ không bị bắt vì trình xử lý ngoại lệ đã cố bắt loại ngoại lệ từ một cá thể của mô đun, trong khi ngoại lệ được nâng lên đã sử dụng loại từ trường hợp khác.

Do đó cảnh báo this may cause unpredictable behaviour mơ hồ - nếu mọi thứ xảy ra sai do chạy mã cấp cao nhất của mô-đun hai lần, các triệu chứng có thể là khá nhiều.

Làm cách nào bạn có thể gỡ lỗi các trường hợp phức tạp hơn?

Trong thời gian ở ví dụ cụ thể này, việc nhập khẩu tác dụng phụ là trực tiếp trong proj/__init__.py, có một xa tinh tế hơn và khó debug biến nơi gói cha mẹ thay vì thực hiện:

import some_other_module 

và sau đó nó là some_other_module (hoặc một module mà nó nhập khẩu) có quyền này:

import proj.proj # or "from proj import proj" 

Giả sử các hành vi sai trái là tái sản xuất, con đường chính để gỡ lỗi các loại vấn đề là chạy python trong chế độ verbose và ch eck chuỗi nhập:

$ python -v -c "print('Hello')" 2>&1 | grep '^import' 
import zipimport # builtin 
import site # precompiled from /usr/lib64/python2.7/site.pyc 
import os # precompiled from /usr/lib64/python2.7/os.pyc 
import errno # builtin 
import posix # builtin 
import posixpath # precompiled from /usr/lib64/python2.7/posixpath.pyc 
import stat # precompiled from /usr/lib64/python2.7/stat.pyc 
import genericpath # precompiled from /usr/lib64/python2.7/genericpath.pyc 
import warnings # precompiled from /usr/lib64/python2.7/warnings.pyc 
import linecache # precompiled from /usr/lib64/python2.7/linecache.pyc 
import types # precompiled from /usr/lib64/python2.7/types.pyc 
import UserDict # precompiled from /usr/lib64/python2.7/UserDict.pyc 
import _abcoll # precompiled from /usr/lib64/python2.7/_abcoll.pyc 
import abC# precompiled from /usr/lib64/python2.7/abc.pyc 
import _weakrefset # precompiled from /usr/lib64/python2.7/_weakrefset.pyc 
import _weakref # builtin 
import copy_reg # precompiled from /usr/lib64/python2.7/copy_reg.pyc 
import traceback # precompiled from /usr/lib64/python2.7/traceback.pyc 
import sysconfig # precompiled from /usr/lib64/python2.7/sysconfig.pyc 
import re # precompiled from /usr/lib64/python2.7/re.pyc 
import sre_compile # precompiled from /usr/lib64/python2.7/sre_compile.pyc 
import _sre # builtin 
import sre_parse # precompiled from /usr/lib64/python2.7/sre_parse.pyc 
import sre_constants # precompiled from /usr/lib64/python2.7/sre_constants.pyc 
import _locale # dynamically loaded from /usr/lib64/python2.7/lib-dynload/_localemodule.so 
import _sysconfigdata # precompiled from /usr/lib64/python2.7/_sysconfigdata.pyc 
import abrt_exception_handler # precompiled from /usr/lib64/python2.7/site-packages/abrt_exception_handler.pyc 
import encodings # directory /usr/lib64/python2.7/encodings 
import encodings # precompiled from /usr/lib64/python2.7/encodings/__init__.pyc 
import codecs # precompiled from /usr/lib64/python2.7/codecs.pyc 
import _codecs # builtin 
import encodings.aliases # precompiled from /usr/lib64/python2.7/encodings/aliases.pyc 
import encodings.utf_8 # precompiled from /usr/lib64/python2.7/encodings/utf_8.pyc 

Ví dụ cụ thể này chỉ hiển thị bộ nhập cơ sở Python 2.7 trên Fedora khi khởi động. Khi gỡ lỗi nhập hai lần RuntimeWarning giống như câu hỏi trong câu hỏi này, bạn sẽ tìm kiếm cụm từ "nhập proj" và sau đó "nhập proj.proj" vào đầu ra tiết, và sau đó xem xét kỹ nhập khẩu ngay trước " nhập khẩu proj.proj "dòng.

+2

Cảm ơn bạn đã thêm câu trả lời này. Vì vậy, để chưng cất điều này, thông báo là: bạn không thể chạy một tệp như một mô-đun thực thi ('python -m WHATEVER.FILE') khi tệp đó cũng tự động được nhập trong gói (bất kỳ' __init __. Py', là được tải, chứa 'từ WHATEVER nhập FILE'). Đúng? – DilithiumMatrix

+0

Về cơ bản, vâng. Nó không phải là * luôn luôn * sai (vì vậy tại sao nó chỉ là cảnh báo chứ không phải là lỗi), nhưng nó cung cấp cho bạn hai bản sao của mô-đun "giống nhau" dưới các tên khác nhau và gỡ lỗi các vấn đề quản lý nhà nước phát sinh từ đó có thể là một thách thức. Kết quả là, nó là đáng tin cậy hơn nhiều để chỉ cần không làm điều đó và thay vào đó có một submodule riêng biệt cung cấp giao diện dòng lệnh. – ncoghlan

+1

Tôi cũng đã cập nhật câu trả lời của mình về cách gỡ lỗi các trường hợp phức tạp hơn, nơi bạn thực sự muốn có thể chạy 'python' ở chế độ tiết để nó in ra chuỗi nhập chính xác và cho phép bạn tìm nơi ẩn ban đầu nhập khẩu đang diễn ra. – ncoghlan

0

python -m hơi phức tạp một chút. @ncoghlan đã cung cấp thông tin chi tiết. khi chúng tôi cố gắng chạy với python -m theo mặc định tất cả các gói trong sys.path/pythonpath được nhập. nếu gói của bạn có câu lệnh import cho bất cứ thứ gì trong các thư mục trong các PATH thì cảnh báo trên xảy ra. See the Pic

PYTHONPATH của tôi đã có thư mục Dự án.Do đó, khi tôi làm

from reader.reader import Reader 

Hệ thống sẽ cảnh báo. Do đó không cần phải nhập khẩu rõ ràng nếu đường dẫn nằm trong đường dẫn python

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