max_max_mir
max_max_mir

Reputation: 1736

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.

Upvotes: 2

Views: 271

Answers (1)

python_user
python_user

Reputation: 7083

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

Upvotes: 3

Related Questions