2013-11-15 19 views
8

user.py:Remove trăn nhập khẩu tròn

from story import Story 

class User: 
    ... 
    def get_stories(self): 
     story_ids = [select from database] 
     return [Story.get_by_id(id) for id in story_ids] 

story.py

from user import User 

class Story: 
    ... 
    def __init__(self, id, user_id, content): 
     self.id = id 
     self.user = User.get_by_id(user_id) 
     self.content = content 

như bạn có thể thấy, có một khẩu tròn trong chương trình này, gây ra một ImportError. Tôi đã học được rằng tôi có thể di chuyển câu lệnh import trong định nghĩa phương thức để ngăn chặn lỗi này. Nhưng tôi vẫn muốn biết, là có một cách để loại bỏ nhập khẩu tròn trong trường hợp này, hoặc, là nó cần thiết (cho một thiết kế tốt)?

+0

Không cần phải loại bỏ việc nhập vòng tròn cho thiết kế tốt. Việc chuyển nhập vào định nghĩa phương thức là một cách hợp lý để trì hoãn việc nhập. –

Trả lời

1

Một cách khác để giảm thiểu tính tròn là thay đổi kiểu nhập. Thay đổi from story import Story thành import story, sau đó tham khảo lớp học là story.Story. Vì bạn chỉ tham chiếu đến lớp bên trong một phương thức, nó sẽ không cần phải truy cập lớp cho đến khi phương thức được gọi, vào thời điểm đó quá trình nhập sẽ hoàn tất thành công. (Bạn có thể phải thực hiện thay đổi này ở một trong hai hoặc cả hai mô-đun, tùy thuộc vào loại nào được nhập trước.)

Thiết kế dường như hơi lạ. Thiết kế của bạn là như vậy mà các lớp UserStory được ghép rất chặt chẽ - không thể sử dụng chúng mà không có lớp kia. Trong trường hợp như vậy, nó thường sẽ có ý nghĩa hơn để có cả hai trong cùng một mô-đun.

+0

Vâng, tôi cảm thấy thiết kế này hơi kỳ lạ ... vì đây là một tình huống phổ biến, tôi tự hỏi thiết kế tốt sẽ như thế nào? thanks – wong2

+1

Vâng, điều này __isn't__ một tình huống phổ biến :) –

+0

Thay đổi được đề xuất sẽ không khắc phục được sự cố, sẽ vẫn có một lần nhập vòng tròn. –

0

Như BrenBarn đã nói, giải pháp rõ ràng nhất là giữ Người dùng và Câu chuyện trong cùng một mô-đun, điều này có ý nghĩa hoàn hảo nếu Người dùng được cho là biết bất kỳ điều gì về Câu chuyện. Bây giờ nếu bạn thực sự cần phải có chúng trong các mô-đun riêng biệt, bạn cũng có thể monkeypatch Người dùng trong story.py để thêm phương thức get_stories. Đó là một thương mại có thể đọc/tách ra khỏi ...

1

Các giải pháp rõ ràng nhất trong trường hợp này là để phá vỡ sự phụ thuộc vào lớp User hoàn toàn, bằng cách thay đổi giao diện để các nhà xây dựng Story chấp nhận một thực tế User, không phải là một user_id. Điều này cũng dẫn đến một thiết kế hiệu quả hơn: ví dụ nếu một người dùng có nhiều câu chuyện, cùng một đối tượng có thể được trao cho tất cả các nhà xây dựng đó.

Ngoài ra, việc nhập toàn bộ mô-đun (là storyuser thay vì thành viên) sẽ hoạt động - mô-đun được nhập trước tiên sẽ xuất hiện trống vào thời điểm nhập thứ hai; tuy nhiên nó không quan trọng vì nội dung của các mô-đun này không được sử dụng ở phạm vi toàn cục.

Điều này hơi thích hợp hơn khi nhập trong một phương thức. Việc nhập khẩu trong một phương thức có chi phí đáng kể so với việc tìm kiếm mô-đun toàn cục (story.Story), vì nó cần được thực hiện cho mỗi cuộc gọi phương thức; có vẻ như trong một trường hợp đơn giản, chi phí tối thiểu là 30 lần.

1

Có một loạt các câu hỏi nhập python nhập vòng này trên web. Tôi đã chọn đóng góp cho chủ đề này vì truy vấn có nhận xét của Ray Hettinger hợp pháp hoá trường hợp sử dụng của một lần nhập tròn, nhưng đề xuất một giải pháp mà tôi tin là không thực sự tốt - chuyển nhập vào phương thức.

Ngoài quyền hettinger, ba kỹ khuyến cáo để phản đối phổ biến là cần thiết:

  1. Tôi chưa bao giờ được lập trình trong Java. Tôi không cố gắng làm phong cách Java.
  2. Tái cấu trúc không phải lúc nào cũng hữu ích hoặc hiệu quả.API logic đôi khi ra lệnh cấu trúc làm cho các tham chiếu nhập đệ quy không thể tránh khỏi. Hãy nhớ rằng, mã tồn tại cho người dùng, không phải là người lập trình.
  3. Kết hợp các mô-đun khá lớn có thể gây ra các vấn đề về khả năng đọc và bảo trì có thể tồi tệ hơn nhiều so với một hoặc hai lần nhập khẩu đệ quy.

Bên cạnh đó, tôi tin rằng khả năng bảo trì và khả năng đọc dictates rằng hàng nhập khẩu được nhóm lại ở phía trên cùng của tập tin, xảy ra một lần duy nhất cho mỗi tên cần thiết, và rằng from module import name phong cách là một lợi thế (ngoại trừ có lẽ cho tên mô-đun rất ngắn với nhiều chức năng, ví dụ: gtk), vì nó tránh lặp lại sự lộn xộn bằng lời nói và làm cho phụ thuộc rõ ràng.

Bằng cách đó, tôi sẽ đặt một phiên bản đơn giản của trường hợp sử dụng của riêng tôi đã mang tôi đến đây và cung cấp giải pháp của tôi.

Tôi có hai mô-đun, mỗi mô-đun xác định nhiều lớp. surface xác định các bề mặt hình học như mặt phẳng, hình cầu, hyperboloids, v.v. path xác định các hình dạng phẳng như đường thẳng, vòng tròn hyperbolae, ... Về mặt logic, đây là những loại riêng biệt và tái cấu trúc không phải là một lựa chọn từ quan điểm của các yêu cầu API. Tuy nhiên, hai loại này là thân mật.

Một hoạt động hữu ích là giao nhau hai bề mặt, ví dụ, giao điểm của hai mặt phẳng là một đường hoặc giao điểm của mặt phẳng và hình cầu là hình tròn.

Nếu ví dụ, trong surface.py bạn làm việc nhập khẩu thẳng về phía trước cần thiết để thực hiện các giá trị trả về cho một hoạt động giao lộ:

from path import Line 

bạn nhận được:

Traceback (most recent call last): 
    File "surface.py", line 62, in <module> 
    from path import Line 
    File ".../path.py", line 25, in <module> 
    from surface import Plane 
    File ".../surface.py", line 62, in <module> 
    from path import Line 
ImportError: cannot import name Line 

hình học, máy bay được sử dụng để xác định các đường dẫn, sau khi tất cả, chúng có thể được định hướng tùy ý trong ba (hoặc nhiều) kích thước. Traceback cho bạn biết cả những gì đang xảy ra và giải pháp.

Chỉ cần thay thế câu lệnh import trong surface.py với:

try: from path import Line 
except ImportError: pass # skip circular import second pass 

Các chuỗi các hoạt động trong các dấu vết trở lại vẫn còn xảy ra. Nó chỉ là lần thứ hai thông qua, chúng tôi bỏ qua sự thất bại nhập khẩu. Điều này không quan trọng, vì Line không được sử dụng ở cấp mô-đun. Do đó, không gian tên cần thiết của surface được tải vào path. Do đó, phân tích không gian tên của path có thể hoàn thành, cho phép nó được nạp vào surface, hoàn thành cuộc gặp gỡ đầu tiên với from path import Line. Do đó việc phân tích cú pháp không gian tên của surface có thể tiến hành và hoàn thành, tiếp tục với bất kỳ điều gì khác có thể cần thiết.

Đây là một thành ngữ dễ và rất rõ ràng. Cú pháp try: ... except ... rõ ràng và ngắn gọn ghi lại vấn đề nhập khẩu vòng tròn, giảm bớt bất kỳ bảo trì nào trong tương lai có thể được yêu cầu. Sử dụng nó bất cứ khi nào một refactor thực sự là một ý tưởng tồi.

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