80

I would like to replace an object instance by another instance inside a method like this:

class A:
    def method1(self):
        self = func(self)

The object is retrieved from a database.

Ayman Hourieh
  • 132,184
  • 23
  • 144
  • 116
amirouche
  • 7,682
  • 6
  • 40
  • 94

8 Answers8

105

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.

Amber
  • 507,862
  • 82
  • 626
  • 550
  • 2
    How does your answer articulate with the 7-years younger [one](https://stackoverflow.com/a/37658673/4194079) ? Is yours still "correct" ? Even if I hardly see how this could be considered a good practice, it looks like the "green tick" should be moved from yours to @Nithin's, don't you think so ? – keepAlive Jun 01 '20 at 21:03
  • 4
    No, I do not. Updating the contents of `__dict__` is not replacing an object with another one. (E.g. it will cause `is` checks to not behave how you would expect if the object had actually been replaced, if the object is a difference class it will not change, etc.) The answer from @Nithin is both dangerous and incorrect. – Amber Jun 03 '20 at 02:50
  • Finally, I would rather say that @Nithin's is just not *strictly* addressing the question. Which is indeed a good reason not to "move the tick".. The point is that doing `self.__dict__.update` actually changes all relevant `self`'s attributes in a very convenient way. In some cases, doing so may actually be what readers are looking for, showing them that their wish of changing the `self` object was not even required, i.e. was a wrong approach.. It would have been good to have an example showing why you think this is dangerous... A 700rep answer is worth it... time-consuming though. – keepAlive Jun 03 '20 at 09:18
  • 2
    @keepAlive At face value, the question is trying to replace one object instance with another. @Nithin's answer clobbers a _subset_ of one object's attributes with another's. I'm with @Amber: that's a fundamentally different behavior than what was asked for, is hacky, and might not even result in 2 objects with identical attributes. Attributes in `self` that aren't in `newObj` remain intact. Operations that depend on object references will not behave as intended, which _might_ be a critical aspect... that happens to be the case for the code I'm looking at right now that led me to this question. – drmuelr Oct 16 '20 at 19:33
  • It took me two days to get that assignment to self is just lost in void. Then I read this answer. – Vladimir Zolotykh Dec 04 '22 at 17:41
79

As far as I understand, If you are trying to replace the current object with another object of same type (assuming func won't change the object type) from an member function. I think this will achieve that:

class A:
    def method1(self):
        newObj = func(self)
        self.__dict__.update(newObj.__dict__)
martineau
  • 119,623
  • 25
  • 170
  • 301
Nithin
  • 5,470
  • 37
  • 44
  • 1
    Be aware that any existing attributes on `self` will remain when using `update`. This is an added benefit for my application :-) – NZD Aug 29 '21 at 20:09
18

It is not a direct answer to the question, but in the posts below there's a solution for what amirouche tried to do:

And here's working code sample (Python 3.2.5).

class Men:
    def __init__(self, name):
        self.name = name

    def who_are_you(self):
        print("I'm a men! My name is " + self.name)

    def cast_to(self, sex, name):
        self.__class__ = sex
        self.name = name

    def method_unique_to_men(self):
        print('I made The Matrix')


class Women:
    def __init__(self, name):
        self.name = name

    def who_are_you(self):
        print("I'm a women! My name is " + self.name)

    def cast_to(self, sex, name):
        self.__class__ = sex
        self.name = name

    def method_unique_to_women(self):
        print('I made Cloud Atlas')


men = Men('Larry')
men.who_are_you()
#>>> I'm a men! My name is Larry
men.method_unique_to_men()
#>>> I made The Matrix


men.cast_to(Women, 'Lana')
men.who_are_you()
#>>> I'm a women! My name is Lana
men.method_unique_to_women()
#>>> I made Cloud Atlas

Note the self.__class__ and not self.__class__.__name__. I.e. this technique not only replaces class name, but actually converts an instance of a class (at least both of them have same id()). Also, 1) I don't know whether it is "safe to replace a self object by another object of the same type in [an object own] method"; 2) it works with different types of objects, not only with ones that are of the same type; 3) it works not exactly like amirouche wanted: you can't init class like Class(args), only Class() (I'm not a pro and can't answer why it's like this).

Pugsley
  • 1,146
  • 14
  • 14
8

Yes, all that will happen is that you won't be able to reference the current instance of your class A (unless you set another variable to self before you change it.) I wouldn't recommend it though, it makes for less readable code.

Note that you're only changing a variable, just like any other. Doing self = 123 is the same as doing abc = 123. self is only a reference to the current instance within the method. You can't change your instance by setting self.

What func(self) should do is to change the variables of your instance:

def func(obj):
    obj.var_a = 123
    obj.var_b = 'abc'

Then do this:

class A:
    def method1(self):
        func(self) # No need to assign self here
Blixt
  • 49,547
  • 13
  • 120
  • 153
3

In many cases, a good way to achieve what you want is to call __init__ again. For example:

class MyList(list):
    def trim(self,n):
        self.__init__(self[:-n])

x = MyList([1,2,3,4])
x.trim(2)
assert type(x) == MyList
assert x == [1,2]

Note that this comes with a few assumptions such as the all that you want to change about the object being set in __init__. Also beware that this could cause problems with inheriting classes that redefine __init__ in an incompatible manner.

Wrzlprmft
  • 4,234
  • 1
  • 28
  • 54
1

Yes, there is nothing wrong with this. Haters gonna hate. (Looking at you Pycharm with your in most cases imaginable, there's no point in such reassignment and it indicates an error).

A situation where you could do this is:

some_method(self, ...):

    ...

    if(some_condition):
        self = self.some_other_method()

    ...

    return ...

Sure, you could start the method body by reassigning self to some other variable, but if you wouldn't normally do that with other parametres, why do it with self?

oulenz
  • 1,199
  • 1
  • 15
  • 24
0

One can use the self assignment in a method, to change the class of instance to a derived class.

Of course one could assign it to a new object, but then the use of the new object ripples through the rest of code in the method. Reassiging it to self, leaves the rest of the method untouched.

class aclass:

    def methodA(self):
        ...
        if condition:
            self = replace_by_derived(self)
            # self is now referencing to an instance of a derived class
            # with probably the same values for its data attributes

        # all code here remains untouched
        ...
        self.methodB() # calls the methodB of derivedclass is condition is True
        ...

    def methodB(self):
        # methodB of class aclass
        ...

class derivedclass(aclass):
    def methodB(self):
        #methodB of class derivedclass
        ...

But apart from such a special use case, I don't see any advantages to replace self.

Ruben Decrop
  • 1,979
  • 1
  • 17
  • 9
0

You can make the instance a singleton element of the class and mark the methods with @classmethod.

from enum import IntEnum
from collections import namedtuple

class kind(IntEnum):
    circle = 1
    square = 2

def attr(y): return [getattr(y, x) for x in 'k l b u r'.split()]

class Shape(namedtuple('Shape', 'k,l,b,u,r')):
    self = None

    @classmethod
    def __repr__(cls):
        return "<Shape({},{},{},{},{}) object at {}>".format(
            *(attr(cls.self)+[id(cls.self)]))

    @classmethod
    def transform(cls, func):
        cls.self = cls.self._replace(**func(cls.self))

Shape.self = Shape(k=1, l=2, b=3, u=4, r=5)
s = Shape.self

def nextkind(self):
    return {'k': self.k+1}

print(repr(s))  # <Shape(1,2,3,4,5) object at 139766656561792>
s.transform(nextkind)
print(repr(s))  # <Shape(2,2,3,4,5) object at 139766656561888>

Roland Puntaier
  • 3,250
  • 30
  • 35