tags:

views:

92

answers:

5

I thought all function and method arguments in Python were passed by reference, leading me to believe that the following code would work:

class Insect:
    def status(self):
        print "i am a %s" % self

class Caterpillar(Insect):
        def grow_up(self):
                self = Butterfly() # replace myself with a new object
                return

class Butterfly(Insect):
        pass

my_obj = Caterpillar()
my_obj.status() # i am a <__main__.Caterpillar instance at 0x100494710>

# ok, time to grow up:
my_obj.grow_up()

my_obj.status() # want new Butterfly object, but it is still Caterpillar! :(

How do I go about changing the object itself within a method call?

-- EDIT:

So far, the responses have been:

1) Change self.__class__ to change the class associated with the current object.
2) "Don't hold it that way."

The original question: How do I make a new object that takes the place of the old one from within a method call defined on the old class?

I think the answer might actually be "you can't."

-- EDIT2:

Why is everyone deathly afraid of saying "It's not possible"?

+2  A: 

self.__class__ = Butterfly may work, because it's rebinding a qualified name (which is a completely different thing from rebinding a bare name).

Rebinding a barename never has any effect on whatever object was previously bound to it (unless that barename was the only reference to that "previous object", in which case the latter may be destroyed -- but that's obviously not the case here, since self is a reference to the same objects as my_obj and so self cannot be the only reference to it).

Edit:

You refer to needing to initialize the newly-re-classed object -- that's easy:

self.__dict__.clear()
self.__class__ = Butterfly
self.__init__(whatever, you, want)

But then in a comment you mention needing to "back up the attributes" -- I don't know what exactly you mean, or why that would at all differ from your "dream case" of assigning directly to self (which would of course blow the attributes away, too). Maybe the idea is that (some of) the arguments to __init__ need to be attributes of self? Then, for example, you could code it like:

d, self.__dict__ = self.__dict__, {}
self.__class__ = Butterfly
self.__init__(d['whatever'], d['you'], d['want'])
Alex Martelli
I don't want to just reclassify the object, I want to instantiate an entirely new object. In my actual problem, grow_up() would be passing arguments (based on self attributes) to the Butterfly constructor.
sneak
There is **no** way in Python to have `self` (a barename, passed as an argument) be affected "in the caller" by barename assignments done in any method or function. You can call `self.__init__` after you assign the `__class__` (which is emphatically **NOT** a "rename" of anything!!!) with whatever arguments you want -- you can even call `self.__dict__.clear()` before then to make sure you have no "stray" state from the previous "incarnation" left around -- but you cannot change the **identity** of the object -- `id(my_obj)` **will** be identical before and after the call, **whatever** you do.
Alex Martelli
+3  A: 

When you say self = Butterfly() all you're doing is changing what the variable self points to; you're not changing what self is, any more than:

x = 1
x = 2

... changes the number 1 to 2.

In general, when people need this, rather than change the type of the object (which many languages flat out forbid) people use a composition strategy:

class Lepidoptera(Insect):
    def __init__(self):
        self.stage = Caterpillar()

    def status(self):
        print 'I am a %s' % self.stage

    def grow_up(self):
        if self.stage.adult:
            raise TypeError('Already an adult')

        self.stage = Butterfly()
Chris B.
What I need is a method WITHIN the Caterpillar class that can change the object into a member of Butterfly. The only way I've seen so far is to iterate over the attributes, back them up, change \_\_class__, empty \_\_dict__, then call \_\_init__ with whatever I need out of the backed-up attributes. All of this seems really hacky to me, and I was hoping there was an easier way.
sneak
And we're saying, that's not the way object-oriented languages are designed. Any solution is going to be hacky. This suggests you might want to rethink your architecture.
Chris B.
That's a gross oversimplification that basically amounts to "Don't hold it that way." Perl handles this sort of thing just fine.
sneak
Funny how Perl is so rarely cited for the elegance and power of its object semantics.
Chris B.
Not to turn this into a flame war, but all of CPAN stands in counter to your implication.
sneak
+1  A: 

Remember that variables in Python are names that are bound to objects. Read section 4.1 of the Language Reference for more details. The gist of it is that a method parameter is a name that is bound to the object that is passed in the invocation.

When you "assign a new value" to the variable, you are simply binding the local name to a new object. The object that it originally referred to is unchanged.

When you invoke a mutating method on a variable, the method can change the internal state of the object by using a qualified name such as self.var which is what Alex is referring to in his answer. If you rebind self, you are not modifying the object.

This is one of the more interesting features of Python. It is a little different than most other languages in that you really have no way to access the raw object pointer (e.g., this) within a method. The only thing that you have access to is the bound name of the instance.

Edit: I don't know how I managed to forget to answer your actual question, but the answer is certainly that you cannot change the underlying object from within a method call. My rambling was the why part without actually providing an answer.

D.Shawley
+1  A: 

Your design is fundamentally flawed. A caterpillar and butterfly are not different creatures, they are different states of the same creature.

class Lepidoptera(Insect):

    # Just use strings for states
    CATERPILLAR = "caterpillar"
    BUTTERFLY = "butterfly"

    def __init__(self):
        self.state = Lepidoptera.CATERPILLAR

    def grow_up(self):
        self.state = Lepidoptera.BUTTERFLY

    def status(self):
        """ Override superclass method. """
        print "I am a %s, currently in the state: %s" % (self, self.state)

If you needed something more sophisticated, the states should be more sophisticated objects that take extra parameters (or simply let the Lepidoptera class take care of it internally).

You are effectively asking how to change a lepidoptera into a cricket, which cannot happen. This is why you cannot find a way to represent this concept in your program (or why it seems so hacky). You've decided to use objects to represent insects, but then you actually create classes that represent insect states. Then you wonder why you can't model the insect behaviour properly.

Working the other way, if you want classes to represent insect states, then clearly you need other classes to represent to insects themselves, which would then contain these states and the methods to change them.

In terms of your real problem, it sounds like you're confusing "the thing with the state" with "the state itself".

detly
My problem was that of python syntax and features, not of program design. Perhaps I should have used better class names in my example.
sneak
It can be distracting. And, you know, everyone here is *so* pedantic...
detly
It's ok, they're going to be making great advances in Asperger's medications in the next 10-15 years, I just know it. Everything's going to work out all-right.
sneak
A: 

As Alex puts it clearly, there is no way to do that strictly. And I can't think of a way it such a thing could be desirable. Maybe you need a factory function insetad? Or different states of the same object?

However, Python being what it is, you can have a "shell" object whose only purpose would be to grant access to an underlyign object. The underlying object could then be modified: while the shell object remains the same, the underlying object is another one.

One possible implementation is as follows (and I've tried again with an unclean implementation for __getattribute__ - it can be made better)

# -*- coding: utf-8 -*-
class Insect(object):
        pass

class Catterpillar(Insect):
    a = "I am a caterpillar"
class Butterfly(Insect):
    a = "I am a butterfly"

class Cocoon(object):
    def __init__(self, *args, **kw):
        self._true = Catterpillar(*args, **kw)

    def __getattribute__(self, attr):
        try:
            if attr != "_true":
                return getattr(object.__getattribute__(self, "_true"),attr)
        except AttributeError:
            pass
        return object.__getattribute__(self, attr)
    def __setattr__(self, attr, val):
        if attr != "_true":
            setattr(self._true, attr, val)
        else:
            object.__setattr__(self, attr, val)

    def change(self, *args, **kw):
        self._true = Butterfly(*args, **kw)

WIth this you can get stuff like:

>>>
>>> a=  Cocoon()
>>> a.a
'I am a caterpillar'
>>> a.__class__
<class '__main__.Catterpillar'>
>>> a.change()
>>> a.a
'I am a butterfly'
>>> a.__class__
<class '__main__.Butterfly'>
>>>

. On the change function you can backup whatever attributes you want, and you can exempt other attributes from change in the __getattribute__ method.

jsbueno