如何在Python中进行下转型
我有两个类,一个是从另一个类继承来的。我想知道怎么把一个对象转换成子类的类型,或者创建一个子类的新变量。我查了一下,发现大多数人都不太赞成这样做,像是“向下转型”这种做法似乎不太好,还有一些不太靠谱的解决方法,比如直接设置实例的class属性,但这看起来也不是个好主意。
例如: http://www.gossamer-threads.com/lists/python/python/871571 http://code.activestate.com/lists/python-list/311043/
我还有一个小问题——向下转型真的那么糟糕吗?如果是的话,为什么呢?
我下面有一个简化的代码示例——基本上我有一些代码在分析了x和y数据后创建了一个Peak对象。在这段代码外,我知道这些数据是“PSD”数据,也就是功率谱密度,所以它有一些额外的属性。我该怎么从Peak转型到Psd_Peak呢?
"""
Two classes
"""
import numpy as np
class Peak(object) :
"""
Object for holding information about a peak
"""
def __init__(self,
index,
xlowerbound = None,
xupperbound = None,
xvalue= None,
yvalue= None
):
self.index = index # peak index is index of x and y value in psd_array
self.xlowerbound = xlowerbound
self.xupperbound = xupperbound
self.xvalue = xvalue
self.yvalue = yvalue
class Psd_Peak(Peak) :
"""
Object for holding information about a peak in psd spectrum
Holds a few other values over and above the Peak object.
"""
def __init__(self,
index,
xlowerbound = None,
xupperbound = None,
xvalue= None,
yvalue= None,
depth = None,
ampest = None
):
super(Psd_Peak, self).__init__(index,
xlowerbound,
xupperbound,
xvalue,
yvalue)
self.depth = depth
self.ampest = ampest
self.depthresidual = None
self.depthrsquared = None
def peakfind(xdata,ydata) :
'''
Does some stuff.... returns a peak.
'''
return Peak(1,
0,
1,
.5,
10)
# Find a peak in the data.
p = peakfind(np.random.rand(10),np.random.rand(10))
# Actually the data i used was PSD -
# so I want to add some more values tot he object
p_psd = ????????????
编辑
感谢大家的贡献……我有点沮丧(懂我意思吗?)因为到目前为止的回答似乎建议我花时间去硬编码从一个类类型到另一个类类型的转换。我想出了一个更自动化的方法——基本上是遍历类的属性,把它们一个一个转移过去。大家觉得这个方法怎么样?这样做合理吗?还是说会带来麻烦?
def downcast_convert(ancestor, descendent):
"""
automatic downcast conversion.....
(NOTE - not type-safe -
if ancestor isn't a super class of descendent, it may well break)
"""
for name, value in vars(ancestor).iteritems():
#print "setting descendent", name, ": ", value, "ancestor", name
setattr(descendent, name, value)
return descendent
3 个回答
这不是一个向下转型的问题(我觉得)。peekfind() 方法创建了一个 Peak 对象 - 这个对象不能被转型成 Psd_Peak 对象,因为它本身就不是 Psd_Peak 类型的。然后你想从这个 Peak 对象创建一个 Psd_Peak 对象。在像 C++ 这样的语言中,你可能会依赖默认的复制构造函数,但这在这里也不行,因为你的 Psd_Peak 类在构造时需要更多的参数。总之,Python 没有复制构造函数,所以你最后得到的代码就显得比较繁琐(比如 fred=fred, jane=jane 这种写法)。
一个好的解决办法可能是创建一个对象工厂,传入你想要的 Peak 对象类型,让它为你创建合适的对象。
def peak_factory(peak_type, index, *args, **kw):
"""Create Peak objects
peak_type Type of peak object wanted
(you could list types)
index index
(you could list params for the various types)
"""
# optionally sanity check parameters here
# create object of desired type and return
return peak_type(index, *args, **kw)
def peakfind(peak_type, xdata, ydata, **kw) :
# do some stuff...
return peak_factory(peak_type,
1,
0,
1,
.5,
10,
**kw)
# Find a peak in the data.
p = peakfind(Psd_Peak, np.random.rand(10), np.random.rand(10), depth=111, ampest=222)
请看下面的例子。另外,一定要遵循LSP(里氏替换原则)
class ToBeCastedObj:
def __init__(self, *args, **kwargs):
pass # whatever you want to state
# original methods
# ...
class CastedObj(ToBeCastedObj):
def __init__(self, *args, **kwargs):
pass # whatever you want to state
@classmethod
def cast(cls, to_be_casted_obj):
casted_obj = cls()
casted_obj.__dict__ = to_be_casted_obj.__dict__
return casted_obj
# new methods you want to add
# ...
在Python中,你其实并不是在“转换”对象,而是一般是把旧的对象转变成新的对象——也就是说,先创建一个新对象,然后把旧的对象丢掉。为了让这个过程顺利进行,新对象的类必须在它的__init__
方法中设计成能够接受旧对象的实例,并进行相应的处理(有时候,如果一个类在创建时可以接受多种类型的对象,它会有不同的构造方法来处理这些情况)。
你确实可以通过把一个实例的__class__
属性指向另一个类来改变这个实例的类,但这样做可能会导致这个类和实例之间不兼容。此外,我认为这种做法有点“味道”,暗示你可能应该换个方法来解决问题。
在实际使用中,你几乎不需要担心Python中的类型问题。(当然有一些明显的例外,比如尝试把两个对象相加。在这种情况下,Python会尽量宽松地检查类型;它会检查是否是数字类型,或者是否可以转换成数字,而不是要求特定的类型。)所以,通常来说,只要对象具备代码所需的属性和方法,实际的类是什么并不重要。