tags:

views:

99

answers:

3

I am calling a constructor in ClassA and want to have the resulting object be of a different class (ClassB) if a certain condition is met. I've tried replacing the first argument to __init__() ('self' in the example below) within __init__() but it doesn't seem to do what I want.

in main:

import ClassA

my_obj = ClassA.ClassA(500)
# unfortunately, my_obj is a ClassA, but I want a ClassB!

in ClassA/__init__.py:

import ClassB

class ClassA:
    def __init__(self,theirnumber):
        if(theirnumber > 10):
            # all big numbers should be ClassB objects:
            self = ClassB.ClassB(theirnumber)
            return
        else:
            # numbers under 10 are ok in ClassA.
            return

in ClassB/__init__.py:

class ClassB:
    pass
+10  A: 

You need __new__() for that.

class ClassA:
    def __new__(cls,theirnumber):
        if theirnumber > 10:
            # all big numbers should be ClassB objects:
            return ClassB.ClassB(theirnumber)
        else:
            # numbers under 10 are ok in ClassA.
            return super(ClassA, cls).__new__(theirnumber)

__new__() runs as part of the class instantiation process before __init__(). Basically __new__() is what actually creates the new instance, and __init__() is then called to initialize its properties. That's why you can use __new__() but not __init__() to alter the type of object created: once __init__() starts, the object has already been created and it's too late to change its type. (Well... not really, but that gets into very arcane Python black magic.) See the documentation.

In this case, though, I'd say a factory function is more appropriate, something like

def thingy(theirnumber):
    if theirnumber > 10:
        return ClassB.ClassB(theirnumber)
    else:
        return ClassA.ClassA(theirnumber)

By the way, note that if you do what I did with __new__() above, if a ClassB is returned, the __init__() method of the ClassB instance will not be called! Python only calls __init__() if the object returned from __new__() is an instance of the class in which the __new__() method is contained (here ClassA). That's another argument in favor of the factory function approach.

David Zaslavsky
Ok, now what if I want to swap out the object with a new one inside of an object method, instead of in the constructor? I want an object that turns into other objects during its lifecycle.
sneak
@sneak, you can't: method gets `self` as an argument and so whatever they may rebind to said barename it won't have any effect outside the method. (You can of course rebind **qualified** names such as `self.__class__` -- qualified names and barenames are totally different in just about every respect you can think of).
Alex Martelli
@Alex, the fact that you can assign to `self.__class__` is an accident of implementation in CPython; the official python spec says that this *should not* work. Even if it wasn't an accident, it's a pretty horrible thing to do. In short: please don't, and especially please don't suggest it to other people.
Aaron Gallagher
@Aaron, please point me to the "official python spec" that says this should not work -- I think you're wrong, as the only mention I see in the language specs, at http://docs.python.org/reference/datamodel.html#slots , says `__class__ assignment works only if both classes have the same __slots__` (and so in particular is guaranteed to work if neither class has any `__slots__`). Much as I think you're provably wrong in what you state as a fact about "this should not work", I think you're off your rocker in saying "it's a pretty horrible thing": it's a fine technique when needed (not often).
Alex Martelli
@Alex, http://docs.python.org/reference/datamodel.html#id1I remembered that sentence, though not the footnote. Either way, the footnote seems to agree with me in the horrability of this.
Aaron Gallagher
@Aaron, "it _generally_ is not a good idea" means it **is** a good idea in the rare cases when it's needed (not often, as I said). And of course it's wrong to "handle it incorrectly" (d'uh). That's a **far** cry from "pretty horrible thing", no matter how you twist it.
Alex Martelli
I have to agree with Alex that it could be appropriate under certain very specific circumstances, and I've never heard that this behavior is an implementation accident. But I didn't want to complicate my answer by explaining that behavior - that's why I referred to it as "arcane Python black magic." I personally have never had an appropriate situation to use that feature (and I've done some weird stuff in Python).
David Zaslavsky
From everything I've read, \_\_class__ _used_ to be read-only, and was changed to writable at some later date. It's not an accident.
sneak
+5  A: 

Don't try to pervert the purpose of constructors: use a factory function. Calling a constructor for one class and being returned an instance of a different class is a sure way to cause confusion.

Ned Batchelder
I think I can manage to stay unconfused, thx.
sneak
+4  A: 

I would suggest using a factory pattern for this. For example:

def get_my_inst(the_number):
   if the_number > 10:
       return ClassB(the_number)
   else:
       return ClassA(the_number)

class_b_inst = get_my_inst(500)
class_a_inst = get_my_inst(5)
sdolan