Friday 21 April 2023

List Comprehensions vs Chaining Style

For sure list-comprehensions are a beautiful Python feature, but I've realised that I've been overusing them lately, applying a list comprehension to another list-comprehension, which makes the code not particularly clear. This has led me to compare it with the common approach followed in other languages: chaining methods on collections (JavaScript array extras or lodash, linq, pydash in python itself, Kotlin collections methods...). Let's compare some examples:

Using nested list comprehensions like this in Python seems pretty elegant to me:


cities = [ city.upper()
	for country in countries if country.name.startswith("c")
	for city in country.cities if city.name.startswith("c")
]

We can write an equivalent in "chaining style" in JavaScript like this:


let cities = countries.filter(country => country.startsWith("c"))
	.flatmap(country => country.cities)
	.filter(city => city.startsWith("c")
	.map(city => city.toUpper());


In this case I think I prefer the list-comprehensions option.

Nested list-comprehensions are also very nice if I want to combine values from the main and the nested collection. For example here, where I want a listing of country-city:


country_and_cities = [ [country.name, city.name]
	for country in countries 
	for city in country.cities 
]

That I would write in "chaining style" in JavaScript like this


let countryAndCities = countries.flatMap(country => country.cities.map(city => [country.name, city.name]));

Let's go now with those cases where I think list-comprehensions make code not so pleasant to read.

For example for parsing some lines read from a CSV into a list of objects we would write:


@dataclass
class Person:
	name: str
	age: int
	
countries = [Person(items[0], int(items[2])
	for items in [
		line.split(",") 
		for line in lines if not line.startswith("--")
	]
]

We would write it in "chaining style" in javascript like this:


lines.filter(line => !line.startsWith("--")
.map(line => {
	let [name, age] = line.split(",");
	return new Person(name, int(population));
});


And for dumping a list of objects to a CSV string we have this Python code with list-comprehensions:


users = [Person("Francois", 55), Person("Iyan", 45)]

"\n".join([user_line for user_line in 
    [";".join(user.name, str(user.age))
    for user in users]
])

That I think looks better in "chaining style" with javascript like this (or using pydash in python for example)


users.map(user => [user.name, user.age].join(";"))
	.join("\n");


No comments:

Post a Comment