web-dev-qa-db-ja.com

Pythonでオブジェクトをキャストする方法

変更できない2つのクラス(WorkingとReturnStatementと呼びます)がありますが、両方をログで拡張したいと思います。トリックは、WorkingのメソッドがReturnStatementオブジェクトを返すため、新しいMutantWorkingオブジェクトもMutantReturnStatementにキャストできない限りReturnStatementを返すということです。コードで言う:

# these classes can't be changed
class ReturnStatement(object):
    def act(self):
        print "I'm a ReturnStatement."

class Working(object):
    def do(self):
        print "I am Working."
        return ReturnStatement()

# these classes should wrap the original ones
class MutantReturnStatement(ReturnStatement):
    def act(self):
        print "I'm wrapping ReturnStatement."
        return ReturnStatement().act()

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        # !!! this is not working, I'd need that casting working !!!
        return (MutantReturnStatement) Working().do()

rs = MutantWorking().do() #I can use MutantWorking just like Working
print "--" # just to separate output
rs.act() #this must be MutantReturnState.act(), I need the overloaded method

期待される結果:
私はワーキングをラッピングしています。
私は働いています。
-
ReturnStatementをラップしています。
私はReturnStatementです。

問題を解決することは可能ですか?また、PHPで問題を解決できるかどうかも知りたいです。機能するソリューションが得られない限り、答えを受け入れることができないので、受け入れられるように機能するコードを書いてください。

16
Visko

他の回答がすでに説明したように、キャストはありません。 decoratorsを使用して、サブクラスを作成したり、機能を追加した新しいタイプを変更したりできます。

ここに完全な例があります(関数デコレータのチェーンを作成する方法 クレジットへのクレジット )。元のクラスを変更する必要はありません。私の例では、元のクラスはWorkingと呼ばれています。

# decorator for logging
def logging(func):
    def wrapper(*args, **kwargs):
        print func.__name__, args, kwargs
        res = func(*args, **kwargs)
        return res
    return wrapper

# this is some example class you do not want to/can not modify
class Working:
    def Do(c):
        print("I am working")
    def pr(c,printit):   # other example method
        print(printit)
    def bla(c):          # other example method
        c.pr("saybla")

# this is how to make a new class with some methods logged:
class MutantWorking(Working):
    pr=logging(Working.pr)
    bla=logging(Working.bla)
    Do=logging(Working.Do)

h=MutantWorking()
h.bla()
h.pr("Working")                                                  
h.Do()

これは印刷されます

h.bla()
bla (<__main__.MutantWorking instance at 0xb776b78c>,) {}
pr (<__main__.MutantWorking instance at 0xb776b78c>, 'saybla') {}
saybla

pr (<__main__.MutantWorking instance at 0xb776b78c>, 'Working') {}
Working

Do (<__main__.MutantWorking instance at 0xb776b78c>,) {}
I am working

また、クラスを変更できない理由を教えてください。試しましたか?なぜなら、サブクラスを作成するための代替として、動的に感じる場合、できるほとんどの場合、所定の場所にある古いクラス:

Working.Do=logging(Working.Do)
ReturnStatement.Act=logging(ReturnStatement.Act)

更新:ログをクラスのすべてのメソッドに適用します

あなたが今、具体的にこれを求めたように。あなたはcanすべてのメンバーをループし、それらすべてにロギングを適用します。ただし、変更するメンバーの種類に関するルールを定義する必要があります。次の例では、名前に__が含まれるメソッドを除外しています。

import types
def hasmethod(obj, name):
    return hasattr(obj, name) and type(getattr(obj, name)) == types.MethodType

def loggify(theclass):
  for x in filter(lambda x:"__" not in x, dir(theclass)):
     if hasmethod(theclass,x):
        print(x)
        setattr(theclass,x,logging(getattr(theclass,x)))
  return theclass

これで、ログに記録されたクラスの新しいバージョンを作成するために必要なことは次のとおりです。

@loggify
class loggedWorker(Working): pass

または、既存のクラスを適切に変更します。

loggify(Working)
7
Johan Lundberg

Pythonには「キャスティング」はありません。クラスのサブクラスは、その親のインスタンスと見なされます。スーパークラスメソッドを適切に呼び出し、クラス属性をオーバーライドすることにより、望ましい動作を実現できます。

この例でできることは、スーパークラスを受け取り、その関連属性をコピーするサブクラス初期化子を用意する必要があることです。つまり、MutantReturnステートメントは次のように書くことができます。

class MutantReturnStatement(ReturnStatement):
    def __init__(self, previous_object=None):
        if previous_object:
            self.attribute = previous_object.attribute
            # repeat for relevant attributes
    def act(self):
        print "I'm wrapping ReturnStatement."
        return ReturnStatement().act()

そして、MutantWorkingクラスを次のように変更します。

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        return MutantReturnStatement(Working().do())

コピーしたい属性が3つ以上ある場合など、self.attr = other.attrメソッドに__init__行を多く含まないようにするPythonicの方法があります。他のインスタンスの__dict__属性をコピーします。

または、何をしているのかわかっている場合の場合、ターゲットオブジェクトの__class__属性を目的のクラスに変更することもできますが、これは誤解を招き、微妙なエラーにつながる可能性があります(サブクラスの__init__メソッドが呼び出されない、Pythonで定義されていないクラスで機能しない、その他の考えられる問題)、私はこのアプローチを推奨しません-これは「キャスティング」ではなく、オブジェクトの変更をブルートフォースでイントロスペクションし、答えを完全に保つためにのみ含まれています:

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        result = Working.do(self)
        result.__class__ = MutantReturnStatement
        return result

繰り返しますが、これは機能するはずですが、機能しません。前者の方法を使用してください。

ちなみに、キャストを許可する他のOO=言語についてはあまり経験がありませんが、どの言語でもサブクラスにキャストできますか?理にかなっていますか?キャストのみ許可されていると思います親クラスに。

2
jsbueno

直接的な方法はありません。

次のように、MutantReturnStatementのinitを定義できます。

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

次のように使用します。

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        # !!! this is not working, I'd need that casting working !!!
        return MutantReturnStatement(Working().do())

そして、次のように、ラッパーでReturnStatementを継承しないようにする必要があります

class MutantReturnStatement(object):
    def act(self):
        print "I'm wrapping ReturnStatement."
        return self.retStatement.act()
1
Jurlie

あなたがすることはキャストではなく、型変換です。それでも、次のようなものを書くことができます

_def cast_to(mytype: Type[any], obj: any):
    if isinstance(obj, mytype):
        return obj
    else:
        return mytype(obj)

class MutantReturnStatement(ReturnStatement):
    def __init__(self, *args, **kwargs):
        if isinstance(args[0], Working):
            pass  
            # your custom logic here 
            # for the type conversion.
_

使用法:

_cast_to(MutantReturnStatement, Working()).act()
# or simply
MutantReturnStatement(Working()).act()
_

(例では、MutantReturnStatementには.do()メンバー関数がないことに注意してください。)

0
Barney

ここでキャストする必要はありません。あなたはただ必要です

class MutantWorking(Working):
def do(self):
    print "I am wrapping Working."
    Working().do()
    return MutantReturnStatement()

これは明らかに正しいリターンと望ましいプリントアウトを与えます。

0
Falcon