this is almost same as the iterative one for directed graph but we cannot use the concept of recursion stack here because in directed graphs there is defined path to traverse but in undirected its possible that an edge or a path can be traversed infite number of times instead check for parents which means vertex from which you reached the current vertex if a vertex is visited and you are not coming to this vertex from the current source vertex source vertex from which dfs has started then it means that in the same dfs chain there is another path to reach this vertex hence a cycle
from collections import defaultdict

class Graph:


    def __init__(self, vertices):
        self.graph = defaultdict(list)
        self.vertices = vertices


    def add_edge(self, u, v):
        self.graph[u].append(v)
        self.graph[v].append(u)


    def dfs(self):
        visited = [False] * self.vertices
        parent = [-1] * self.vertices

        stack = []

        for v in range(self.vertices):
            if visited[v] == False:
                visited[v] = True
            
                stack.append(v)

                while stack:
                    s = stack.pop()

                    for i in self.graph[s]:
                        if visited[i] == False:
                            parent[i] = s
                            stack.append(i)
                            visited[i] = True
                        elif parent[s] != i:
                            return "Contains Cycle"

        return "No cycle"


g = Graph(5)
g.add_edge(1, 0)
g.add_edge(1, 2)
g.add_edge(2, 0)
g.add_edge(0, 3)
g.add_edge(3, 4)
print(g.dfs())