Skip to content

Instantly share code, notes, and snippets.

@PadLex
Last active April 29, 2020 10:37
Show Gist options
  • Select an option

  • Save PadLex/5fea22fb0033fe6d323bac322492ac5d to your computer and use it in GitHub Desktop.

Select an option

Save PadLex/5fea22fb0033fe6d323bac322492ac5d to your computer and use it in GitHub Desktop.
This script will automatically delete unused functions from a python script.

Auto remove unused python methods

Are you dealing with a large script full of unused methods? This script will iteratively execute vulture to try and guess which methods are unreachable and remove them. You can whitelist specific functions and test the script at different levels of depth. Should work on all versions of python 2 and 3 supported by vulture.

import re
import os
import shutil
import warnings
# Method definitions #
def get_indentation(line):
for i, char in enumerate(line):
if not char.isspace():
return i
def find_method_definition_end(script, def_line_number):
# Deal with multi-line function definitions
i = 0
while True:
line = script[def_line_number + i]
select_between = r'%s(.*?)%s'
# remove strings
line = re.sub(select_between % ('\'', '\''), '', line)
line = re.sub(select_between % ('\"', '\"'), '', line)
# remove comments
line = re.sub(select_between % ('#', '$'), '', line)
if ':' in line:
if i > 0:
print(def_line_number + i)
return def_line_number + i
i += 1
def find_function_ends(script, element):
start = element["line"] - 1 # Start of the definition. def count(one, \n
# Verify that the expected start of the definition corresponds with the actual start.
regex = r"\s*def\s*%s\s*\(" % element["name"]
if not re.match(regex, script[start]):
raise Exception("Error: line number is not corresponding. "
"The file may have been modified after running vulture")
colon_start = find_method_definition_end(script, start) # Find the end of the definition. two, three): \n
# Deal with one-line methods
colon_indentation = get_indentation(script[colon_start])
next_indentation = get_indentation(script[colon_start + 1])
if colon_indentation == start and next_indentation <= colon_indentation:
end = start
return start, end
start_indentation = next_indentation
count_empty = 0
for i in range(colon_start + 1, len(script)-1):
line = script[i]
if '/t' in line: # Tabs warning
warnings.warn("PEP 8 disallows the use of tabs. "
"The script will still work so long as you don't combine tabs with spaces.")
if line.isspace(): # Ignore empty lines
count_empty += 1
continue
new_indentation = get_indentation(line) # Detect end of function
if new_indentation < start_indentation:
end = i - 1 - count_empty
return start, end
if not line.isspace():
count_empty = 0
raise Exception("Error: could not find end of %s %s starting at line %d"
% (element["type"], element["name"], element["line"]))
def remove_between(script, marked_for_deletion):
clean_script = []
previous_end = 0
for del_range in marked_for_deletion:
start = del_range[0]
end = del_range[1] + 1
clean_script.extend(script[previous_end: start])
print(previous_end, start)
previous_end = end
clean_script.extend(script[previous_end:])
return clean_script
def remove_unused(script_path, output_path, vulture_output_path): # Execute one removal cycle
finished = True
# Identify unused objects from vulture output
unused_elements = []
with open(vulture_output_path, 'r') as input_file:
contents = input_file.readlines()
for line in contents:
split = line.split()
element = {
"line": int(split[0].split(':')[1]),
"type": split[2],
"name": split[3].replace('\'', '')
}
unused_elements.append(element)
# Read script
with open(script_path, 'r') as script_file:
script = script_file.readlines()
# Find start end end of methods
marked_for_deletion = []
for element in unused_elements:
if element["type"] == "function":
start, end = find_function_ends(script, element)
marked_for_deletion.append((start, end))
finished = False
# Generate script without unused elements and write changes to disk
clean_script = remove_between(script, marked_for_deletion)
with open(output_path, 'w') as output_file:
output_file.writelines(clean_script)
# ToDo remove debug
print(contents)
print(unused_elements)
print(marked_for_deletion)
return finished
if __name__ == "__main__":
# Initial parameters #
# Relative path to the script you want to parse
# Probably the only variable you want to change
script_path = "laser.py"
if not os.path.isfile(script_path):
raise ValueError("File does not exist: %s" % script_path)
if script_path[-3:] != ".py":
raise ValueError("Error: %s does not end with .py" % script_path)
# Whether or not to automatically execute vulture
# If you intend to manually generate the unused.txt file set thi to False
auto_call_vulture = True
# Whitelist methods which should be ignored by vulture
# Add any false positives here
whitelist = ["effect"]
# Directory name to contain each iteration of the script
# Every iteration will have fewer and fewer unused methods but will be more likely to break your script
cycle_directory = "cycles"
if os.path.exists(cycle_directory):
shutil.rmtree(cycle_directory)
os.mkdir(cycle_directory)
# Code starts here #
# Copy script as iteration 0
i0_path = script_path[:-3] + "_i0.py"
i0_path = os.path.join(cycle_directory, i0_path)
shutil.copy(script_path, i0_path)
script_path = i0_path
whitelist_str = "--ignore-names " + ",".join(whitelist) if len(whitelist) > 0 else ''
unused_path = os.path.join(cycle_directory, "unused.txt")
# Iteratively removed unused functions until vulture cannot find any
finished = False
n = 0
while not finished:
script_path = script_path[:-4] + "%d.py" % n
output_path = script_path[:-4] + "%d.py" % (n + 1)
print('\n', script_path, output_path)
# Execute vulture and write output to cycles/unused.txt
if auto_call_vulture:
os.system("vulture %s %s > %s" % (whitelist_str, script_path, unused_path))
finished = remove_unused(script_path, output_path, unused_path)
if not auto_call_vulture:
finished = True
n += 1 # Useful comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment