I come from Ruby and you can method chain very easily. Let's look at an example. If I want to select all even nums from a list and add 5 to it. I would do something like this in Ruby.
nums = [...]
nums.select {|x| x % 2 == 0 }.map { |x| x + 5 }
In Python that becomes
nums = [...]
list(map(lambda x: x + 5, filter(lambda x: x % 2 == 0, nums)))
The Python syntax looks horrible. I tried to Google and didn't really find any good answers. All I saw was how you can achieve something like this with custom objects but nothing to process lists this way. Am I missing something?
When in a debugging console, it used to be extremely helpful to get som ActiveRecord objects in an array and I could just chain methods to process the entities to debug things. With Python, it almost seems like too much work.
In Ruby, every enumerable object includes the Enumerable
interface, which is why we get all of those helpful methods like you mention. But in Python, there's no common superclass for iterables. An iterable is literally defined as "a thing which supports __iter__
", and while there is an abstract class called Iterable
which pretends to be a superclass of all iterables, it doesn't actually provide any methods and it doesn't sit in the inheritance chain of all iterables (it overrides the behavior of isinstance
and issubclass
using the magic of dunder methods, the same way you can override +
by writing __add__
).
The Alakazam library implements exactly this feature. (Disclosure: I am the creator and maintainer of this library, but it does exactly what you're asking for, so I'll mention it here)
Alakazam provides the Alakazam
class, which wraps any Python iterable and provides, as methods, all of the built-in Python sequence methods, all of the itertools
module, and some other useful stream-oriented methods that aren't included in Python by default. Consider your example from above
nums.select {|x| x % 2 == 0 }.map { |x| x + 5 }
In Python, that looks like
list(map(lambda x: x + 5, filter(lambda x: x % 2 == 0, nums)))
With Alakazam, that looks like
zz.of(nums).filter(lambda x: x % 2 == 0).map(lambda x: x + 5).list()
or, using Alakazam's lambda syntax
zz.of(nums).filter(_1 % 2 == 0).map(_1 + 5).list()
Whenever reasonable, Alakazam's methods like filter
and map
are lazy to match Python's behavior, so we still need to write list()
at the end to consume the iterable and produce a single list result.