Looking for inactive functions – Part 4

If you’ve ever opened a big Python project and thought, “Where do I even start?” . As some of my projects grow, keeping track of how classes, functions, and modules connect quickly turn into a tangled mess – sometime. I often ask myself “Wouldn’t it be nice if you could actually see how everything fits together?”

That’s where this knowledge graph comes in. I am turning my codebase into a network. The nodes represent my files, classes and functions. Each edges show how they depend on each other. I could use arrows but the would only require an little edit to the code below – just change G = nx.Graph() to G = nx.DiGraph()

Suddenly, instead of guessing where that function gets called or which class inherits from what, I can literally map it out.

I use the Python libraries NetworkX and Matplotlib’s pyplot. You can build and visualize these graphs in just a few lines of code. NetworkX helps you create the relationships (the brain of the graph), while Matplotlib draws it out (the eyes). The end result? A clear, visual overview of the code structure — kind of like a mind map for a Python project.

I think this approach is useful for beginners because it helps:

  • Understand how different parts of your project connect.
  • Spot problems like tightly coupled functions or circular dependencies.
  • Learn faster, since you can see the bigger picture instead of diving blindly into the code.

So, next time you’re exploring a new project — or your own code starts feeling too big to hold in your head — try mapping it out as a graph. It’s a fun, visual way to make sense of complexity and build real intuition about how Python projects fit together.

Added the functions to the Knowledge Graph. To my surprise I found a I found a few classes that I set up right in the beginning which I had forgotten about. The Game.py file is pretty long and I am now thinking about seperating some of the core Game logic and the UI functions.

In my next post, I will but adding links to find what functions are calling which. This is the aim of this litte side project because I am looking to old functions that are no longer active and can be delete.

The Map class in my Zombie Game does all of the work calculating paths, loudest tiles, mose noise, illumination, neigbouring tiles. The tile class is containing all the tiles is held in this class as an object.
There is a skill called “Matching Pairs”. This skill means the a survivor automatically finds two of weapons that can be fire double handed. This effects katana sword, pistols, machettes and sawnoffs. I have this function built in a searching function, but it could also be in “Bluecards” function.

import os
import re
import networkx as nx
import matplotlib.pyplot as plt


class Map(object):
	def __init__(self):
		self.use_directories = []
		self.ignore_filenames = []
		self.file_extensions_required = []
		self.map_files = []
		self.classes_found = []
		self.functions_found = []
		

	def mapper(self):
		
		self.get_all_files()
		
	def get_all_files(self):

		# read all the required files in the directories into map_files array

		counter = 0

		for directory in self.use_directories:
			
			files = [ f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) ]
			
			for f in files:
				if self.is_file_required(f): 
					full_path = "/".join([directory, f])
					graph_id = "file" + str(counter)
					self.map_files.append( { 'id' : graph_id, 'path' : full_path, 'name' : f } )
					counter = counter + 1
				
				
		# if we have any files at all

		if self.map_files:
			
			self.find_functions_and_classes()
			self.cleanup()

			
	def cleanup(self):
		
		# Clean up references to Parent Classes to their files OR Child classes to their reps. parent classes

		for e, m in enumerate(mapper.classes_found):
			
			if m['class_parent'] == 'object':
				m['parentid'] = m['fileid']
			else:
				for f in mapper.classes_found:
					if f['class'] == m['class_parent']:
						m['parentid'] = f['id']
				
			

	def is_file_required(self, filename):
		
		# Ignoring : file has no extension
		if "." not in filename:
			return False
		
		# Ignoring : file extension not required
		file_parts = filename.split(".")
		if file_parts[-1] not in self.file_extensions_required:
			return False
		
		# Ignoring : Filename is excluded
		if filename in self.ignore_filenames:
			return False
			
		return True


	def find_functions_and_classes(self):
		
		counter = 0
		
		for mf in self.map_files:

			parent_file = mf['id']
			
			with open(mf['path'], "r") as f:
				current_class = ""
				
				for l in f:
					l = l.replace("\n", "").strip()
					if re.search("^class", l):
						current_class=l
						class_definition = re.match(r"class (?P<classname>.+)\((?P<classparent>.*)\)", l)
						
						class_name = ''
						class_parent = ''
						
						if class_definition:
							cdetails = class_definition.groupdict()
							class_name = cdetails['classname']
							class_parent = cdetails['classparent']
						
						graph_id = "class" + str(counter)
						classid = graph_id						
						self.classes_found.append({ 'id' : graph_id, 'fileid' : mf['id'], 'parentid' : '', 'current_class' : current_class, 'class' : class_name, 'class_parent' : class_parent })
						counter = counter + 1
						
					if re.search("^def\s(.*):$", l):
						function_definition = re.match(r"def (?P<functionname>.+)\((?P<aarrgs>.*)\)", l)
						function_name = ''
						
						if function_definition:
							fdetails = function_definition.groupdict()
							function_name = fdetails['functionname']
						
						graph_id = "function" + str(counter)
						self.functions_found.append({ 'id' : graph_id, 'fileid' : mf['id'], 'parentid' : classid, 'function' : function_name })
						counter = counter + 1
					
# create class
mapper = Map()

# set requirements for mapper

mapper.use_directories.append("/home/phill/Desktop/projects/zombizied_gui")
mapper.use_directories.append("/home/phill/Desktop/projects/zombizied_gui/classes")
mapper.file_extensions_required.append("py")
mapper.ignore_filenames.append("zombizied_ai_preprocessor.py")
mapper.ignore_filenames.append("zombizied_mapbuilder.py")





# start mapping 

mapper.mapper()


G = nx.Graph()


# Add Root node

G.add_node("Zombizied", entity="root", label='Zombizied GUI')

# Add Files to root

for e, m in enumerate(mapper.map_files):
	G.add_node(m['id'], entity="file", label=m['name'])
	G.add_edge("Zombizied", m['id'] )
	
# Add Class Nodes


for e, c in enumerate(mapper.classes_found):

	try:
		c['parentid'] != ''
	except:
		c['parentid'] = 'Zombizied'
	finally:
		
		G.add_node(c['id'], weight= 1, entity="class", label=c['class'])
		G.add_edge(c['parentid'], c['id'])

# Add Function Nodes

for e, f in enumerate(mapper.functions_found):
	
	try:
		f['parentid'] != ''
	except:
		f['parentid'] = 'Zombizied'	
	finally:
		G.add_node(f['id'], weight= 1, entity="function", label=f['function'])
		G.add_edge(f['parentid'], f['id'])

labels = nx.get_node_attributes(G, 'label')


color_map = {
	'root' : 'red',
	'file' : 'skyblue',
	'class' : 'lightgreen',
	'function' : 'lightcoral'
}

node_colors = [
	color_map.get(G.nodes[node].get('entity', ''), 'gray')
	for node in G.nodes()
]

pos = nx.spring_layout(G, seed=2323424)

nx.draw(G,
		pos,
		labels=labels,
		node_color=node_colors,
		node_size=500,
		font_size=8,
		edge_color='lightgray'
)	

plt.show()

Similar Posts