如何在Python中进行对象转换

26

我有两个类(我们称之为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

预期结果:
我正在包装Working。
我正在工作。
--
我正在包装ReturnStatement。
我是一个ReturnStatement。
问题是否能够解决?我也很好奇这个问题是否可以在PHP中解决。除非我得到一个可行的解决方案,否则我不能接受答案,请编写可行的代码以便获得认可。

8
在Python中不存在"casting"这个词。 - Ignacio Vazquez-Abrams
另外,你不应该使用 "ReturnStatement().act()" - 如果你想让另一个类的 act 方法在当前对象上工作,请使用 returnStatement.act(self) - 或者将其标记为 classmethod 或 staticmethod - 如果它不需要当前对象的实例。 - jsbueno
Working.do() 返回一个 ReturnStatement。我想让 MutantWorking.do() 返回一个 MutantReturnStatement。我知道 Python 中不存在类型转换,但问题确实存在。有解决方案吗? - Visko
无论我是重写MutantReturnStatement的__init__()方法来完成额外的工作并返回ReturnStatement,还是重写MutantReturnStatement的act()方法来完成ReturnStatement的act()方法的工作,都没有关系。因为Working使用的是ReturnStatement而不是MutantReturnStatement,所以我无法访问MutantReturnStatement。 - Visko
5个回答

12

正如其他答案已经解释的那样,Python中没有强制类型转换。但是您可以使用装饰器创建子类或创建具有额外功能的修改后的新类型。

以下是一个完整的示例(感谢How to make a chain of function 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)

更新: 将日志应用于类的所有方法

如您现在特别要求这样做。您可以遍历所有成员并将日志应用于它们。但是,您需要定义一个规则来修改哪些类型的成员。下面的示例排除了名称中带有__的任何方法。

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)

1
我认为你理解了我的问题,到目前为止这似乎是“答案”。谢谢! - Visko
@Visko,我觉得你误读了我的回答。我的'loghello'就是你要求的'MutantWorking'。你不需要修改'Working',就像我没有修改'hello'一样。如果你觉得还是不清楚或者不同意,我可以尝试更新我的回答。 - Johan Lundberg
也许我理解错了什么,但它是否返回一个MutantReturnState对象?如果不是,那么这不是一个可接受的解决方案。 - Visko
所以如果我有12个要重写的ReturnStatement方法,那么我必须调用12个指令,例如ReturnStatement.Act=logging(ReturnStatement.Act)吗?这就是为什么我不喜欢这个解决方案,但这是最接近我想要的。我宁愿说在Python中做这件事是不可能的,问题只能通过转换来解决。 - Visko
那是你使用的肮脏黑客魔法 :). 不过它能够工作,所以我必须接受它 :). 但这并不会改变我的想法,即Python缺少这个功能,它比在MutantWorker中简单转换返回语句要复杂得多 :). 无论如何,感谢你的回答,你做得很好! :) - Visko
显示剩余3条评论

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()

然后将您的MutantWorking类更改为:

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

如果你有很多(比如超过3个)属性需要复制,那么在__init__方法中不要写太多的self.attr = other.attr语句,有一些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
        

再强调一遍 - 这种方法可能有效,但不推荐使用。

顺便说一下,我对其他允许转换的面向对象语言不太了解 - 但是在任何语言中都允许将类型强制转换为子类吗?这有意义吗?我认为只能将类型强制转换为父类。

更新:当我们使用PEP 484中描述的类型提示和静态分析方式时,有时静态分析工具无法理解正在发生什么。因此,就有了typing.cast调用:它在运行时绝对不会做任何事情,只是返回传递给它的相同对象,但工具会“学习”返回对象的类型,并且不会对其抱有任何疑虑。它可以消除帮助工具中的类型错误,但我不能强调它在运行时没有任何影响:

In [18]: from typing import cast                                                                                                   

In [19]: cast(int, 3.4)                                                                                                            
Out[19]: 3.4

你能否用代码回答我的问题?否则我觉得你没有回答我的问题。 - Visko
@jsbueno,是的,你可以在例如C++中定义从任何对象到任何对象的强制转换。 - Johan Lundberg
我并不一定知道非变异类的属性。假设它们是导入到Python的C++库。 - Visko
打字更新正是我所需要的 - 非常感谢。 - Chad Brockman

1

没有直接的方法。

您可以像这样定义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()

我是Python的新手,代码中可能会有一些错误,但它展示了我的想法。 - Jurlie
我喜欢你的回答!如果没有人提出其他更好的技术,我会接受这个 :)。 - Visko
@Visko,如果你喜欢某个东西,请点赞+1(您可以多次点赞)。此外,使用装饰器,您可以无需为每个方法制作一个这样的代码块来完成此操作。 - Johan Lundberg
这个解决方案仍然不完美。我忘了提到我需要MutantWorking必须返回具有几乎像ReturnStatement一样工作的MutantReturnStatement,只是它的act()方法被重载了。在这个解决方案中,我可以将returnStatement作为属性访问,这改变了我使用act()的方式(而不是rs.act(),现在是rs.returnStatement.act())。 - Visko

0

这里不需要强制类型转换。你只需要

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

这将显然给出正确的返回值和所需的输出。


1
这不是一个好的解决方案,因为我必须返回一个相同状态的对象(为了保持简单性,我的示例中未包含属性)。这会返回一个新实例,这不是我想要的。 - Visko

0

你所做的不是强制类型转换,而是类型转换。不过,你可以写出类似以下的代码:

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()成员函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接