How do I downcast in python
Asked Answered
B

3

13

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
Brawl answered 3/3, 2013 at 16:14 Comment(6)
why downcast? At some point you were able to create an instance of the base class! Why not create an instance of the derived class from the beginning?Solecism
I guess the main motivation would be laziness, combined with a sense that OO is supposed to make things easier in the long run - by planning of data structures. In some senses by saying that B is a subclass of A surely python should be able to meld from one to the other without me hardcoding it? - Or is that unreasonable? Suppose i have a base class with many subclasses. OO would make you think that a change to the base class wouldn't require changes to the subclasses - but in your way it would.Brawl
Think Duck Typing. If it quacks like a Psd_Peak (that is, it has Psd_Peak methods) it is a Psd_Peak. The method you want is sitting right there on the variable. Just call it. You can use isinstance(Psd_Peak) or hasattr(myvar, 'my_method') or just call it and catch AttributeError for when you guess wrong.Chabot
I like the duck typing idea. Unfortunately it doesn't really solve my problem. In essence what I'm asking is how do I stick a beak onto my animal to make it into a duck....Brawl
Why not just make peakfind return a Psd_Peak?Baldridge
in this simplified example i can obviously do that - however the peak finding code is supposed to be reasonably general- as finding a peak is and i re-use it quite a bit. the other stuff i want to do whilst knowing it is a power spectral density should only relate to the Psd_Peak object.Brawl
B
11

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.

Brainwork answered 3/3, 2013 at 16:19 Comment(4)
Thanks for the clarification. I guess in a sense i don't mind if the process is one of 'casting' or 'converting'. My contention though is that it should be something that is possible automagically by python - to a disinterested person, the fact that I've defined one class as a subtype of the other should give python almost everything it needs to do this. Other point about 'converting'/'casting'...for large objects would this not be incredibly wasteful?Brawl
This isn't something that could be done automatically. Suppose you convert an instance to its parent class after initializing it. But the subclass has changed the meaning of a key attribute, or accepts a wider range of values for the attribute than does the base class. The base methods might not be able to make sense of the data in the subclass instance's attributes. How is Python supposed to know that?Brainwork
To address your second question, yes it could be time-consuming for large objects. Fortunately you hardly ever need to do it. In any case, if performance were that important, you wouldn't be using Python.Brainwork
One reason way you may care about types is Pickling. Some C++ objects that are marshalled in python may not pickle correctly, so you may have a type system that allows you to downcast to avoid the C++ objects before picklingTurman
R
2

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
    # ...
Rambow answered 10/12, 2018 at 2:39 Comment(0)
C
1

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)
Chabot answered 3/3, 2013 at 19:58 Comment(3)
Thanks for the suggestion. I guess I'm not entirely happy with it though - since it requires 'peakfind' which should be pretty general to know what type of data it is working on... Seems nicer to have a downcast (or conversion) or whatever after the fact. You are right that Peak isn't a Psd_Peak - BUT Psd_Peak is a Peak - therefore there is a simple transform to it - I was just surprised that python hadn't provided it. Since it is written by people much clever than me, it presumably is a design decision rather than an omission?Brawl
** Actually your answer has got me thinking - I guess i could make peakfind take the actual constructor as an argument - then it could do its stuff on the object, not knowing if it is a Peak, or a Psd_Peak, or a Foo_Peak etc.Brawl
@Brawl yeah, that's pretty much what the example does. Follow the steps through and you'll see that the factory is calling Psd_Peak's init implicitly when instatiating the class.Chabot

© 2022 - 2024 — McMap. All rights reserved.