2010-10-17 23 views
41

Hãy xem xét python (3.x) kịch bản:trăn nhập khẩu tròn một lần nữa (aka gì sai với thiết kế này)

main.py:

from test.team import team 
from test.user import user 

if __name__ == '__main__': 
    u = user() 
    t = team() 
    u.setTeam(t) 
    t.setLeader(u) 

thử nghiệm/user.py:

from test.team import team 

class user: 
    def setTeam(self, t): 
     if issubclass(t, team.__class__): 
      self.team = t 

thử nghiệm/team.py:

from test.user import user 

class team: 
    def setLeader(self, u): 
     if issubclass(u, user.__class__): 
      self.leader = u 

Không w, tất nhiên, tôi đã nhập khẩu tròn và ImportError tuyệt vời.

Vì vậy, không phải là pythonista, tôi có ba câu hỏi. Trước hết:

i. Làm thế nào tôi có thể làm cho điều này hoạt động?

Và biết rằng một người nào đó chắc chắn sẽ nói "Thông tư nhập khẩu luôn cho biết vấn đề thiết kế", câu hỏi thứ hai là:

ii. Tại sao thiết kế này lại xấu?

Và cuối cùng, thứ ba:

iii. Điều gì sẽ là lựa chọn tốt hơn?

Để chính xác, việc kiểm tra kiểu như trên chỉ là một ví dụ, đó cũng là một lớp chỉ mục dựa trên lớp, cho phép nghĩa là. tìm tất cả người dùng là thành viên của một nhóm nghiên cứu (sử dụng lớp có nhiều lớp con, vì vậy chỉ số được tăng lên gấp đôi, cho người dùng nói chung và đối với từng lớp con cụ thể) hoặc tất cả các đội có sử dụng được coi là một thành viên

Edit:

Tôi hy vọng rằng ví dụ chi tiết hơn sẽ làm rõ những gì tôi cố gắng đạt được. File bỏ qua cho readibility (nhưng có file nguồn một 300KB làm tôi sợ bằng cách nào đó, vì vậy hãy giả sử rằng mỗi lớp là trong tập tin khác nhau)

# ENTITY 

class Entity: 
    _id = None 
    _defs = {} 
    _data = None 

    def __init__(self, **kwargs): 
     self._id = uuid.uuid4() # for example. or randint(). or x+1. 
     self._data = {}.update(kwargs) 

    def __settattr__(self, name, value): 
     if name in self._defs: 
      if issubclass(value.__class__, self._defs[name]): 
       self._data[name] = value 

       # more stuff goes here, specially indexing dependencies, so we can 
       # do Index(some_class, name_of_property, some.object) to find all 
       # objects of some_class or its children where 
       # given property == some.object 

      else: 
       raise Exception('Some misleading message') 
     else: 
      self.__dict__[name] = value  

    def __gettattr__(self, name): 
     return self._data[name] 

# USERS 

class User(Entity): 
    _defs = {'team':Team} 

class DPLUser(User): 
    _defs = {'team':DPLTeam} 

class PythonUser(DPLUser) 
    pass 

class PerlUser(DPLUser) 
    pass 

class FunctionalUser(User): 
    _defs = {'team':FunctionalTeam} 

class HaskellUser(FunctionalUser) 
    pass 

class ErlangUser(FunctionalUser) 
    pass 

# TEAMS 

class Team(Entity): 
    _defs = {'leader':User} 

class DPLTeam(Team): 
    _defs = {'leader':DPLUser} 

class FunctionalTeam(Team): 
    _defs = {'leader':FunctionalUser} 

và bây giờ một số sử dụng:

t1 = FunctionalTeam() 
t2 = DLPTeam() 
t3 = Team() 

u1 = HaskellUser() 
u2 = PythonUser() 

t1.leader = u1 # ok 
t2.leader = u2 # ok 
t1.leader = u2 # not ok, exception 
t3.leader = u2 # ok 

# now , index 

print(Index(FunctionalTeam, 'leader', u2)) # -> [t2] 
print(Index(Team, 'leader', u2)) # -> [t2,t3] 

Vì vậy, nó hoạt động tuyệt vời (chi tiết thực hiện được thừa nhận, nhưng không có gì phức tạp) bên cạnh điều nhập khẩu vòng tròn vô hại này.

+2

Được coi là phương pháp hay để tận dụng các lớp học của bạn-- Nhóm/Người dùng. – snapshoe

+4

Ngoài ra: hãy kiểm tra [thuộc tính] của python (http://docs.python.org/library/functions.html#property) để biết cách thay thế ưu tiên để khai báo các bộ định cư và getters. – intuited

+0

@intuited: Tôi yêu trang trí, nhưng dường như nó không hoạt động tốt với __setattr__/__getattr__ (ví dụ ở trên là khá đơn giản) –

Trả lời

77

Thông tư nhập khẩu không hẳn đã là một điều xấu Đó là tự nhiên cho mã team dựa vào user trong khi user làm điều gì đó với team.

Thực tiễn tồi tệ hơn ở đây là from module import member. Mô-đun team đang cố gắng để có được lớp user tại thời điểm nhập và mô-đun user đang cố gắng để có được lớp học team. Nhưng lớp học team chưa tồn tại bởi vì bạn vẫn ở dòng đầu tiên là team.py khi user.py chạy.

Thay vào đó, chỉ nhập mô-đun. Điều này dẫn đến việc đặt tên không gian rõ ràng hơn, làm cho việc vá lỗi sau này có thể xảy ra và giải quyết vấn đề nhập. Vì bạn chỉ nhập mô-đun vào thời gian nhập, bạn không quan tâm đến lớp lớp bên trong nó chưa được xác định. Vào thời điểm bạn sử dụng lớp học, nó sẽ như thế.

Vì vậy, kiểm tra/users.py:

import test.teams 

class User: 
    def setTeam(self, t): 
     if isinstance(t, test.teams.Team): 
      self.team = t 

thử nghiệm/teams.py:

import test.users 

class Team: 
    def setLeader(self, u): 
     if isinstance(u, test.users.User): 
      self.leader = u 

from test import teams và sau đó teams.Team cũng là OK, nếu bạn muốn viết test ít hơn. Đó vẫn là nhập khẩu một mô-đun, không phải là một thành viên mô-đun.

Ngoài ra, nếu TeamUser tương đối đơn giản, hãy đặt chúng trong cùng một mô-đun. Bạn không cần phải tuân theo thành ngữ một lớp cho mỗi tập tin Java. Thử nghiệm isinstance và các phương pháp set cũng hét lên unpythonic-Java-wart với tôi; tùy thuộc vào những gì bạn đang làm, bạn có thể sử dụng một cách rõ ràng, không được kiểm tra kiểu @property.

+9

Nhập khẩu thông thường vốn là một điều xấu.Nếu bạn muốn lấy một phần của ứng dụng của bạn và, ví dụ, làm cho một thư viện ra khỏi nó, sau đó điều quan trọng về sự phụ thuộc của đoạn đó là tất cả họ phải đi từ ứng dụng đến thư viện. Thư viện có phụ thuộc vào ứng dụng của bạn không được sử dụng cho bất kỳ ai. Thậm chí không bạn - bạn sẽ không thể chạy các bài kiểm tra trên thư viện mà không cần gói nó với ứng dụng, điều này sẽ đánh bại mục đích cố gắng tách chúng ngay từ đầu. Kể từ khi phụ thuộc vòng tròn đi BOTH cách, nó là không thể bao giờ chia mã thành các khối tách rời. –

+13

Hãy để tôi nói lại điều đó cho bạn: Thông tư nhập khẩu vốn dĩ là một điều xấu, _if_ bạn từng muốn lấy một đoạn ứng dụng của bạn và, ví dụ, tạo một thư viện ra khỏi nó. _Within_ một đoạn tách rời, có thể hoàn toàn hợp lý khi sử dụng các tham chiếu vòng tròn. – mhsmith

+17

"Vốn có" có mùi hôi. Nó giống như nói "rõ ràng" hay "chắc chắn". Nếu thế giới có phụ thuộc vòng tròn, và bạn sử dụng các lớp học để mô hình hóa thế giới, thì các lớp của bạn có thể có các phụ thuộc vòng tròn. Có, ở cấp độ thực hiện, họ làm cho mọi thứ trở nên khó khăn và nó là giải pháp thảo luận đáng giá. – bootchk

3

i. Để làm cho nó hoạt động, bạn có thể sử dụng một nhập khẩu hoãn lại. Một cách sẽ là để riêng user.py và thay đổi team.py thành:

class team: 
    def setLeader(self, u): 
     from test.user import user 
     if issubclass(u, user.__class__): 
      self.leader = u 

iii. Để thay thế, tại sao không đặt nhóm và lớp người dùng trong cùng một tệp?

+1

quảng cáo. iii - Tôi có hơn 60 lớp như vậy và đưa chúng vào một tệp không thực sự là tùy chọn quảng cáo –

+0

i. - không phải là đánh hiệu suất sao? bất cứ ai biết nếu python nội bộ tối ưu hóa loại nhập khẩu? –

+3

có, nó được tối ưu hóa. xem http://docs.python.org/library/sys.html#sys.modules – snapshoe

2

dở/mùi là những điều sau đây:

  • Probaly không cần thiết kiểm tra kiểu (see also here). Chỉ cần sử dụng các đối tượng bạn nhận được vì nó là một người dùng/nhóm và nâng cao một ngoại lệ (hoặc trong hầu hết các trường hợp, một trường hợp được nâng lên mà không cần mã bổ sung) khi nó bị hỏng. Để lại điều này, và bạn nhập khẩu vòng tròn biến mất (ít nhất là cho bây giờ). Miễn là các đối tượng bạn nhận được hành xử như một người dùng/một nhóm, chúng có thể là bất kỳ thứ gì.(Duck Typing)
  • thấp hơn lớp trường hợp (đây là nhiều hay ít là vấn đề của hương vị, nhưng các tiêu chuẩn được chấp nhận chung (PEP 8) thực hiện nó một cách khác
  • setter nơi không cần thiết: bạn chỉ có thể nói: my_team.leader=user_buser_b.team=my_team
  • vấn đề với tính nhất quán dữ liệu:. nếu (my_team.leader.team!=my_team)
+0

Trong thực tế getters và setters được đơn giản hóa nhiều. Thông thường, chúng có nhiều khả năng chịu trách nhiệm hơn, tức là kiểm tra tính nhất quán của dữ liệu (i không đăng toàn bộ mã ở đây). Lớp chữ thường - ok, tôi chỉ đọc PEP8;). Cuối cùng nhưng không kém phần quan trọng - kiểm tra kiểu. Vấn đề là các đối tượng được đặt ở đây, được nhân giống và việc sử dụng chúng thường bị trì hoãn. Vì vậy, nếu đối tượng có loại sai tôi nên backtrace tất cả các tuyên truyền mà có thể được khá không thể. Thay vào đó tôi đang xác thực đối tượng trước (có, vi phạm EAFP), và xác thực dựa trên lớp của đối tượng hơn là thuộc tính. Bất kỳ giải pháp lành mạnh nào ở đây? –

+0

Bạn chỉ có thể thêm một thành viên vào các cá thể của bạn cho biết lớp đó là gì: Sau đó, kiểm tra của bạn sẽ trông giống như: 'if not u.is_a ==" user ":'. –

+0

@Michael: có vẻ như tôi phải đối phó với nó theo cách đó, mặc dù cấu trúc thừa kế lớp này tăng gấp đôi –

1

Đây là điều tôi chưa thấy. Ý tưởng/thiết kế tồi có sử dụng trực tiếp sys.modules không? Sau khi đọc giải pháp @bobince, tôi nghĩ rằng tôi đã hiểu toàn bộ doanh nghiệp nhập khẩu nhưng sau đó tôi gặp phải sự cố tương tự với một số question liên kết đến trang này.

Dưới đây là một mất trên giải pháp:

# main.py 
from test import team 
from test import user 

if __name__ == '__main__': 
    u = user.User() 
    t = team.Team() 
    u.setTeam(t) 
    t.setLeader(u) 

# test/team.py 
from test import user 

class Team: 
    def setLeader(self, u): 
     if isinstance(u, user.User): 
      self.leader = u 

# test/user.py 
import sys 
team = sys.modules['test.team'] 

class User: 
    def setTeam(self, t): 
     if isinstance(t, team.Team): 
      self.team = t 

và tập tin tập tin test/__init__.py là trống rỗng. Lý do là vì test.team đang được nhập trước tiên. Khoảnh khắc python đang nhập/đọc một tệp mà nó gắn thêm mô-đun vào sys.modules.Khi chúng tôi nhập test/user.py, mô-đun test.team sẽ được xác định vì chúng tôi đang nhập nó trong main.py.

Tôi bắt đầu thích ý tưởng này cho các mô-đun phát triển khá lớn nhưng có các hàm và lớp phụ thuộc lẫn nhau. Giả sử rằng có một tệp có tên là util.py và tệp này chứa nhiều lớp phụ thuộc vào nhau. Có lẽ chúng ta có thể tách mã giữa các tệp khác nhau phụ thuộc vào nhau. Làm thế nào để chúng ta có được xung quanh việc nhập khẩu vòng tròn?

Vâng, trong util.py tập tin chúng tôi chỉ đơn giản là nhập khẩu tất cả các đối tượng từ các tập tin "tin" khác, tôi nói riêng kể từ khi các tập tin không có nghĩa là để được truy cập trực tiếp, thay vì chúng ta truy cập chúng thông qua các tập tin ban đầu:

# mymodule/util.py 
from mymodule.private_util1 import Class1 
from mymodule.private_util2 import Class2 
from mymodule.private_util3 import Class3 

Sau đó, trên mỗi người trong số các tập tin khác:

# mymodule/private_util1.py 
import sys 
util = sys.modules['mymodule.util'] 
class Class1(object): 
    # code using other classes: util.Class2, util.Class3, etc 

# mymodule/private_util2.py 
import sys 
util = sys.modules['mymodule.util'] 
class Class2(object): 
    # code using other classes: util.Class1, util.Class3, etc 

các 01.236 Cuộc gọisẽ hoạt động miễn là mymodule.util được cố nhập trước tiên.

Cuối cùng, tôi sẽ chỉ ra rằng điều này đang được thực hiện để giúp người dùng dễ đọc (tệp ngắn hơn) và do đó tôi sẽ không nói rằng nhập khẩu vòng tròn "vốn dĩ" là xấu. Tất cả mọi thứ có thể đã được thực hiện trong cùng một tập tin nhưng chúng tôi đang sử dụng để chúng tôi có thể tách mã và không nhầm lẫn chính mình trong khi di chuyển qua các tập tin rất lớn.

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