2017-02-08 7 views
1

私のプロジェクトでは、のオブジェクトobjを生成します。実行時に、ユーザーが何をしたいか、オブジェクトが最もよく表現されていると思われるものに応じて、objのタイプをTofuまたはBox(およびそれ以降)に変更する必要があります。次に、ユーザーは、対応するクラスに実装されている特定のアルゴリズムの恩恵を受ける必要があります。私はこの動作の良い実装を探しています。私は__class__属性を変更する下記のコードを使用していますが、これは悪いスタイルだと確信しています。作成後のオブジェクトのタイプを変更する(Pythonでの型キャスト)

class CubicObject(object): 
    name = 'Baseclass' 

    def __init__(self, sidelength): 
     self.sidelength = sidelength 


class Tofu(CubicObject): 
    name = 'Class A' 

    def eat(self): 
     print("I've eaten a volume of %s. " % (self.sidelength**3)) 


class Box(CubicObject): 
    name = 'Class B' 

    def paint(self): 
     print("I painted a surface of %s. " % (self.sidelength**2 * 6)) 

# user only knows the object is vaguely cubic 
obj = CubicObject(sidelength=1.0) 
# user thinks the object is a Box 
obj.__class__ = Box 
obj.paint() 
# user changes mind and thinks its a piece of Tofu 
obj.__class__ = Tofu 
obj.eat() 
obj.paint() # generates an error as it should, since we cannot paint Tofu 

私の二つの質問があります:私はその__class__属性を変更するとき

  • どのようなクラスAのプロパティは、オブジェクトのobjの に転送されますか?どの機能が
    と呼ばれ、どの属性が更新されているのか、それ以外にobj という名前がAのいずれかの名前に変更された場合はどうなりますか?
  • 私が望む振る舞いを実現するために、他にもクリーンな方法がありますか? が必要な場合は、オブジェクトobjを破棄してもう1つ、 を再作成できますが、この場合は一般的な方法( obj = RoundObject(subclasstype='Tofu')のように コードのために)で行いたいと思います。

基本的な問題は、ユーザーがCubicObjectのサブクラスで独自の関数を実装できることと、プログラムの実行中にこれらのサブクラスを切り替えることができるということです。

+6

これは大きなXY問題のようです。正確に何をしようとしているのですが、この機能が必要ですか? –

+1

なぜこれが不可能であるかを示すダースの例を考え出すことができます。 –

+0

これは、型キャストがあるJavaまたはC++で行うと思われることです。 – synchronizer

答えて

2

私はそのクラス属性を変更したときに、どのようなクラスAのプロパティは、オブジェクトのobjの に転送されますか?どのような関数が呼び出され、 の属性が更新されますか、それ以外の場合obj はAの名前に変更されますか?

すべてのインスタンスに割り当てられた属性が保持されます。つまり、Pythonオブジェクトには、通常、すべてのインスタンス属性が記録され、保持される属性が保持されます。オブジェクトのクラスは、割り当てられたクラスに効果的に変更されます。 (Pythonランタイムは、異なるメモリレイアウトを持つオブジェクトの割り当てを__class__に禁止しています)。つまり、新しいクラスのすべてのメソッドとクラス属性がインスタンスで使用可能であり、オブジェクトがこの新しいクラスで作成されたかのように、以前のクラスのメソッドまたはクラス属性は存在しません。 割り当てによって引き起こされる副作用はありません(特別な方法は呼び出されません) あなたが作っているものについては、それは「効果があります」。

私が望む振る舞いを実現するために、他にもクリーンな方法がありますか? が必要な場合は、オブジェクトobjを破棄して別のオブジェクトを作成することもできますが、 ですが、この場合はコードの他の部分のためobj = RoundObject(subclasstype = 'Tofu')のようにしたいと思います。 )。

はい、ご指摘のとおり、これは最善の方法ではありません。 あなたが持つことができるものは、あなたが必要とする別個のメソッドを持つクラス階層であり、オブジェクトを属性として取得します。また、何をしているかに応じて、この外部階層の新しいオブジェクトを作成し、変わらない。これはAdapter Patternとして知られています。

class CubicObject(object): 
    name = 'Baseclass' 

    def __init__(self, sidelength): 
     self.sidelength = sidelength 


class BaseMethods(object): 
    def __init__(self, related): 
     self.related = related 

class Tofu(BaseMethods): 
    name = 'Class A' 

    def eat(self): 
     print("I've eaten a volume of %s. " % (self.related.sidelength**3)) 


class Box(BaseMethods): 
    name = 'Class B' 

    def paint(self): 
     print("I painted a surface of %s. " % (self.related.sidelength**2 * 6)) 

# user only knows the object is vaguely cubic 
obj = CubicObject(sidelength=1.0) 
# user thinks the object is a Box 
box = Box(obj) 
box.paint() 

# user changes mind and thinks its a piece of Tofu 
tofu = Tofu(obj) 

tofu.eat() 
# or simply: 
Tofu(obj).eat() 

あなた自身、マニュアルのクラスでそれを転がし、またはプロセスの一部を自動化する機能を実装してよく知られており、テストライブラリを使用することができます。このようなライブラリの1つはzope.interfaceです。アダプタパターンを使用して巨大で複雑なシステムを記述することができます。したがって、数百種類のオブジェクトを持つことができます。side_length属性を持つ限り、それらに「キュービック」インタフェースを持つものとしてマークすることができます。そして、あなたはside_length属性で "Cube"のことを行うクラスを数十個用意しています。zope.interfaceの機能では、Cubicインターフェイスを持つオブジェクトの何十かを、元のオブジェクトをパラメータとして渡します。

zope.interfacesしかし、zope.interfacesは、20年近くの使用の間、必要に応じて文書化されていないため、少し難解かもしれません(そして、ある時点では、XMLファイルを使用してインタフェースとアダプタを宣言しています。 XMLを扱う)ので、小規模なプロジェクトの場合は、上記のように手動でロールバックすることができます。

私の現在の実装では、すでにデリゲートオブジェクトを使用していますが、それは私が(私は通常、デリゲートの すべての機能を複製することデリゲートオブジェクトに提供したいAPI 内のすべての興味深い機能が非表示になりますので、それが 非現実的ですオブジェクトだが、理解できないほど混乱する人は 人)。

あなたの実使用例が大きいので、そのSAのケースが実際に学び、zope.interfaceを使用する - しかし、完全インタフェース/レジストリ/アダプタシステムへの別の回避策は、Tofu上のいくつかのCubeメソッドのアクセスを許可したい場合

class BaseMethods(object): 
    def __init__(self, related): 
     self.related = related 

    def __getattr__(self, attr): 
     return getattr(self.related, attr) 
1

A:と他の人が私は再書き込みは必要ありませんし、あなたが方法を取得できるようになる魔法__getattr__ Pythonのメソッドの上に持っており、透明な方法で参照されるオブジェクトの属性BaseMethodsクラスに実装することですborg patternのバリエーションはここで助けになるかもしれません:

obj = box 
obj.paint() 
obj = tofu 
obj.eat() 
obj.paint() 

sidelengthの両方で共有されます。同じ状態を保ち、それらの間の

def make_objs(classes, *args, **kwargs): 
    __shared_state = {} 
    return tuple(cls(__shared_state, *args, **kwargs) for cls in classes) 


box, tofu = make_objs(sidelength=1.0, classes=(Box, Tofu)) 
スイッチバック

と力:

class CubicObject(object): 
    name = 'Baseclass' 

    def __init__(self, __shared_state, sidelength): 
     self.__dict__ = __shared_state 
     self.sidelength = sidelength 


class Tofu(CubicObject): 
    name = 'Class A' 

    def eat(self): 
     print("I've eaten a volume of %s. " % (self.sidelength**3)) 


class Box(CubicObject): 
    name = 'Class B' 

    def paint(self): 
     print("I painted a surface of %s. " % (self.sidelength**2 * 6)) 

は今、同じ状態を共有する複数のインスタンスを作ります。

+0

オブジェクトのいずれも状態を変更することがなく、そのファセットのいずれも存在しない場合には、それはリモートでしか使用できません。 – jsbueno

+0

ところで、あなたの例では、引数 'classes'の二重宣言のためエラーが発生します – jsbueno

+0

キーワード引数' box、tofu = make_objs(sidelength = 1.0、classes =(Box、Tofu)) 'のみを使うか、最初の 'box、tofu = make_objs((Box、Tofu)、1.0)'となります。 –

関連する問題