python(3.x)スクリプトを考えてみましょう:
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)
test/user.py:
from test.team import team
class user:
def setTeam(self, t):
if issubclass(t, team.__class__):
self.team = t
test/team.py:
from test.user import user
class team:
def setLeader(self, u):
if issubclass(u, user.__class__):
self.leader = u
もちろん、もちろん、循環インポートと素晴らしいImportErrorがあります。
ですから、pythonistaではなく、3つの質問があります。まず第一に:
私。これを機能させるにはどうすればよいですか?
そして、誰かが「循環インポートは常に設計上の問題を示している」と必然的に言うことを知って、2番目の質問が来ます:
ii。なぜこのデザインは悪いのですか?
そして最後に、3番目のもの:
iii。より良い代替手段は何でしょうか?
正確には、上記のタイプチェックは単なる例であり、クラスに基づくインデックスレイヤーも存在します。 1つのチームのメンバーであるすべてのユーザーを検索する(ユーザークラスには多くのサブクラスがあるため、ユーザー全体および特定のサブクラスごとにインデックスが2倍になります)またはユーザーをメンバーとして指定したすべてのチーム
編集:
より詳細な例が私が達成しようとしていることを明確にすることを願っています。読みやすくするために省略されたファイル(ただし、300kbのソースファイルが1つあるため、どういうわけか怖いので、すべてのクラスが異なるファイルにあると想定してください)
# 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}
そして今いくつかの使用法:
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]
したがって、この不気味な循環インポートのほかに、それはうまく機能します(実装の詳細は省略されていますが、複雑ではありません)。
循環インポートは本質的に悪いことではありません。 team
がuser
で何かを実行している間、user
コードがteam
に依存するのは自然なことです。
ここでの悪い習慣はfrom module import member
です。 team
モジュールはインポート時にuser
クラスを取得しようとしています。user
モジュールはteam
クラスを取得しようとしています。ただし、team
クラスはまだ存在しません。これは、team.py
の実行時にuser.py
の最初の行にいるためです。
代わりに、モジュールのみをインポートしてください。これにより、名前空間がより明確になり、後からのモンキーパッチが可能になり、インポートの問題が解決されます。 moduleをインポート時にインポートするだけなので、内部のclassがまだ定義されていないことを気にする必要はありません。クラスを使い始める頃には、そうなります。
したがって、test/users.py:
import test.teams
class User:
def setTeam(self, t):
if isinstance(t, test.teams.Team):
self.team = t
test/teams.py:
import test.users
class Team:
def setLeader(self, u):
if isinstance(u, test.users.User):
self.leader = u
test
lessと書きたい場合は、from test import teams
、次にteams.Team
でも構いません。これは、モジュールメンバーではなく、まだモジュールをインポートしています。
また、Team
とUser
が比較的単純な場合は、同じモジュールに配置します。 Java one-class-per-fileイディオムに従う必要はありません。isinstance
テストおよびset
メソッドもunpythonic-Java-wartを叫びます私には、あなたが何をしているのかに応じて、型チェックされていないプレーンな@property
を使用するほうがよいでしょう。
私。これを機能させるには、遅延インポートを使用できます。 1つの方法は、user.pyをそのままにし、team.pyを次のように変更することです。
class team:
def setLeader(self, u):
from test.user import user
if issubclass(u, user.__class__):
self.leader = u
iii。別の方法として、チームとユーザーのクラスを同じファイルに入れてみませんか?
悪い習慣/臭いは次のことです:
my_team.leader=user_b
およびuser_b.team=my_team
(my_team.leader.team!=my_team)
?依存関係グラフを修正するだけです。たとえば、ユーザーはチームの一部であることを知る必要がない場合があります。ほとんどの循環依存関係は、このようなリファクタリングを認めています。
# team -> user instead of team <-> user
class Team:
def __init__(self):
self.users = set()
self.leader = None
def add_user(self, user):
self.users.add(user)
def get_leader(self):
return self.leader
def set_leader(self, user):
assert user in self.users, 'leaders must be on the team!'
self.leader = user
循環依存は、リファクタリングを大幅に複雑にし、コードの再利用を禁止し、テストにおける分離を減らします。
Python=では、実行時にインポートしたり、モジュールレベルにインポートしたり、ここで説明したその他のトリックを使用したりすることで、ImportError
を回避することが可能ですが、これらの戦略は設計上の欠陥を克服するために紙を使用します。可能な限り循環インポートを避ける価値があります。
これは私がまだ見たことがないものです。 sys.modules
を直接使用するのは悪いアイデア/デザインですか? @bobinceソリューションを読んだ後、インポートビジネス全体を理解していると思いましたが、 question に似た問題に遭遇しました。これはこれにリンクしています。
これがソリューションの別の見方です:
# main.py
from test import team
from test import user
if __== '__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
ファイルtest/__init__.py
ファイルが空です。これが機能する理由は、test.team
が最初にインポートされるためです。 pythonがファイルをインポート/読み取りしているときに、モジュールがsys.modules
に追加されます。test/user.py
をインポートすると、モジュールtest.team
はすでに定義されているため、 main.py
にインポートしています。
非常に大きくなるが、相互に依存する関数とクラスがあるモジュールについては、このアイデアが好きになり始めています。 util.py
というファイルがあり、このファイルに相互に依存する多くのクラスが含まれているとします。おそらく、互いに依存する異なるファイル間でコードを分割することができます。循環インポートを回避するにはどうすればよいですか?
まあ、util.py
ファイルでは、他の「プライベート」ファイルからすべてのオブジェクトをインポートするだけです。これらのファイルは直接アクセスするためのものではなく、元のファイルからアクセスするため、プライベートと言います。
# mymodule/util.py
from mymodule.private_util1 import Class1
from mymodule.private_util2 import Class2
from mymodule.private_util3 import Class3
次に、他の各ファイルで:
# 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
sys.modules
の呼び出しは、mymodule.util
を最初にインポートしようとする限り機能します。
最後に、これは読みやすさ(短いファイル)をユーザーに提供するために行われていることだけを指摘します。したがって、循環インポートが「本質的に」悪いとは言えません。すべてを同じファイルで実行することもできますが、これを使用して、巨大なファイルをスクロールしながらコードを分離し、混乱しないようにします。