Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I downcast in python

I have two classes - one which inherits from the other. I want to know how to cast to (or create a new variable of) the sub class. I have searched around a bit and mostly 'downcasting' like this seems to be frowned upon, and there are some slightly dodgy workarounds like setting instance.class - though this doesn't seem like a nice way to go.

eg. http://www.gossamer-threads.com/lists/python/python/871571 http://code.activestate.com/lists/python-list/311043/

sub question - is downcasting really that bad? If so why?

I have simplified code example below - basically i have some code that creates a Peak object after having done some analysis of x, y data. outside this code I know that the data is 'PSD' data power spectral density - so it has some extra attributes. How do i down cast from Peak, to 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 = ????????????

edit

Thanks for the contributions.... I'm afraid I was feeling rather downcast(geddit?) since the answers thus far seem to suggest I spend time hard coding converters from one class type to another. I have come up with a more automatic way of doing this - basically looping through the attributes of the class and transfering them one to another. how does this smell to people - is it a reasonable thing to do - or does it spell trouble ahead?

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
like image 760
JPH Avatar asked Mar 03 '13 16:03

JPH


3 Answers

See following example. Also, be sure to obey the LSP (Liskov Substitution Principle)

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
    # ...
like image 79
Jay Yang Avatar answered Oct 16 '22 15:10

Jay Yang


You don't actually "cast" objects in Python. Instead you generally convert them -- take the old object, create a new one, throw the old one away. For this to work, the class of the new object must be designed to take an instance of the old object in its __init__ method and do the appropriate thing (sometimes, if a class can accept more than one kind of object when creating it, it will have alternate constructors for that purpose).

You can indeed change the class of an instance by pointing its __class__ attribute to a different class, but that class may not work properly with the instance. Furthermore, this practice is IMHO a "smell" indicating that you should probably be taking a different approach.

In practice, you almost never need to worry about types in Python. (With obvious exceptions: for example, trying to add two objects. Even in such cases, the checks are as broad as possible; here, Python would check for a numeric type, or a type that can be converted to a number, rather than a specific type.) Thus it rarely matters what the actual class of an object is, as long as it has the attributes and methods that whatever code is using it needs.

like image 27
kindall Avatar answered Oct 16 '22 16:10

kindall


This isn't a downcasting problem (IMHO). peekfind() creates a Peak object - it can't be downcast because its not a Psd_Peak object - and later you want to create a Psd_Peak object from it. In something like C++, you'd likely rely on the default copy constructor - but that's not going to work, even in C++, because your Psd_Peak class requires more parameters in its constructor. In any case, python doesn't have a copy constructor, so you end up with the rather verbose (fred=fred, jane=jane) stuff.

A good solution may be to create an object factory and pass the type of Peak object you want to peekfind() and let it create the right one for you.

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)
like image 32
tdelaney Avatar answered Oct 16 '22 16:10

tdelaney