Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python assignment to self in constructor does not make object the same

I am making a constructor in Python. When called with an existing object as its input, it should set the "new" object to that same object. Here is a 10 line demonstration:

class A:
    def __init__(self, value):
        if isinstance(value, A):
            self = value
        else:
            self.attribute = value
a = A(1)
b = A(a)#a and b should be references to the same object
print("b is a", b is a)#this should be true: the identities should be the same
print("b == a", b == a)#this should be true: the values should be the same

I want the object A(a) constructed from the existing object a to be a. Why is it not? To be clear, I want A(a) to reference the same object as a, NOT a copy.

like image 221
hacatu Avatar asked Dec 09 '22 04:12

hacatu


2 Answers

self, like any other argument, is among the local variables of a function or method. Assignment to the bare name of a local variable never affects anything outside of that function or method, it just locally rebinds that name.

As a comment rightly suggests, it's unclear why you wouldn't just do

b = a

Assuming you have a sound reason, what you need to override is not __init__, but rather __new__ (then take some precaution in __init__ to avoid double initialization). It's not an obvious course so I'll wait for you to explain what exactly you're trying to accomplish.

Added: having clarified the need I agree with the OP that a factory function (ideally, I suggest, as a class method) is better -- and clearer than __new__, which would work (it is a class method after all) but in a less-sharply-clear way.

So, I would code as follows:

class A(object):

    @classmethod
    def make(cls, value):
        if isinstance(value, cls): return value
        return cls(value)

    def __init__(self, value):
        self.attribute = value

Now,

a = A.make(1)
b = A.make(a)

accomplishes the OP's desires, polymorphically over the type of argument passed to A.make.

like image 182
Alex Martelli Avatar answered May 11 '23 17:05

Alex Martelli


The only way to make it work exactly as you have it is to implement __new__, the constructor, rather than __init__, the initialiser (the behaviour can get rather complex if both are implemented). It would also be wise to implement __eq__ for equality comparison, although this will fall back to identity comparison. For example:

>>> class A(object):
    def __new__(cls, value):
        if isinstance(value, cls):
            return value
        inst = super(A, cls).__new__(cls)
        inst.attribute = value
        return inst
    def __eq__(self, other):
        return self.attribute == other.attribute


>>> a = A(1)
>>> b = A(a)
>>> a is b
True
>>> a == b
True
>>> a == A(1)
True  # also equal to other instance with same attribute value

You should have a look at the data model documentation, which explains the various "magic methods" available and what they do. See e.g. __new__.

like image 43
jonrsharpe Avatar answered May 11 '23 16:05

jonrsharpe