2

I am using python and have an object, that object has a method. I am looking for a simple way, to replace the entire object from within that function.

E.g

class a():
    def b(self):
        self = other_object

How can you do that?

Thanks

YatShan
  • 425
  • 2
  • 8
  • 22
thebeancounter
  • 4,261
  • 8
  • 61
  • 109
  • 5
    This is impossible, and smells like an [XY problem](http://xyproblem.info/). Why do you think you need to do this? – kaya3 Feb 10 '20 at 08:34
  • `self` is the instance of `a`, I don't think you can replace it. Why do you want to do it anyway? – Guy Feb 10 '20 at 08:35
  • What's wrong with what you have? – martineau Feb 10 '20 at 08:35
  • You can have a class variable to store the `other` object and implement `__getattr__` and `__setattr__` to explicitly use the `other` object to get and set attributes. – Diptangsu Goswami Feb 10 '20 at 08:45
  • You dont need a method for it. You just do `obj = other_obj` in any part of your code. If you still think you need it, it is impossible. – sanyassh Feb 10 '20 at 08:47
  • @DiptangsuGoswami is iterating on all the attributes and setting them guaranteed to provide me an exact replica of the other objects? – thebeancounter Feb 10 '20 at 08:49
  • @thebeancounter they won't have the same id though, which means mutating one won't affect the other. But if you're ok with that, you can still go for a deep copy – Right leg Feb 10 '20 at 08:52
  • @thebeancounter if you want a copy of an object, you should consider using `copy.deepcopy`. – Diptangsu Goswami Feb 10 '20 at 08:52
  • @DiptangsuGoswami I can not use copy. I must do this from within the object, without returning a new pointer – thebeancounter Feb 10 '20 at 08:54
  • Hey, what about using a Facade-type design pattern? class A with `__getattr__(self, attrname): return getattr(self.__theinstance, attrname)` route to `self.__theinstance`, ditto setattr and you can set `self.__theinstance = other_object` ? Kinda odd, but not exceedingly so. The client code is none the wiser, except that its references point to A's instances and never change. – JL Peyret Feb 10 '20 at 21:14
  • @JLPeyret Not sure I understand, can you post an answer with code example? – thebeancounter Feb 11 '20 at 14:21

2 Answers2

1

You use a proxy/facade object to hold a reference to the actual object, the self if you wish and that proxy (better term than Facade, but not changing my code now) is what the rest of your codebase sees. However, any attribute/method access is forwarded on to the actual object, which is swappable.

Code below should give you a rough idea. Note that you need to be careful about recursion around __the_instance, which is why I am assigning to __dict__ directly. Bit messy, since it's been a while I've written code that wraps getattr and setattr entirely.

class Facade:
    def __init__(self, instance):
        self.set_obj(instance)

    def set_obj(self, instance):
        self.__dict__["__theinstance"] = instance

    def __getattr__(self, attrname):
        if attrname == "__theinstance":
            return self.__dict__["__theinstance"]                

        return getattr(self.__dict__["__theinstance"], attrname)

    def __setattr__(self, attrname, value):
        if attrname == "__theinstance":
            self.set_obj(value)

        return setattr(self.__dict__["__theinstance"], attrname, value)


class Test:
    def __init__(self, name, cntr):
        self.name = name 
        self.cntr = cntr

    def __repr__(self):
        return "%s[%s]" % (self.__class__.__name__, self.__dict__)

obj1 = Test("first object", 1)

obj2 = Test("second", 2)
obj2.message = "greetings"


def pretend_client_code(facade):
    print(id(facade), facade.name, facade.cntr, getattr(facade, "value", None))


facade = Facade(obj1)

pretend_client_code(facade)

facade.set_obj(obj2)

pretend_client_code(facade)

facade.value = 3

pretend_client_code(facade)

facade.set_obj(obj1)

pretend_client_code(facade)

output:

4467187104 first object 1 None
4467187104 second 2 None
4467187104 second 2 3
4467187104 first object 1 None

So basically, the "client code" always sees the same facade object, but what it is actually accessing depends on what your equivalent of def b is has done.

Facade has a specific meaning in Design Patterns terminology and it may not be really applicable here, but close enough. Maybe Proxy would have been better.

Note that if you want to change the class on the same object, that is a different thing, done through assigning self.__class__ . For example, say an RPG game with an EnemyClass who gets swapped to DeadEnemyClass once killed: self.__class__ = DeadEnemyClass

JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • @kaya3 yes, you can. whether or not that's a good idea is a different question, but there is plenty of code that does it. For example, it's one approach to building State Machines. If you go that route, only use classes that are designed to be used that way - having always the same method names and attribute names, just with different contents. – JL Peyret Feb 12 '20 at 03:37
  • My mistake; I have tried it in the past out of curiosity and seen an error, but apparently it's possible if both the original and new class are user-defined classes without `__slots__`. – kaya3 Feb 12 '20 at 03:43
  • 1
    ah, see, we both learned something. I didn't realize `__slots__` would interfere, but I can't say I'm surprised they do. It should be approached with a lot of caution, but it can replace a fair bit of factory design pattern boiler plate, at the cost of potentially being very surprising to the user of the code when their instances change classes on the fly. Definitely be mindful of Liskov Substitution Principle - ideally they would all share the same immediate ancestor class - and use only when appropriate. – JL Peyret Feb 12 '20 at 03:49
  • I think it could only be safe when the new class is a subclass of the old one, or no other reference to the instance exists. Otherwise e.g. a List[A] might find itself containing something not of type A, through no fault of its own. – kaya3 Feb 12 '20 at 03:56
0

You can't directly do that. What you can do is save it as an instance variable.

class A():
    def __init__(self, instance=None):
        self.instance = val or self

    # yes, you can make it a property as well.
    def set_val(self, obj):
        self.instance = obj

    def get_val(self):
        return self.instance

It is unlikely that replacing the 'self' variable will accomplish whatever you're trying to do, that couldn't just be accomplished by storing the result of func(self) in a different variable. 'self' is effectively a local variable only defined for the duration of the method call, used to pass in the instance of the class which is being operated upon. Replacing self will not actually replace references to the original instance of the class held by other objects, nor will it create a lasting reference to the new instance which was assigned to it.

Original source: Is it safe to replace a self object by another object of the same type in a method?

Bugs Buggy
  • 1,514
  • 19
  • 39