init 1
init 1

Reputation: 29

How to make Python class constructor return an already existing object (pointing to the same object)?

I'm writing a class called Position, something like Python's Small Integer Constant Pool:

class Position:
    """Takes 2 args: x, and y. If coordinate are equal, make them pointing to the same object"""

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __new__(self, *args, **kwargs):
        import gc
        for obj in gc.get_objects():
            if isinstance(obj, eval(self.__qualname__)):  # Won't work cause arg 2 is a string, not a type name
                if obj.x == self.x and obj.y == self.y:
                    return obj
        return super(Position, self).__new__(self, *args, **kwargs)  # Have no idea what to write

p1 = Position(1, 2)
p2 = Position(2, 3)
p3 = Position(1, 2)

print(p1 is p2, p2 is p3, p1 is p3)  # Should print False, False, True

Rewriting hash and eq won't work when using is to compare.

I want my class to be something like:

a = 1
b = 1
print(a is b)  # True

Upvotes: 0

Views: 957

Answers (1)

jsbueno
jsbueno

Reputation: 110301

You can't rely on the garbage collector list of all existing objects for this, and much less on a linear search of all objects.

Simply keep your objects in a registry where you can find then back.

You can use WeakValues dictionary so that if there is no reference left to anyone of your objects it is discarded. (Just use a plain dictionary if you want each one to remain created, even if it is no longer in use):

from weakref import WeakValueDictionary


class Position:
    """Takes 2 args: x, and y. If coordinate are equal, make them pointing to the same object"""
    _registry = WeakValueDictionary()

    def __new__(cls, x, y):
        if (x,y) in cls._registry:
            return cls._registry[x,y]
        instance = super().__new__(cls)  # don't pass extra *args and **kwargs to obj.__new__
        cls._registry[x,y] = instance
        return instance

    def __init__(self, x, y):
        if hasattr(self, "x"):  # avoid running init twice if the attribute is already set
            return
        self.x = x
        self.y = y


p1 = Position(1, 2)
p2 = Position(2, 3)
p3 = Position(1, 2)

in this scenario p1 == p3 evaluates to True

Upvotes: 2

Related Questions