Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to return a previously defined object when initiating a new one?

Tags:

python

I am working on a grid-generator that takes x- and y-coordinates as input. When initiating, this node is stored using a @classmethod (storage is a set, see MWE) during which a unique index is provided. As part of __init__, there is a check if there is already a node with the same (x,y)-coordinates; if so, no new node should be created.

So far, so good. The problem arises when I want to return the previously defined node when the (x,y)-coordinates are already assigned to one. So when I initiate a node with previously defined (x,y)-coordinates, I want that previously defined node-object to be returned. As an example:

n1 = Node(x=0, y=0)
n2 = Node(x=0, y=0)

In this example, both n1 and n2 should contain exactly the same object, and not a copy with the same details. Thus, n1 == n2 should return True, as the objects are identical. This is necessary for further computations (left out for clarity reasons).

Below a MWE of my Node-class:

class Node:

    __nodes = set()  # Here, Node-objects are stored
    __node_idx = None

    def __init__(self, x, y):
        # This is the check if the (x,y)-coordinates are already assigned to a node.
        if (x, y) not in [n.coordinates for n in self.__nodes]:
            self.x = x
            self.y = y
            self.set_idx(self)
            self.store_node(self)
        # Should here be something like an 'else'-statement?

    @classmethod
    def set_idx(cls, node):
        """Function to set unique index based on the number of nodes."""
        # There is a procedure to determine this index, but that does not matter for the question.
        node.__node_idx = idx

    @classmethod
    def store_node(cls, node):
        cls.__nodes.add(node)

    @property
    def index(self):
        return self.__node_idx

    @property
    def coordinates(self):
        return self.x, self.y

Stating that self becomes this previously defined node is not working; I have considered it. Thus as else-statement in the __init__:

    ...
    else:
        self = [n for n in self.__nodes if n.coordinates == (x, y)][0]

I have looked at the __hash__-method, but I am not familiar with it and I do not know if this is where the solution to my problem lies.

I am using Python 3.7. Thank you very much for any help; really appreciated!

like image 609
user15519982 Avatar asked Mar 31 '21 07:03

user15519982


People also ask

How to construct a new object from an existing object?

Frequently, in practice, there is a need to construct a new object so that it is initially the same as some existing object. To do this, either we can use Object.clone () method or define a constructor that takes an object of its class as a parameter. In java, a method can return any type of data, including objects.

What happens when an object reference is passed to a method?

Note: When an object reference is passed to a method, the reference itself is passed by use of call-by-value. However, since the value being passed refers to an object, the copy of that value will still refer to the same object that its corresponding argument does.

How do you return an object in Java?

Returning Objects. In java, a method can return any type of data, including objects. For example, in the following program, the incrByTen( ) method returns an object in which the value of a (an integer variable) is ten greater than it is in the invoking object.

Can a method return an object in C?

C# | Method returning an object. In C#, a method can return any type of data including objects. In other words, methods are allowed to return objects without any compile time error. Example 1: // C# program to illustrate the concept.


Video Answer


2 Answers

What about using the factory pattern?

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

class NodeFactory:
    def __init__(self):
        self.nodes = set()
    
    def create_node(self, x, y):
        for node in self.nodes:
            if node.x == x and node.y == y:
                return node
        node = Node(x, y)
        self.nodes.add(node)
        return node

factory = NodeFactory()

n1 = factory.create_node(1, 5)
n2 = factory.create_node(1, 5)
n3 = factory.create_node(2, 1)

print(n1)
print(n2)
print(n3)

Output:

<__main__.Node object at 0x7fa595aa6640>
<__main__.Node object at 0x7fa595aa6640>
<__main__.Node object at 0x7fa595aa6100>

As you can see n1 and n2 are the same object here, so:

>>> n1 == n2
True
like image 191
Selnay Avatar answered Nov 14 '22 21:11

Selnay


You can override the __new__ method of the class to control the creation of the nodes.

We store the existing nodes in a class attribute, a dict with the coordinates as keys and the Node objects as values.

__new__ looks if there already is a node with these coordinates in this dict. If yes, it just returns the already existing node. If not, it creates and returns a new one.

Depending on what you do in it, it may be better not to run __init__ again (or maybe not all parts of it) for already existing nodes. So, we just set an _already_initialized flag on the instance, that will be False at creation time, and that will be changed to True by __init__. This way, you can completely control what to do the first time a node is initialized, and the following times.

The code, with a few prints included to show what's going on:

class Node:

    _nodes = {} 

    def __new__(cls, x, y):
        if (x, y) in cls._nodes:
            print('Reusing', cls._nodes[(x, y)])
            return cls._nodes[(x, y)]
       
        new_node = super().__new__(cls)
        new_node._already_initialized = False
        print(f'New node created for x={x}, y={y}')

        cls._nodes[(x, y)] = new_node
        
        return new_node
    
    def __init__(self, x, y):
        if not self._already_initialized:
            self.x = x
            self.y = y
            self.set_idx(self)
            self._already_initialized = True
            print(self, 'initialized')
        else:
            print(self, 'was already initialized, leaving as it is')

    @classmethod
    def set_idx(cls, node):
        """Function to set unique index based on the number of nodes."""
        # There is a procedure to determine this index, but that does not matter for the question.
        pass

    @property
    def index(self):
        return self.__node_idx

    @property
    def coordinates(self):
        return self.x, self.y
    
    def __repr__(self):
        return f'Node: x={self.x}, y={self.y}'

Demo:

print('Creating n1')    
n1 = Node(x=0, y=0)

print('\nCreating n2')
n2 = Node(x=0, y=0)

print('\nCreating n3')
n3 = Node(1, 1)

print(f'\nn1 == n2: {n1 == n2}')
print(f'n1 is n2: {n1 is n2}')

print(f'\nNode._nodes: {Node._nodes}')

Output:

Creating n1
New node created for x=0, y=0
Node: x=0, y=0 initialized

Creating n2
Reusing Node: x=0, y=0
Node: x=0, y=0 was already initialized, leaving as it is

Creating n3
New node created for x=1, y=1
Node: x=1, y=1 initialized

n1 == n2: True
n1 is n2: True

Node._nodes: {(0, 0): Node: x=0, y=0, (1, 1): Node: x=1, y=1}

like image 35
Thierry Lathuille Avatar answered Nov 14 '22 20:11

Thierry Lathuille