48

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]

したがって、この不潔な循環インポートのほかに、それはうまく機能します(実装の詳細は省略されていますが、複雑なことは何もありません)。

4

5 に答える 5

82

循環輸入は本質的に悪いことではありません。で何かをしている間、teamコードが依存するのは自然なことです。useruserteam

ここでのより悪い習慣はですfrom module import memberteamモジュールはuserインポート時にクラスを取得しようとしていますが、モジュールuserteamクラスを取得しようとしています。ただし、実行時teamの最初の行にいるため、クラスはまだ存在していません。team.pyuser.py

代わりに、モジュールのみをインポートしてください。これにより、名前空間がより明確になり、後でモンキーパッチを適用できるようになり、インポートの問題が解決されます。インポート時にモジュールをインポートするだけなので、モジュール内のクラスがまだ定義されていなくてもかまいません。クラスを使い始める頃には、そうなるでしょう。

したがって、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

from test import teamsteams.Teamそして、あなたがより少なく書きたいならば、それからまたOKですtest。それでも、モジュールメンバーではなく、モジュールをインポートしています。

また、TeamUserが比較的単純な場合は、それらを同じモジュールに配置します。Javaのファイルごとに1つのクラスのイディオムに従う必要はありません。isinstanceテストとsetメソッドもunpythonic-Java-warttomeを叫びます。何をしているのかによっては、タイプチェックされていないプレーンなを使用したほうがよい場合があります@property

于 2010-10-18T01:10:20.337 に答える
3

私。それを機能させるために、遅延インポートを使用できます。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。別の方法として、チームクラスとユーザークラスを同じファイルに入れてみませんか?

于 2010-10-17T23:35:39.953 に答える
2

悪い習慣/臭いは次のことです:

  • おそらく不要な型チェック(ここも参照)。取得したオブジェクトをユーザー/チームとして使用し、例外が発生したときに例外を発生させます(または、ほとんどの場合、追加のコードを必要とせずに発生します)。これをそのままにしておくと、循環インポートがなくなります(少なくとも今のところ)。取得したオブジェクトがユーザー/チームのように動作する限り、それらは何でもかまいません。ダックタイピング
  • 小文字のクラス(これは多かれ少なかれ好みの問題ですが、一般的に受け入れられている標準(PEP 8)はそれを異なって行います
  • 必要のないセッター:あなたはただ言うことができます:my_team.leader=user_b そしてuser_b.team=my_team
  • データの一貫性に関する問題:もしも(my_team.leader.team!=my_team)
于 2010-10-18T00:37:41.220 に答える
0

これは私がまだ見たことがないものです。sys.modules直接使用するのは悪い考え/デザインですか?@bobinceソリューションを読んだ後、私は輸入ビジネス全体を理解したと思いましたが、それから私はこれにリンクする質問に似た問題に遭遇しました。

これが解決策の別の見方です:

# 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

ファイル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

最後に、これはユーザーの読みやすさ(短いファイル)を支援するために行われていることを指摘するだけです。したがって、循環インポートが「本質的に」悪いとは言えません。すべてが同じファイルで実行された可能性がありますが、これを使用して、コードを分離し、巨大なファイルをスクロールしているときに混乱しないようにしています。

于 2014-03-13T19:37:47.023 に答える
0

依存関係グラフを修正するだけで済みます。たとえば、ユーザーはそれがチームの一部であるという事実を知る必要がない場合があります。ほとんどの循環依存は、そのようなリファクタリングを認めています。

# 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、実行時にインポートしたり、モジュールレベルにインポートしたり、ここで説明した他のトリックを使用したりすることで回避できますが、これらの戦略は設計上の欠陥を克服します。可能な限り、循環インポートを回避することは価値があります。

于 2019-03-25T05:24:02.470 に答える