7
class Wrapper(object):
    def __init__(self, o):
        # get wrapped object and do something with it
        self.o = o
    def fun(self, *args, **kwargs):
        self = self.o # here want to swap
        # or some low level C api like
        # some_assign(self, self.o)
        # so that it swaps id() mem addr to self.o
        return self.fun(*args, **kwargs) # and now it's class A

class A(object):
    def fun(self):
        return 'A.fun'

a = A()
w = Wrapper(a)
print type(w) # wrapper
print w.fun() # some operation after which I want to loose Wrapper
print a is w # this goes False and I'd like True :) 
             # so that this is the original A() object 

Is there any way to do this in Python?

outis
  • 75,655
  • 22
  • 151
  • 221
pprzemek
  • 2,455
  • 3
  • 25
  • 25
  • @outis Yes, getting: , A.fun, False – pprzemek Oct 29 '11 at 17:32
  • 2
    It is possible to do some weired stuf like this. I'm not sure if I should tell you how, because people might start actually doing terrible things like this. – Sven Marnach Oct 29 '11 at 17:32
  • 2
    If I understand you right, you want an equivalent of Smalltalk's '#become:'? Googling for that gives me: http://wargle.blogspot.com/2009/07/smalltalks-become-in-python.html. (That said the code instills violent urges in me.) – millimoose Oct 29 '11 at 17:40
  • @Inerdia: That code is not only disguisting, ut it doesn't even work for this purpose - the `is` will still be `False`. –  Oct 29 '11 at 17:41
  • what are you trying to accomplish? – ObscureRobot Oct 29 '11 at 17:43

2 Answers2

17

Assigning to self inside a method simply rebinds the local variable self to the new object. Generally, an assignment to a bare name never changes any objects, it just rebinds the name on the left-hand side to point to the object on the right-hand side.

So what you would need to do is modify the object self points to to match the object self.o points to. This is only possible if both A and Wrapper are new-style classes and none of them defines __slots__:

self.__class__ = self.o.__class__
self.__dict__ = self.o.__dict__

This will work in CPython, but I'm not sure about the other Python implementation. And even in CPython, it's a terrible idea to do this.

(Note that the is condition in the last line of your code will still be False, but I think this does what you intend.)

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 2
    Ah, this is the trick you were talking about. +1 For sheer cleverness, despite the fact it's terribly hideous. –  Oct 29 '11 at 17:44
  • 1
    And will fail miserably in many corner cases, for example if classes derive from C-types other than `object`. – yak Apr 14 '12 at 10:30
4

No, you can't. This would require pass by reference. You can change the local variable (more precisely, the parameter) self just fine, but doing that does not influence whatever location the reference passed as argument came from (such as your w).

And given the circumstances (implicitly passed self), it's not even possible to apply the usual hacks (such as using a single-element list and mutate x[0]). Even if such tricks would work (or if there's an even more obscure hack that can do this), they'd be highly discouraged. It goes against everything Python programmers are used to. Just make the Wrapper object act as if it was replaces (i.e. forward everything to self.o). That won't make identity checks succeed, but it's by far the simplest, cleanest and most maintainable solution.

Note: For the sake of experimenting, there is a nonstandard and absolutely nonportable PyPy extension which can do this (replacing an object completely): __pypy__.become. Needless to say, it'd be extremely ill-advised to use it. Find another solution.

  • It is possible even in CPython, so "you can't" seems wrong. It is a terrible idea, though. – Sven Marnach Oct 29 '11 at 17:36
  • @SvenMarnach: Well, I'm not aware of anything (except of course writing C code that does it) the interpreter, and I'm already disturbingly familiar with such hacks. Still, I can't rule it out entirely, so I added a clause to cover that (even before your comment). –  Oct 29 '11 at 17:38
  • Added my own answer to explain how. To be honest, it doesn't do exactly what the OP asked for. +1 for the link to `__pypy__.become`, interesting. – Sven Marnach Oct 29 '11 at 17:44