Python Forum
Organizing several similar classes with overlapping variables
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Organizing several similar classes with overlapping variables
#1
I am struggling for weeks now how to structarize a bunch of classes that have several similarities.
Perhaps the best analogy would be a class representing a graph, e.g. a bunch of nodes that may be connected with each other.
But I have several classes, where the nodes will have different meanings/behaviours, lets call the special nodes "vertices" and "points".
Every node/vertex/point will be a dictionary of lists (adjacent nodes).

The end user will access the nodes via Desciptors and cached properties (kinda what networkx does), but this is probably not important now.

So I have the base class that performs various operations on the nodes (e.g. can remove them)

class BaseGraph:

    def __init__(self, nodes):
        self._nodes = nodes

    def nodes(self):
        return NodeView(self._nodes)
   
    def remove_node(self, node):
        del self._nodes[node]
     ...
We would create an instance by
my_graph = BaseGraph({"a":["b","c"], "b":["a"], "c":["a"]})
(there are a bunch of methods and functions that modify and compute with the nodes).

But then I have several classes that will have BaseGraph as its parent, but the nodes will have somewhat different meaning, for instance, some can be modified one way, some the other way. Lets call different nodes "vertices" and "points". They classes also have different mathematical meanings, so it would not make sense to keep them as separate classes (kinda what networkx does, it has classes "Graph", "MultiGraph", "DirectedGraph",...)

So we have:

class VertexGraph(BaseGraph)
    def __init__(self, vertices):
        self._nodes = vertices

    def vertices(self):
        return NodeView(self._nodes)
class PointGraph(BaseGraph)
    def __init__(self, points):
        self._nodes = points

    def points(self):
        return NodeView(self._nodes)

    def do_something_with_point(self, point):
        ....
class VertexPointGraph(BaseGraph)
    def __init__(self, vertices, points):
        self._nodes = vertices | points
        self._points = points
        self._vertices = vertices

    def points(self):
        return NodeView(self._points)

    def vertices(self):
        return NodeView(self._vertices)

    def do_something_with_point(self, point):
        ....
I could just write several separate classes, but a lot of methods from BaseGraph performs the same operation on points or vertices, e.g. deletion (and the base class is several hundred lines long)

And a lot of functions func(graph) also do not care if the nodes are verties or graphs, but some do. So therefore it would make sense to store all points and vertices in _nodes, but then I have to keep track that _nodes, _points, and _vertices are always updated (if I change a point, I should also change the node, which is not optimal).

One idea, that would work, would be to keep track of the node type by a dictionary

    def __init__(self, vertices, points):
        self._nodes = vertices | points
        self.kind = {v:"Vertex" for v in vertices} | {p:"Point" for p in points}
But then there would be difficult e.g. to iterate over these, which I do often (e.g. iterate over points or iterate over vertices). Also, I would like to quickly know if a given node is a vertex or point.
Also, I would like the class to be extendible in the future, e.g. if I add a third kind of node.

So perhaps it is good the way it is, I just have to be careful to keep all nodes, vertices and points in sync when modifying and deleting them.

All ideas and thoughts are very welcome Smile
Reply
#2
(May-06-2023, 09:25 PM)6hearts Wrote: One idea, that would work, would be to keep track of the node type by a dictionary

 def __init__(self, vertices, points):
    self._nodes = vertices + points
    self.kind = {v:"Vertex" for v in vertices} | {p:"Point" for p in points}
You don't need to do that, normally the Python type of the objects suffice. For example the vertices are instances of a class Vertex and the points are instances of a class Point. There is no need to maintain a dictionary to store the types.
Reply
#3
At the moment they are all type dict, since I really do not have much functionality on them, except for storing data. And I mostly just need to lookup data, so dict was the natural choice.
So you suggest I crate (mostly) empy classes class Point(dict) and class Vertex(dict)?
Reply
#4
When graphing points or vertices, are they treated differently? Or are you making two classes because you think you need one graph class for each class of item you are trying to graph?
Reply
#5
(May-06-2023, 10:18 PM)6hearts Wrote: I really do not have much functionality on them, except for storing data. And I mostly just need to lookup data,
Dataclases are probably the appropriate tools instead of dictionaries, and it solves the problem of identifying the type of the objects in a collection with mixed objects. Namedtuples could perhaps be used too.
Reply
#6
(May-07-2023, 03:45 AM)deanhystad Wrote: When graphing points or vertices, are they treated differently? Or are you making two classes because you think you need one graph class for each class of item you are trying to graph?

Yes, maybe I am overthinking everythig. I took great inspiration in the networx package, where they have different classes for each graph type (graph, multigraph,..) but there the internal structure is always different, so it makes sense. Maybe not in my case, since only the node types are different. I really liked netwokx structure, where they store all data as dictionaries and then have different views on this data so the user can access it more naturally.


(May-07-2023, 06:14 AM)Gribouillis Wrote: Dataclases are probably the appropriate tools instead of dictionaries, and it solves the problem of identifying the type of the objects in a collection with mixed objects. Namedtuples could perhaps be used too.
Hm... but for the nodes I would really need the functionality of a list, since a node is really just a list of neighbouring nodes. And I will often change particular items at particular indices, so __setitem__ and __getitem__ will be often called.
Surely I could add a data parameter to the dataclass, but then it would be less clear for the end user, they would access the data through node.data[i] instead of node[i].

What if I just wrap a list and have a name parameter

class Vertex(list):
    def __init__(self, data):
        super(Vertex, self).__init__(data)
        self.name = "vertex"
And then use filters so the user can iterate over certain types.
But I kinda hate the idea to have an almost empty class just for determining its "type".
Reply
#7
(May-06-2023, 09:48 PM)6hearts Wrote: Every node/vertex/point will be a dictionary of lists (adjacent nodes).
(May-07-2023, 08:01 AM)6hearts Wrote: a node is really just a list of neighbouring nodes.
These are contradictory descriptions a node. Which one is correct? Also what are the differences between a node, a point an a vertex? This needs clarification first.
Reply
#8
Oh, sorry, so I would keep the nodes in a dictionary, but each node is a list.
So the class graph would have a variable nodes what would be a dictionary, each node is a represented as a list, seqence of adjacent nodes.
For example, we would have a member

class graph:
    def add_node(node: Hashable, adjacent_nodes: list):
        self._nodes[node] = adjacent_nodes 
The difference between them would eg. be that on nodes, we can permute the adjacent nodes (they then could be sets, not lists), on vertices we could only do cyclic permutations, on points we could, for example, split them in two. split_point([1,2,3,4]) would return ([1,2],[3,4]), but such operations would nor be logical for vertices. Also, when plotting, they would look different.

I think subclassing would do the trick, have a class Node, a class Vertex(Node), class Point(Node),...
and then then just store these as dictionary items, so self._nodes = {"a":node1, "b":vertex1, "c":vertex2,...}

But the downsides are:
  1. I would need filters for accessing only vertices or only points on a graph that has mixed nodes
  2. I could not easily count the number of vertices, since I would have to iterate over the dict
  3. I would not have a classes such as VertexGraph and PointGraph, since they would all be mixed inside the dict.


What about if I made a custom dictionary class that would allow the keys to be partitioned, so I could kida initialize it by
nodes = my_dict(categories=("vertices", "points",..))
and then i could iterate over all nodes, or nodes.vertices, nodes.points,...
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Unexpected Output - Python Dataframes: Filtering based on Overlapping Dates Xensor 5 722 Nov-15-2023, 06:54 PM
Last Post: deanhystad
  Find overlapping date in database Hilal 2 1,714 Dec-18-2021, 08:15 PM
Last Post: Hilal
  Sum similar items tester_V 3 1,986 Jun-29-2021, 06:58 AM
Last Post: tester_V
  Organizing list of objects by attribute scarney1988 3 2,236 Mar-11-2020, 03:55 PM
Last Post: scarney1988
  Organizing Data in Dictionary Ranjirock 3 2,646 Aug-27-2019, 02:48 PM
Last Post: Ranjirock
  I can't use file __init__ to store shared variables and classes in the package AlekseyPython 2 3,353 Feb-04-2019, 06:26 AM
Last Post: AlekseyPython
  Python: if 'X' in 'Y' but with two similar strings as 'X' DreamingInsanity 6 3,880 Feb-01-2019, 01:28 PM
Last Post: buran
  How can classes access each other Functions and Variables at the same time PythonOK 4 3,074 Dec-09-2018, 03:46 AM
Last Post: ichabod801
  Similar to Poker bluekade5050 1 32,877 Nov-14-2018, 04:46 PM
Last Post: j.crater
  organizing by distance gonzo620 7 3,943 Oct-16-2018, 01:41 AM
Last Post: stullis

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020