Search code examples
pythonnetworkx

NetworkX - how is Graph.nodes() able to take in parameters?


When I look at the code for Graph.nodes() here: https://github.com/networkx/networkx/blob/main/networkx/classes/graph.py#L658 it doesn't take in any parameters. So how is it that I am able to pass something like data=True (e.g., Graph.nodes(data=True))? Ideally, I would get a "got an unexpected keyword argument 'data'" error.

It is also decorated as a property, but I am able to call it as a function. This is probably a basic python question, but this is the first time I am encountering this.


Solution

  • Keeping networkx aside. Take this code snippet.

    class CallableIterable:
        def __init__(self, val):
            self.val = val[:]
    
        def __call__(self, arg1=True):
            print('acting as a callable')
            return f'You called this with {arg1}'
    
        def __contains__(self, arg):
            print('acting as an iterable')
            return arg in self.val
    
    class Graph:
        @property
        def nodes(self):
            return CallableIterable([1, 2, 3, 4, 5])
    

    You can see that there are two classes CallableIterable and Graph. Graph has a property nodes that returns an instance of CallableIterable when called.

    Create an instance of Graph g.

    g = Graph()

    Now you can use the property g.nodes in two ways.

    As an iterable

    print(1 in g.nodes)

    This gives

    acting as an iterable
    True
    

    This is because in triggers the __contains__ method of the CallableIterable class (in this example).

    As a callable

    print(g.nodes('some random value'))

    This gives

    acting as a callable
    You called this with some random value
    

    This is because it triggers the __call__ method of the CallableIterable class.

    The arguments passed in (), in this case, 'some random value' of g.nodes('some random value') or in your case, data=True of Graph().nodes(data=True) are passed to the __call__ of the return value of the property (which is g.nodes), NOT TO the property decorated function Graph.nodes.

    Internally this is g.nodes.__call__('some random value').

    With this understanding apply the same principle to networkx, where NodeView is the type of object returned instead of the CallableIterable as seen in this example, the working is the same.

    1. nodes = NodeView(self) is an instance of the NodeView class.
    2. This class has a __call__ and a __contains__ method.
    3. Which lets you use it as a callable / function (Graph().nodes(data=True))) and as an iterable ('something' in Graph().nodes).

    The NodeView class has other functions than just __call__ and __contains__, each of which will get called appropriately.

    This is essentially what the docstrings mean with the following

        Can be used as `G.nodes` for data lookup and for set-like operations.
        Can also be used as `G.nodes(data='color', default=None)` to return a
        NodeDataView which reports specific node data but no set operations