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
yield from
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 yield
:
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 True
:
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 ;)')