2

I was trying to assign a Python object to another in-place using a member function such as replace_object() below. However, as you can see, object_A remains unchanged and the only way to copy object_B is to create an entirely new object object_C, which defeats the purpose of in-place assignment.

What is going on here and how can I make the assignment in-place?

class some_class():

    def __init__(self, attribute):

        self.attribute = attribute

    def replace_object(self, new_object):

        self = new_object

        # Does this line even have any effect?
        self.attribute = new_object.attribute 

        self.new_attribute = 'triangle'

        return self

object_A = some_class('yellow')
print(object_A.attribute)       # yellow
object_B = some_class('green')
object_C = object_A.replace_object(object_B)
print(object_A.attribute)       # yellow
print(object_C.attribute)       # green

#print(object_A.new_attribute)  # AttributeError!
print(object_B.new_attribute)   # triangle
print(object_C.new_attribute)   # triangle

I also tried to play around with deep copies using copy.copy(), but to no avail.

An interesting twist to this is that if I replace

object_C = object_A.replace_object(object_B)

with

object_A = object_A.replace_object(object_B)

then I get what I want. But why can't the same result be achieved by the statement self = new_object statement within replace_object()?

PS: I have a very good reason to do this in-place assignment, so although it may not be best practice in general, just go along with me here.

Tfovid
  • 761
  • 2
  • 8
  • 24
  • That's not how assignment works in Python, and trying to change those kinds of language mechanics is unlikely to go well even for experts. [Here's a quick guide to what assignment actually does in Python.](https://nedbatchelder.com/text/names.html) – user2357112 Nov 21 '17 at 19:40
  • Could you explain *why* you think you need to do this? – Scott Hunter Nov 21 '17 at 19:41
  • @ScottHunter ... because the apparent alternative, namely `object_A = object_A.replace_object(object_B)`, seems redundant. – Tfovid Nov 21 '17 at 19:53
  • Why can't you just copy all the attributes to mirrow the ones in the object passed in, or however you are defining "equality". – juanpa.arrivillaga Nov 21 '17 at 20:13
  • @juanpa.arrivillaga The point is precisely that I _cannot_ copy all the attributes within `replace_object()`. – Tfovid Nov 21 '17 at 20:19
  • @Tfovid of course you can. Just dispense with trying to do `self = other_object`, then go through the attributes you care about, e.g. `attr1, attr2` etc, and do `self.attr1 = other_object.attr1; self.attr2 = other_object.attr2; ...` – juanpa.arrivillaga Nov 21 '17 at 20:39
  • @juanpa.arrivillaga Please look at the code I posted: The statement `self.attribute = new_object.attribute` does exactly what you're suggesting, but it does not work. So no, you cannot just copy the attributes. – Tfovid Nov 22 '17 at 07:53
  • 1
    As I already explained, that is because you do `self = new_object`. Don't do that, because now when you do: `self.attribute = new_object.attribute` the statement is equivalent to `new_object.attribute = new_object.attribute`, i.e. completely pointless. – juanpa.arrivillaga Nov 22 '17 at 08:00
  • @juanpa.arrivillaga I'm sorry, yes, I get it now. Although I still kind of find it inelegant to have to iterate through all the attributes :( – Tfovid Nov 22 '17 at 08:19

1 Answers1

2

You can't 'assign an object to another'. You can assign new and existing objects to new and existing names.

self = new_object only says 'from now on the name self will refer to new_object', and does nothing to the old object. (Note self is just a variable name like any other and only by convention refers to an object within a class definition.)

The subsequent command self.attribute = new_object.attribute has no effect because self has already become a duplicate label for the new_object.

You could copy all the properties of a new object to the old object. You would end up with two distinct objects with different names and identical properties. A test of equality (a == b) would return false unless you overrode the equality operator for these objects.

To copy all the properties inline you could do something like this:

def replace_object(self, new_object):
    self.__dict__ = new_object.__dict__.copy() # just a shallow copy of the attributes

There are very likely better ways to do whatever it is you want to do.

Stuart
  • 9,597
  • 1
  • 21
  • 30
  • So what's the alternative? Isn't `object_A = object_A.replace_object(object_B)` both redundant and inelegant? – Tfovid Nov 21 '17 at 19:54
  • I've edited the answer with a suggestion of how you can copy the properties. But find it difficult to see a scenario where this is a useful approach. – Stuart Nov 21 '17 at 20:42
  • That could do it, thanks. But why does it have to be a shallow copy? Why not just `self.__dict__ = new_object.__dict__`? – Tfovid Nov 22 '17 at 09:40
  • If you do that then the old object's attribute dictionary will always point to the new object's attribute dictionary (both names point to the same object). E.g. if you type `new_object.x = 30` then you will find that `old_object` also now has an attribute `x` equal to 30. Maybe that's what you want though? – Stuart Nov 22 '17 at 13:14
  • Actually, I just opted for looping through all the attributes and using `__setattr__()` instead. Is this kocher or am I still doing something wrong? – Tfovid Nov 22 '17 at 13:38
  • It's more or less the same. `.copy()` will copy all of the new object's attributes indiscriminately. With your approach you can control which attributes are copied. This will take more lines of code but might be preferable as you can avoid copying unwanted or irrelevant attributes. – Stuart Nov 22 '17 at 13:44