如何在Python中转换对象
我有两个类(我们称它们为 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 个回答
没有直接的方法。
你可以这样定义 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中没有“类型转换”这个概念。任何一个类的子类都被视为其父类的一个实例。想要实现特定的行为,可以通过正确调用父类的方法,以及重写类的属性来达到目的。
更新:随着静态类型检查的出现,现在有了“类型转换”——请看下面的内容。
在你的例子中,你可以创建一个子类的初始化方法,这个方法接收父类并复制它的相关属性——所以,你的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
正如其他回答所说的,没有类型转换。你可以创建子类,或者使用装饰器来制作带有额外功能的新类型。
这里有一个完整的例子(感谢如何制作函数装饰器链?)。你不需要修改原来的类。在我的例子中,原来的类叫做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)