如何在Python中转换对象

Vis*_*sko 15 python inheritance casting

我有两个类(让我们称之为Working和ReturnStatement),我无法修改,但我想用日志记录扩展它们.诀窍是Working的方法返回一个ReturnStatement对象,因此新的MutantWorking对象也返回ReturnStatement,除非我可以将它强制转换为MutantReturnStatement.用代码说:

# 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
Run Code Online (Sandbox Code Playgroud)

预期的结果:
我正在包装工作.
我在工作.
-
我正在包装ReturnStatement.
我是一个ReturnStatement.

有可能解决问题吗?如果问题也可以在PHP中解决,我也很好奇.除非我得到一个有效的解决方案,否则我无法接受答案,所以请编写工作代码以获得接受.

jsb*_*eno 9

Python 中没有“强制转换”。类的任何子类都被视为其父类的实例。可以通过正确调用超类方法并覆盖类属性来实现所需的行为。

更新:随着静态类型检查的出现,出现了“类型转换” - 检查如下。

在您的示例中,您可以做的是必须有一个子类初始值设定项来接收超类并复制其相关属性 - 因此,您的 MutantReturnstatement 可以这样编写:

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()
Run Code Online (Sandbox Code Playgroud)

然后将您的 MutantWorking 类更改为:

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        return MutantReturnStatement(Working().do())
Run Code Online (Sandbox Code Playgroud)

如果您想要复制很多(例如,超过 3 个 :-) )属性,则可以使用 Pythonic 方法来避免方法上有很多行 - 其中最懒的方法就是简单地复制其他实例self.attr = other.attr的属性。__init____dict__

或者, 如果您知道自己在做什么,您也可以简单地将__class__目标对象的属性更改为所需的类 - 但这可能会产生误导并导致微妙的错误(__init__子类的方法不会被调用,也不会处理非 python 定义的类和其他可能的问题),我不推荐这种方法 - 这不是“强制转换”,它是使用内省来暴力破解对象更改,并且仅包含在内以保持答案完整:

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

再说一遍 - 这应该可行,但不要这样做 - 使用前一种方法。

顺便说一句,我对其他允许强制转换的 OO 语言不太有经验,但是任何语言都允许强制转换到子类吗?是否有意义?我认为casting只允许父类。

更新:当人们按照 PEP 484 中描述的方式使用类型提示和静态分析时,有时静态分析工具无法弄清楚发生了什么。因此,有一个typing.cast调用:它在运行时绝对不执行任何操作,只是返回传递给它的相同对象,但是工具然后“了解”返回的对象是传递的类型,并且不会抱怨它。它将消除辅助工具中的键入错误,但我不能强调它在运行时没有任何影响:

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()
Run Code Online (Sandbox Code Playgroud)


Joh*_*erg 7

其他答案已经解释过没有演员表.您可以使用装饰器创建具有额外功能的子类或修改的新类型.

这是一个完整的例子(如何建立一系列函数装饰器?).您无需修改​​原始类.在我的示例中,原始类称为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()
Run Code Online (Sandbox Code Playgroud)

这将打印

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
Run Code Online (Sandbox Code Playgroud)

另外,我想了解为什么你不能修改一个类.你试过了吗?因为,作为创建子类的替代方法,如果您感觉动态,您几乎总是可以修改旧类:

Working.Do=logging(Working.Do)
ReturnStatement.Act=logging(ReturnStatement.Act)
Run Code Online (Sandbox Code Playgroud)

更新:将记录应用于类的所有方法

正如你现在特别要求的那样.您可以遍历所有成员并将日志记录应用于所有成员.但是您需要为要修改的成员类型定义规则.以下示例排除了名称中包含__的任何方法.

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
Run Code Online (Sandbox Code Playgroud)

有了这个,你需要做的就是创建一个类的新记录版本:

@loggify
class loggedWorker(Working): pass
Run Code Online (Sandbox Code Playgroud)

或者修改现有的类:

loggify(Working)
Run Code Online (Sandbox Code Playgroud)