Python generators are awesome. Why ?
- their syntax is simple an concise
- they lazily generate values and hence are very memory efficient
- bonus point: since Python 3 you can chain them with
Their drawback ? They can be iterated only once, and they hide the iterable length.
I took an habit of making a generator of every function I write that generates an iterable. Basically, it simply means using
def list_vowels_before(char): for vowel in ('a', 'e', 'i', 'o', 'u', 'y'): if vowel >= char: return yield vowel
But now, if I want an iterable that I can iterate several times, I need to convert to a list the generator returned by this function in every piece of code that invoke it:
selected_vowels = list(list_vowels_before('t'))
But this isn't very good in terms of code readibility & maintainability: if one use this function but forget to do the conversion, the resulting object won't behave as expected. E.g. a simple conditional that test if the list is empty will evaluate to
selected_vowels = list_vowels_before('a') if selected_vowels: print('AMAZING: there are vowels BEFORE the letter "a" !!')
In case you want to use the generators syntax, but ensure that a function always return a list, here is a simple recipe you can use:
# This @decorator is totally optional, but it is a recommended best practice try: # We try to import GrahamDumpleton/wrapt if available from wrapt import decorator except ImportError: # fallback to the standard, less complete equivalent from functools import wraps as decorator @decorator def aslist(generator): "Function decorator to transform a generator into a list" def wrapper(*args, **kwargs): return list(generator(*args, **kwargs)) return wrapper
And now you just have to add a single line of code to your previously defined function:
@aslist def list_vowels_before(char): for vowel in ('a', 'e', 'i', 'o', 'u', 'y'): if vowel >= char: return yield vowel selected_vowels = list_vowels_before('a') if selected_vowels: print('This will never be printed ;)')