35

いくつかのサブクラスがあるクラスがあるとします。

クラスをインスタンス化できます。次に、その__class__属性をサブクラスの1つに設定できます。ライブオブジェクトで、クラスタイプをそのサブクラスのタイプに効果的に変更しました。それらのメソッドのサブクラスのバージョンを呼び出すメソッドを呼び出すことができます。

それで、これを行うことはどれほど危険ですか?奇妙に思えますが、そんなことをするのは間違っているのでしょうか。実行時にタイプを変更する機能にもかかわらず、これは完全に回避する必要がある言語の機能ですか?なぜまたはなぜそうではないのですか?

(回答に応じて、私が何をしたいのか、そしてより良い代替案があるかどうかについて、より具体的な質問を投稿します)。

4

8 に答える 8

30

これが私がこれを危険なものにしていると私が考えることができるもののリストです。

  • コードを読んだりデバッグしたりする人を混乱させる可能性があります。
  • 適切__init__なメソッドを取得していないため、すべてのインスタンス変数が適切に(またはまったく)初期化されていない可能性があります。
  • 2.xと3.xの違いは非常に重要であるため、移植するのは面倒です。
  • classmethods、手動でコーディングされた記述子、メソッド解決順序へのフックなどのいくつかのエッジケースがあり、それらは従来のクラスと新しいスタイルのクラスの間で異なります(また、2.xと3.xの間)。
  • を使用する場合__slots__、すべてのクラスに同一のスロットが必要です。(互換性があるが異なるスロットがある場合、最初は機能しているように見えるかもしれませんが、恐ろしいことをします…)
  • 新しいスタイルのクラスの特別なメソッド定義は変更されない場合があります。(実際、これは現在のすべてのPython実装で実際に機能しますが、機能するように文書化されていないため、…)
  • を使用する__new__と、物事はあなたが素朴に期待したようには機能しません。
  • クラスに異なるメタクラスがある場合、事態はさらに混乱します。

一方、これが必要だと思われる多くの場合、より良いオプションがあります。

  • ベースインスタンスを作成してから派生インスタンスに変更するのではなく、ファクトリを使用して適切なクラスのインスタンスを動的に作成します。
  • または他のメカニズムを使用__new__して、構造をフックします。
  • 継承を悪用するのではなく、データ駆動型の動作を備えた単一のクラスになるように、物事を再設計します。

最後のケースの最も一般的な特定のケースとして、すべての「変数メソッド」を、インスタンスがサブクラスではなく「親」のデータメンバーとして保持されるクラスに配置するだけです。変更する代わりにself.__class__ = OtherSubclass、を実行してくださいself.member = OtherSubclass(self)。魔法のように変更するメソッドが本当に必要な場合__getattr__は、クラスをその場で変更するよりも、自動転送(viaなど)の方がはるかに一般的でPythonのイディオムです。

于 2012-11-08T00:46:12.683 に答える
17

属性の割り当ては、__class__アプリケーションを長時間実行していて、一部のオブジェクトの古いバージョンを、データを失うことなく同じクラスの新しいバージョンに置き換える必要がある場合に便利ですreload(mymodule)。他の例は、永続性を実装する場合ですpickle.load

特にアプリケーションを起動する前に完全なコードを記述できる場合は、他のすべての使用は推奨されません。

于 2012-11-08T01:28:04.567 に答える
5

任意のクラスでは、これが機能する可能性は非常に低く、機能したとしても非常に脆弱です。基本的には、あるクラスのメソッドから基礎となる関数オブジェクトを引き出し、元のクラスのインスタンスではないオブジェクトでそれらを呼び出すことと同じです。それが機能するかどうかは、内部実装の詳細に依存し、非常に密結合の形式です。

とはいえ、このように使用するために特別に設計され__class__た一連のクラスの間でオブジェクトの を変更することは、まったく問題ありません。私は長い間これを行うことができることを知っていましたが、同時により良い解決策が思い浮かばなかったこの手法の用途をまだ見つけていません. したがって、ユースケースがあると思われる場合は、それを選択してください。何が起こっているのかをコメント/ドキュメントで明確にしてください。特に、関連するすべてのクラスの実装は、各クラスを個別に検討するのではなく、すべての不変条件/仮定/その他を尊重する必要があることを意味します。関連するコードはこれを認識しています!

于 2012-11-08T01:14:25.050 に答える
3

まあ、最初に警告された問題を軽視しているわけではありません。ただし、特定の場合に役立つ場合があります。

まず第一に、私がこの投稿を探している理由は、私がこれをしただけで、それ__slots__が気に入らないからです. (はい、私のコードはスロットの有効な使用例です。これは純粋なメモリの最適化です)、スロットの問題を回避しようとしていました。

これは、Alex Martelli の Python Cookbook (第 1 版) で初めて見ました。第 3 版では、レシピ 8.19「ステートフル オブジェクトまたはステート マシンの問題の実装」です。Pythonに関しては、かなり知識のあるソースです。

InactiveEnemy とは動作が異なる ActiveEnemy オブジェクトがあり、それらの間をすばやく切り替える必要があるとします。たぶんDeadEnemyでさえ。

InactiveEnemy がサブクラスまたは兄弟である場合、クラス属性を切り替えることができます。より正確には、正確な祖先は、それを呼び出すコードと一貫性のあるメソッドと属性ほど重要ではありません。Javaインターフェイスを考えてみてください。または、何人かが言及しているように、クラスはこの使用を念頭に置いて設計する必要があります。

今でも、状態遷移規則やその他のあらゆる種類のものを管理する必要があります。そして、はい、クライアント コードがこの動作を予期しておらず、インスタンスが動作を切り替える場合、事態はファンに打撃を与えます。

しかし、私はこれを Python 2.x で非常にうまく使用しており、異常な問題は一度もありませんでした。共通の親と、同じメソッド シグネチャを持つサブクラスの小さな動作の違いを使用して行うのが最適です。

__slots__ちょうど今それをブロックしている私の問題まで、問題ありません。しかし、スロットは一般的に首の痛みです。

ライブコードにパッチを当てるためにこれを行うことはありません。また、ファクトリ メソッドを使用してインスタンスを作成することもできます。

しかし、事前にわかっている非常に具体的な条件を管理するには? クライアントが完全に理解することが期待されるステートマシンのようなものですか? それから、それは魔法にかなり近いものであり、それに付随するすべてのリスクがあります. とてもエレガントです。

Python 3 の懸念事項? 動作するかどうかをテストしますが、Cookbook では例として FWIW で Python 3 の print(x) 構文を使用しています。

于 2014-06-28T04:42:32.003 に答える
0

それがどれほど「危険」であるかは、主にオブジェクトを初期化するときにサブクラスが何をしたかによって異なります。基本クラスのを実行しただけで適切に初期化されず__init__()、初期化されていないインスタンス属性などが原因で後で何かが失敗する可能性があります。

それがなくても、ほとんどのユースケースでは悪い習慣のようです。そもそも目的のクラスをインスタンス化する方が簡単です。

于 2012-11-08T00:46:03.857 に答える
-1

コメントでは、動的なsの可能なユースケースとしてセルオートマトンのモデリングを提案しました__class__。アイデアを少し具体化してみましょう。


動的を使用する__class__

class Stage(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Stage1(Stage):
    def step(self):
        if ...:
            self.__class__ = Stage2

class Stage2(Stage):
    def step(self):
        if ...:
            self.__class__ = Stage3

cells = [Stage1(x,y) for x in range(rows) for y in range(cols)]

def step(cells):
    for cell in cells:
        cell.step()
    yield cells

より良い用語がないので、これを呼びます

従来の方法:(主にabarnertのコード)

class Stage1(object):
    def step(self, cell):
        ...
        if ...:
            cell.goToStage2()

class Stage2(object):
    def step(self, cell):
        ...
        if ...:        
            cell.goToStage3()

class Cell(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.current_stage = Stage1()
    def goToStage2(self):
        self.current_stage = Stage2()
    def __getattr__(self, attr):
        return getattr(self.current_stage, attr)

cells = [Cell(x,y) for x in range(rows) for y in range(cols)]

def step(cells):
    for cell in cells:
        cell.step(cell)
    yield cells

比較:

  • Cell従来の方法では、それぞれが現在のステージ属性を持つインスタンスのリストを作成します。

    動的な__class__方法では、のサブクラスであるインスタンスのリストが作成されますStage__class__すでにこの目的を果たしているため、現在のステージ属性は必要ありません。

  • 従来の方法ではgoToStage2、、、goToStage3...メソッドを使用してステージを切り替えます。

    動的な__class__方法では、そのような方法は必要ありません。を再割り当てするだけ__class__です。

  • 従来の方法では、特別なメソッドを使用して、属性__getattr__に保持されている適切なステージインスタンスにいくつかのメソッド呼び出しを委任し ます。self.current_stage

    動的な__class__方法では、そのような委任は必要ありません。のインスタンスはcells、すでに必要なオブジェクトです。

  • cell従来の方法では、を引数として に渡す必要がありますStage.step。これはそうcell.goToStageN呼ぶことができます。

    動的な__class__方法は何も渡す必要はありません。私たちが扱っているオブジェクトには、必要なものがすべて揃っています。


結論:

両方の方法を機能させることができます。これらの2つの実装がどのように機能するかを想像できる限り、動的な__class__実装は次のようになります。

  • より単純(Cellクラスなし)、

  • よりエレガント(醜い方法や、代わりにgoToStage2書く必要がある理由のような頭の体操はありません)、cell.step(cell)cell.step()

  • 理解しやすくなります(いいえ__getattr__、追加レベルの間接参照はありません)

于 2012-11-08T02:40:13.007 に答える
-1

を変更せずに同じことを行う方法の例を次に示します__class__。質問へのコメントで@unutbuを引用:

セルオートマトンをモデリングしているとします。各セルが、たとえば 5 つのステージのいずれかにあるとします。Stage1、Stage2 などの 5 つのクラスを定義できます。各 Stage クラスには複数のメソッドがあるとします。

class Stage1(object):
  …

class Stage2(object):
  …

…

class Cell(object):
  def __init__(self):
    self.current_stage = Stage1()
  def goToStage2(self):
    self.current_stage = Stage2()
  def __getattr__(self, attr):
    return getattr(self.current_stage, attr)

変更を許可する__class__と、セルに新しいステージのすべてのメソッドを即座に与えることができます (名前は同じですが、動作は異なります)。

を変更する場合も同じですcurrent_stageが、これは完全に正常で Pythonic な操作であり、誰も混乱させることはありません。

さらに、 でオーバーライドするだけで、変更したくない特定の特別なメソッドを変更しないCellようにすることができます。

さらに、データ メンバー、クラス メソッド、静的メソッドなどに対して、すべての中級 Python プログラマーが既に理解している方法で機能します。

変更を拒否する場合__class__は、stage 属性を含め、多くの if ステートメントを使用するか、異なる stage の関数を指す多くの属性を再割り当てする必要があるかもしれません

はい、ステージ属性を使用しましたが、それはマイナス面ではありません。これは、現在のステージが何であるかを追跡するための明らかな目に見える方法であり、デバッグと読みやすさの点で優れています。

また、stage 属性を除いて、単一の if ステートメントや属性の再割り当てはありません。

これは、変更せずにこれを行う複数の異なる方法の 1 つにすぎません__class__

于 2012-11-08T01:15:07.467 に答える