Style
You’ve already been told that by @alexwlchan in your previous question but: "whitespace is cheap" and it improves readability a lot. Compare your code to:
def asciiart(text, conf=default_conf): """Produces an ascii art representation of text using the conf data""" if isinstance(conf, str): conf = conf.split('\n') height, key, data = int(conf[0]), conf[1], conf[2:] figures = dict(map( lambda x: (x, data[key.index(x) * height: (key.index(x) + 1) * height]), key)) art = '\n'.join([ '\n'.join([ ' '.join([figures[x][i] for x in t.lower() if x in figures]) for i in range(height)]) for t in text.split('\n')]) return art
Again, you should also consider using generator expressions rather than list-comprehensions to feed into your join
calls: turn those square brackets into parenthesis.
Handling font configuration
First of, when reading the file, you should remember to close it:
with open('C://Programs//Python35//Lib//asciiart.txt') as configuration_file: default_conf = configuration_file.read()
You may also want to name the resulting string DEFAULT_CONF
as it is a constant.
Second, why parse this string at each call of asciiart
? You should provide a function that will do the parsing and asciiart
will only be responsible of using the parsed configuration:
ASCII_ART_CONFIGURATION = {} def parse_ascii_art_configuration(filename='C://Programs//Python35//Lib//asciiart.txt'): with open(filename) as f: conf = f.read().split('\n') height, key, data = int(conf[0]), conf[1], conf[2:] figures = dict(map( lambda x: (x, data[key.index(x) * height: (key.index(x) + 1) * height]), key)) ASCII_ART_CONFIGURATION.update(figures) def asciiart(text): if not ASCII_ART_CONFIGURATION: parse_ascii_art_configuration() art = '\n'.join( '\n'.join( ' '.join(ASCII_ART_CONFIGURATION[x][i] for x in t.lower() if x in ASCII_ART_CONFIGURATION) for i in range(height)) for t in text.split('\n')) return art
The advantage being:
- You give your users direct access to the underlying
ASCII_ART_CONFIGURATION
that they can populate the way they want if need be; - You give your users an easy way to provide their own configuration filepaths;
- You parse the configuration only once.
Obviously, this version wont work since asciiart
does not have access to height
any more. Time to bring our old friend zip
(or itertools.zip_longest
) back:
from itertools import zip_longest def asciiart(text): if not ASCII_ART_CONFIGURATION: parse_ascii_art_configuration() art = '\n'.join( '\n'.join( ' '.join(row) for row in zip_longest( *(ASCII_ART_CONFIGURATION[x] for x in t.lower() if x in ASCII_ART_CONFIGURATION), fillvalue='')) for t in text.split('\n')) return art
Better iterations
Now that we have a better API, let's focus a bit more on the code:
iteration over a file can be made using either the "reading" API or the iterator one, this can help better handle the separation of each part:
with open(filename) as f: height = int(next(f)) keys = next(f) data = [line.rstrip('\n') for line in f]
building of the figure
dictionary can be made more efficient using the grouper
recipe from itertools
:
def grouper(iterable, n): "Collect data into fixed-length chunks or blocks" # grouper('ABCDEFG', 3) --> ABC DEF G" args = [iter(iterable)] * n return zip_longest(*args, fillvalue='') def parse_ascii_art_configuration(filename='C://Programs//Python35//Lib//asciiart.txt'): with open(filename) as f: height = int(next(f)) keys = next(f) data = [line.rstrip('\n') for line in f] figures = { character: list(group) for character, group in zip(keys, grouper(data, height)) } ASCII_ART_CONFIGURATION.update(figures)
now that we use zip_longest
to concatenate ascii-art letters, you can simplify handling missing symbols by turning ASCII_ART_CONFIGURATION
into a collections.defaultdict
:
ASCII_ART_CONFIGURATION = defaultdict(list) def asciiart(text): if not ASCII_ART_CONFIGURATION: parse_ascii_art_configuration() return '\n'.join( '\n'.join( ' '.join(row) for row in zip_longest( *(ASCII_ART_CONFIGURATION[x] for x in line_of_text), fillvalue='�')) for line_of_text in text.lower().splitlines())