Why does one version of my function return None, while the other works fine?

Inspired by the pipeline operator (|>) in other languages, I tried to implement a function to imitate it. When I use reduce to implement it, it results in None somewhere along the way, but another one using loops works just fine.

from functools import partial, reduce
from pprint import pprint


def pipeline(data, *funcs):
    res = data
    for func in funcs:
        res = func(res)
    return res

def pipeline_v2(data, *funcs):
    reduce(lambda arg, func: func(arg), funcs, data)

def main() -> None:
    result = pipeline(
        range(1, 11),
        partial(map, lambda x: x + 1),
        partial(filter, lambda x: x % 2 == 0),
    )
    pprint(f'Final result: {tuple(result)}')
    pprint('---------------------------------------------------------------------------------------------')
    result = pipeline_v2(
        range(1, 11),
        partial(map, lambda x: x + 1),
        partial(filter, lambda x: x % 2 == 0),
    )
    pprint(f'Final result: {tuple(result)}')


if __name__ == "__main__":
    main()

This is the output:

Final result: (2, 4, 6, 8, 10)
---------------------------------------------------------------------------------------------
Traceback (most recent call last):
  File "pipeline_code.py", line 34, in <module>
    main()
  File "pipeline_code.py", line 30, in main
    print(f'Final result: {tuple(result)}')
                           ^^^^^^^^^^^^^
TypeError: 'NoneType' object is not iterable

Why does this happen? To me it seems like pipeline and pipeline_v2 do the same thing.

pipeline_v2 doesn’t return anything (python doesn’t implicitly return the last line, unlike some languages).

If a function doesn’t return a value, the return is None, which is what result is in your error message.

1 Like

I just noticed that on my own. Thanks for pointing it out. I guess this is one of the footguns in Python.

1 Like

Also, out of curiosity, if I were to add type annotations to pipeline (so hopefully a static type checker catches this mistake), how would I go about doing it? The return type of pipeline depends on the last function passed to it, and I don’t know how to access that.

I don’t know the answer to that. You could say it returns a Callable so that the type-checker can at least check for that, but I don’t know how to express the possible signature(s) of the Callable