I have created a wrapper for displaying messages with ArcPy and IDLE (as that is all I have available to myself due to certain circumstances) as I frequently use both to test and develop new tools. However I find myself repeatingprint statements andarcpy functions to display feedback.
Unfortunately, it has to be in compatible with Python 2.7.
"""REMOVED HEADER"""import sysfrom time import strftimeimport arcpydef display_title(title, char_limit=100): # Format the title, and provide a border above and below to seperate the # title title = ("\n{0}\n{1:^" + str(char_limit) + "}\n{0}\n").format( "-" * char_limit, title.upper()) # Display the title print(title) arcpy.AddMessage(title)def display_message(message, char_limit=100): # Add the time to the message, and this will also run the # beautify_message function message = add_time_to_message(message, char_limit) # Display the message print(message) arcpy.AddMessage(message)def display_warning(message, char_limit=100): # Add the time to the message, and this will also run the # beautify_message function message = add_time_to_message("WARNING: " + message, char_limit) # Display the message print(message) arcpy.AddWarning(message)def display_error(message, char_limit=100, traceback=False): # Add the time to the message, and this will also run the # beautify_message function message = add_time_to_message("Error" + message, char_limit) # Add the Python traceback information (if available) if traceback: tracebackInfo = traceback.format_exc() if tracebackInfo != "None\n": message += "\n\nPython Traceback Information:\n" message += tracebackInfo # Display the message print(message) arcpy.AddError(message) # Terminate the Script now sys.exit()def display_parameters(parameters, char_limit=100): # Make sure that the input type is correct if isinstance(parameters, tuple) or isinstance(parameters, list): if len(parameters) > 1: # Then there is more than one, print a list var_string = "Parameter list:\n" for var in parameters: var_string += "{} = {}\n".format(var[0], var[1]) # Remove the last "\n" var_string[:-1] else: var_string = "{} = {}".format(parameters[0][0], parameters[0][1]) # Format the message string message = add_time_to_message(var_string, char_limit) # Display the message print(message) arcpy.AddMessage(message)# Format the message so it displays nicely within the progress boxdef beautify_message(message, char_limit=100): # Create an empty list of beautified lines processed_lines = [] # Split the message into lines that are defined with a new line split_message = message.split("\n") # Loop through the each split line for line in split_message: # Set the counter to zero counter = 0 # Loop though each split line to check the length while len(line) > char_limit: # Then this line is too long, split it up split_line = line[counter:(counter + 1) * char_limit] # Capture the last space just in case space = True if split_line[-1] == " " else 0 # Split it on spaces split_line = split_line.split() # Add the chunk to lines processed_lines.append(" ".join(split_line[:-1])) # Take the left over bit and add it back to line line = split_line[-1] + " " * space + line[( counter + 1) * char_limit:] # Add the last bit to lines processed_lines.append(line) # Return the final output return processed_lines# Add the timestamp and indent messagedef add_time_to_message(message, char_limit=100): # Determine the current time in string format to be included in some # messages current_time = strftime("%x %X") + " - " # Determine the limit for each line if including the time limit = char_limit - len(current_time) # Break the message up into lines of the right length lines = beautify_message(message, limit) # Add the time and the first line message = current_time + lines[0] + "\n" # Add the rest of the lines for line in lines[1:]: message += " " * len(current_time) + line + "\n" # Remove the extra "\n" character message = message[:-1] # Return the final output return messageAny and all constructive criticism welcome!
1 Answer1
Overall, your code is well-formatted and I believe is fairly readable. It clearly accomplishes its job, looks nice! I'll keep this review in python 2.7 since it sounds like your constraints require it, though hopefully you are able to migrate it to 3.x eventually:
Multipleisinstance checks
Instead of:
if isinstance(val, type) or isinstance(val, othertype) or ...Do
if isinstance(val, (type, othertype, ...))Guard Clauses
Switch the instance check to a negative one and return early. This removes indentation from your code:
# go fromif isinstance(parameters, (tuple, list)): # code# doif not isinstance(parameters, (tuple, list)): return# rest of functionIt also seems strange that if parameters don't match a type, that a function would silently return rather than raise or at least display an error. I'd reconsider the implementation here:
if not isinstance(parameters, (tuple, list)): raise TypeError( "Parameters expected to be of type 'list' or 'tuple', got {}" .format(type(parameters)) )display_parameters
There are a few things to note here:
- Do a single concatenation of strings rather than multiple in a loop
- Use tuple unpacking where possible
def display_parameters(parameters, char_limit=100): if not isinstance(parameters, (tuple, list)): raise TypeError(...) if len(parameters) > 1: var_string = "Parameter list:\n" else: var_string = "" var_string += ( "\n".join( "{} = {}".format(a, b) for a, b, *_ in parameters ) ).rstrip('\n') # Format the message string message = add_time_to_message(var_string, char_limit) # Display the message print(message) arcpy.AddMessage(message)beautify_message
This is probably the most confusing function IMO. It's well named, so I understand the intent, but it reads a bit clunky. Thecounter is really a static0, since it's never redefined.
Furthermore, in trying to break up lines into n-length chunks, this is thegrouper recipedefined in more-itertools (with the options that fit your use-case the best):
from itertools import izip_longestdef grouper(iterable, n, fillvalue=''): "Collect data into non-overlapping fixed-length chunks or blocks." # grouper('ABCDEFG', 3) → ABC DEF G iterators = [iter(iterable)] * n return izip_longest(fillvalue=fillvalue, *iterators)Replacing your while-loop with this code:
# Format the message so it displays nicely within the progress boxdef beautify_message(message, char_limit=100): processed_lines = [] for line in message.split('\n'): for grp in grouper(line, char_limit): processed_lines.append(''.join(grp)) # Return the final output return processed_linesDocstrings
I would add docstrings to show expected inputs, returns, and general documentation of your code.
