def edge_subgraph(G, ebunch):
    """Return the subgraph induced on edges in `ebunch`.

    The induced subgraph of the graph contains the edges appearing in `ebunch`
    and only the nodes that appear in some edge in `ebunch`.

    Parameters
    ----------
    ebunch : list, iterable
        A container of edges as 3-tuples (u, v, key), which will be iterated
        through exactly once.

    Returns
    -------
    H : MultiDiGraph
        A subgraph of the graph with the same graph, node, and edge attributes.

    Notes
    -----
    The graph, edge or node attributes just point to the original graph.
    So changes to the node or edge structure will not be reflected in
    the original graph while changes to the attributes will.

    To create a subgraph with its own copy of the edge/node attributes use:
    nx.Graph(G.subgraph(nbunch))

    If edge attributes are containers, a deep copy can be obtained using:
    G.subgraph(nbunch).copy()

    For an inplace reduction of a graph to a subgraph you can remove nodes:
    G.remove_nodes_from([ n in G if n not in set(nbunch)])

    Examples
    --------
    >>> G = nx.Graph()   # or DiGraph, MultiGraph, MultiDiGraph, etc
    >>> G.add_path([0,1,2,3])
    >>> H = G.subgraph([0,1,2])
    >>> H.edges()
    [(0, 1), (1, 2)]

    """
    H = G.__class__()
    G_succ = G.succ
    for u, v, key in ebunch:
        try:
            attrs = G_succ[u][v][key]
        except KeyError:
            raise KeyError('Invalid edge: ({0}, {1}, {2})'.format(u, v, key))
        H.add_edge(u, v, key=key, **attrs)
    for u in H:
        H.node[u] = G.node[u].copy()
    H.graph = G.graph.copy()
    return H
