Repository: networkx/networkx

Issue:
`relabel_nodes` does not work for callables with `__getitem__`
<!-- If you have a general question about NetworkX, please use the discussions tab to create a new discussion -->

<!--- Provide a general summary of the issue in the Title above -->

`relabel_nodes` accepts mappings in the form of functions. It differentiates function from mapping by testing`not hasattr(mapping, "__getitem__")`. This test is ambiguous for callables that do possess '__getitem__`, e.g. `str`.

Would it be better to test `isinstance(mapping, typing.Mapping)` and document accordingly?

### Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
```python
import networkx as nx

G = nx.DiGraph()
G.add_nodes_from([1, 2])

# works
# nx.relabel_nodes(G, lambda x: str(x))

# error
nx.relabel_nodes(G, str)
```

```
Traceback (most recent call last):
  File "...\networkx_2_8_4_relabel_callable.py", line 10, in <module>
    nx.relabel_nodes(G, str)
  File "...\lib\site-packages\networkx\relabel.py", line 121, in relabel_nodes
    return _relabel_copy(G, m)
  File "...\lib\site-packages\networkx\relabel.py", line 193, in _relabel_copy
    H.add_nodes_from(mapping.get(n, n) for n in G)
  File "...\lib\site-packages\networkx\classes\digraph.py", line 519, in add_nodes_from
    for n in nodes_for_adding:
  File "...\lib\site-packages\networkx\relabel.py", line 193, in <genexpr>
    H.add_nodes_from(mapping.get(n, n) for n in G)
AttributeError: type object 'str' has no attribute 'get'
```

### Expected Behavior
<!--- Tell us what should happen -->
`relabel_nodes` works for callables.

### Steps to Reproduce
<!--- Provide a minimal example that reproduces the bug -->
See current behavior.

### Environment
<!--- Please provide details about your local environment -->
Python version: 3.9
NetworkX version: 2.8.5


### Additional context
<!--- Add any other context about the problem here, screenshots, etc. -->


Discussion:

Patch:
diff --git a/networkx/relabel.py b/networkx/relabel.py
index 35e71536a..65297d573 100644
--- a/networkx/relabel.py
+++ b/networkx/relabel.py
@@ -114,9 +114,13 @@ def relabel_nodes(G, mapping, copy=True):
     --------
     convert_node_labels_to_integers
     """
-    # you can pass a function f(old_label)->new_label
+    # you can pass a function f(old_label) -> new_label
+    # or a class e.g. str(old_label) -> new_label
     # but we'll just make a dictionary here regardless
-    if not hasattr(mapping, "__getitem__"):
+    # To allow classes, we check if __getitem__ is a bound method using __self__
+    if not (
+        hasattr(mapping, "__getitem__") and hasattr(mapping.__getitem__, "__self__")
+    ):
         m = {n: mapping(n) for n in G}
     else:
         m = mapping


Test patch:
diff --git a/networkx/tests/test_relabel.py b/networkx/tests/test_relabel.py
index 46b74482b..9a9db76eb 100644
--- a/networkx/tests/test_relabel.py
+++ b/networkx/tests/test_relabel.py
@@ -106,6 +106,12 @@ class TestRelabel:
         H = nx.relabel_nodes(G, mapping)
         assert nodes_equal(H.nodes(), [65, 66, 67, 68])
 
+    def test_relabel_nodes_classes(self):
+        G = nx.empty_graph()
+        G.add_edges_from([(0, 1), (0, 2), (1, 2), (2, 3)])
+        H = nx.relabel_nodes(G, str)
+        assert nodes_equal(H.nodes, ["0", "1", "2", "3"])
+
     def test_relabel_nodes_graph(self):
         G = nx.Graph([("A", "B"), ("A", "C"), ("B", "C"), ("C", "D")])
         mapping = {"A": "aardvark", "B": "bear", "C": "cat", "D": "dog"}