4

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.

hacatu
  • 638
  • 6
  • 15
  • Why don't you simply do `b = a` instead of `b = A(a)`? – Jakube Feb 15 '15 at 22:30
  • Sry, Ignore this. Completely wrong, didn't test. – ljetibo Feb 15 '15 at 22:32
  • I took this from a larger program, but I have a method that calls the constructor on one of its inputs to make sure it is an instance of the class. If it already is, I don't want it changed. – hacatu Feb 15 '15 at 22:32

4 Answers4

9

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.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • I'm basically calling the constructor on the inputs to some function to turn them into objects of the class if they aren't already. I think that I will just make some kind of LiftToA function or something, rather than override __new__. – hacatu Feb 15 '15 at 22:37
  • @hacatu, yes, a factory function is the right approach -- a class method the most elegant way to make one, and `__new__` is indeed a class method, but writing a plain, explicit class method is usually clearer. Let me edit the A to show that. – Alex Martelli Feb 15 '15 at 22:39
  • 1
    "`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": I can understand that's the case for `self`, but it doesn't seem to me that other arguments are all "local only"? For example, if the function in the original question reassigns the `value` argument to something else, surely the original `value` that was passed in to the function at invocation site would have its reference changed as well? – xji Jan 28 '17 at 10:54
3

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__.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 1
    If no `__init__` exists this works, otherwise that `__init__` is always called on the result of `__new__` if said result is an instance of the given class, which may often cause problems. This is one of the reasons an explicitly-named, explicitly-called class method is usually a preferable design choice. – Alex Martelli Feb 15 '15 at 22:45
  • @AlexMartelli good point, I've made it clearer that one or the other, not both, should be implemented. – jonrsharpe Feb 15 '15 at 22:46
2

__init__ is an initializer, not a constructor. You would have to mess around with __new__ to do what you want, and it's probably not a good idea to go there.

Try

a = b = A(1)

instead.

Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99
0

If you call a constructor, it's going to create a new object. The simplest thing is to do what hacatu suggested and simply assign b to a's value. If not, perhaps you could have an if statement checking if the value passed in is equal to the object you want referenced and if it is, simply return that item before ever calling the constructor. I haven't tested so I'm not sure if it'd work.

oxrock
  • 643
  • 5
  • 12