How do I change out the underlying object inside a method call?
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
retur开发者_开发技巧n
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"?
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()
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'])
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.
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".
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.
精彩评论