Is there a library function in Python to turn a generator-function into a function returning a list?
Question:
A number of times I’ve thought that generator style can be more direct that returning a list, for example,
def foo(input_array):
for x in input_array:
yield processed(x)
vs.
def bar(input_array):
accumulator = []
for x in input_array:
accumulator.append(processed(x))
return accumulator
(okay, if it were really that simple, I’d write map
, but you get the point: the generator version is cleaner). However, a return type of a generator is not always desired. Is there a built-in decorator that I can use to change foo
into a function returning a list or tuple? The way I’d write it myself is,
import functools
def transform_return_value(transformer):
def inner(f):
@functools.wraps(f)
def new_f(*argv, **kwargs):
return transformer(f(*argv, **kwargs))
return new_f
return inner
@transform_return_value(list)
def foo(input_array):
for x in input_array:
yield processed(x)
Answers:
To the best of my knowledge (and I’ve looked, because I’ve wondered exactly the same thing), no: there is no direct way of doing this with the standard library.
There is a thoroughly tested listify
wrapper in unstdlib.py library, though: https://github.com/shazow/unstdlib.py/blob/master/unstdlib/standard/list_.py#L149
def listify(fn=None, wrapper=list):
"""
A decorator which wraps a function's return value in ``list(...)``.
Useful when an algorithm can be expressed more cleanly as a generator but
the function should return an list.
Example::
>>> @listify
... def get_lengths(iterable):
... for i in iterable:
... yield len(i)
>>> get_lengths(["spam", "eggs"])
[4, 4]
>>>
>>> @listify(wrapper=tuple)
... def get_lengths_tuple(iterable):
... for i in iterable:
... yield len(i)
>>> get_lengths_tuple(["foo", "bar"])
(3, 3)
"""
def listify_return(fn):
@wraps(fn)
def listify_helper(*args, **kw):
return wrapper(fn(*args, **kw))
return listify_helper
if fn is None:
return listify_return
return listify_return(fn)
For efficient and concise list definitions try using list comprehension:
def foo(input_array):
return [processed(x) for x in input_array]
If you want a function to return a list, have it return a list. This is much cleaner, easier to understand, read and debug than using decorator.
You may prefer to write this inline, rather than call a function.
Although @David Wolever’s answer is suerly the cleanest way, one thing I often find myself doing (as it doesn’t require to define an external decorator) is writing the generator as a local function, like this:
def foo(input_array):
def gen():
for x in input_array:
yield processed(x)
return list(gen())
Here’s an alternative, simple decorator without any bells and whistles:
from functools import wraps
from types import GeneratorType
def listify(func):
"""decorator for making generator functions return a list instead"""
@wraps(func)
def new_func(*args, **kwargs):
r = func(*args, **kwargs)
if isinstance(r, GeneratorType):
return list(r)
else:
return r
return new_func
A number of times I’ve thought that generator style can be more direct that returning a list, for example,
def foo(input_array):
for x in input_array:
yield processed(x)
vs.
def bar(input_array):
accumulator = []
for x in input_array:
accumulator.append(processed(x))
return accumulator
(okay, if it were really that simple, I’d write map
, but you get the point: the generator version is cleaner). However, a return type of a generator is not always desired. Is there a built-in decorator that I can use to change foo
into a function returning a list or tuple? The way I’d write it myself is,
import functools
def transform_return_value(transformer):
def inner(f):
@functools.wraps(f)
def new_f(*argv, **kwargs):
return transformer(f(*argv, **kwargs))
return new_f
return inner
@transform_return_value(list)
def foo(input_array):
for x in input_array:
yield processed(x)
To the best of my knowledge (and I’ve looked, because I’ve wondered exactly the same thing), no: there is no direct way of doing this with the standard library.
There is a thoroughly tested listify
wrapper in unstdlib.py library, though: https://github.com/shazow/unstdlib.py/blob/master/unstdlib/standard/list_.py#L149
def listify(fn=None, wrapper=list):
"""
A decorator which wraps a function's return value in ``list(...)``.
Useful when an algorithm can be expressed more cleanly as a generator but
the function should return an list.
Example::
>>> @listify
... def get_lengths(iterable):
... for i in iterable:
... yield len(i)
>>> get_lengths(["spam", "eggs"])
[4, 4]
>>>
>>> @listify(wrapper=tuple)
... def get_lengths_tuple(iterable):
... for i in iterable:
... yield len(i)
>>> get_lengths_tuple(["foo", "bar"])
(3, 3)
"""
def listify_return(fn):
@wraps(fn)
def listify_helper(*args, **kw):
return wrapper(fn(*args, **kw))
return listify_helper
if fn is None:
return listify_return
return listify_return(fn)
For efficient and concise list definitions try using list comprehension:
def foo(input_array):
return [processed(x) for x in input_array]
If you want a function to return a list, have it return a list. This is much cleaner, easier to understand, read and debug than using decorator.
You may prefer to write this inline, rather than call a function.
Although @David Wolever’s answer is suerly the cleanest way, one thing I often find myself doing (as it doesn’t require to define an external decorator) is writing the generator as a local function, like this:
def foo(input_array):
def gen():
for x in input_array:
yield processed(x)
return list(gen())
Here’s an alternative, simple decorator without any bells and whistles:
from functools import wraps
from types import GeneratorType
def listify(func):
"""decorator for making generator functions return a list instead"""
@wraps(func)
def new_func(*args, **kwargs):
r = func(*args, **kwargs)
if isinstance(r, GeneratorType):
return list(r)
else:
return r
return new_func