如何在Python中转换对象

27 投票
5 回答
125204 浏览
提问于 2025-04-17 12:11

我有两个类(我们称它们为 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 中能否解决。除非我得到一个有效的解决方案,否则我不能接受这个答案,所以请写出能工作的代码,以便被接受。

5 个回答

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

如果你想在__init__方法中复制很多属性(比如超过3个),有一些Python特有的方法可以避免写很多self.attr = other.attr的代码——最简单的方法就是直接复制另一个实例的__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
12

正如其他回答所说的,没有类型转换。你可以创建子类,或者使用装饰器来制作带有额外功能的新类型。

这里有一个完整的例子(感谢如何制作函数装饰器链?)。你不需要修改原来的类。在我的例子中,原来的类叫做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)

撰写回答