2

Just now I was reading a Python question on stack overflow, about clamping a list/array of results to be within a certain range.

Once of the more simple answers suggested something like:

clamped_list = [ max(64, min(128, i)) for i in source_list ]

This sort of list/array construction loop is championed as "The Pythonic Way". If answers implement this same algorithm as a series of steps in a loop-body, there would suggestions that it is "not pythonic", and probably down-votes.

Yet if a C/C++ for() loop were constructed similarly ~

for (i=0; i<MAX_ELEMENTS; clamped[i] = iMAX( 64, iMIN( 128, source[i] ) ), i+=1 );

It would never pass any sort of code review (at least none I have ever participated in).

What is the history of this complicated "Pythonic Way", how did something that had been frowned on in other languages long before Python was invented become the idiomatic way in python?

  • List-comprehensions are not necessarily equivalent to the for-loop youve presented. The clamped assignment would be in the body, not the parens. I prefer map anyways. – D. Ben Knoble Feb 13 at 0:28
  • @RayButterworth - No, it's not required. – Kingsley Feb 13 at 2:42
  • 1
    It's called a "comprehension" - it's not exactly an alternate way to write a loop, it's a different language construct (although there may be a loop somewhere under the hood). Your impression that it's complicated comes from being unfamiliar with it; but for python programmers, it's not complicated, and it's often a more concise and a more readable way to express something in code (again, if that sounds strange, it's because of unfamiliarity). Historically, it comes from math & functional programming languages - see this. – Filip Milovanović Feb 13 at 6:22
  • 1
    BTW, the reason that thing in C++ would never pass code review, is because it's full of clutter, you can't really read it and understand what it's doing without some effort. The Python code above (assuming you are familiar with the language) can be read and understood as is; it almost reads as an English sentence. – Filip Milovanović Feb 13 at 6:28
  • 1
    There's no language "C/C++". If I were writing that in C++, it'd be auto clamped_view = source | transformed([](auto i){ return max(64, min(128, i)); }); – Caleth Feb 13 at 9:20
7

C does not have lists as part of the language, and definitely no list comprehensions. That's why you have to operate on arrays/vectors using indexes, or allocate or declare them explicitly, which makes the whole section a little bit more clumsy.

But if you reorder the very same statements you showed in your C example into a loop body like this

for (i=0; i<MAX_ELEMENTS; i++) 
{
   clamped[i] = iMAX( 64, iMIN( 128, source[i] ));
}

(adding brackets for those who dislike loop bodies without them, even for single statements), then I see no compelling reason why this could not pass a code review.

However, note the Python statement does more than the C code above: it also declares clamped_list as a list of the same size as source_list. If you add some equivalent of that to the code, the difference between C and Python becomes more obvious:

int i;
int clamped[MAX_ELEMENTS];
for (i=0; i<MAX_ELEMENTS; i++) 
{
   clamped[i] = iMAX( 64, iMIN( 128, source[i] ));
}

So the reasons here why the C code is more verbose (not more complex, the actual complexity is the same), are

  • C has no inbuilt lists or list comprehensions, so explicit indexing/iteration is necessary
  • C requires explicit declarations of variables and arrays
  • Lots of C programmers prefer brackets even for one-liners.

Note there is also a synergy effect in this example: since the Python code requires so few "noise" code, putting the "min/max" statements with the list comprehension into just one line is still very readable. In C, it is probably better to put the code which describes the iteration on one line, and the min/max operation on a second, because both things together in one line becomes hard to comprehend.

Furthermore, since C++ 14 you can write such code almost as "dense" as in Python. Citing @Caleth's example from a comment, in modern C++ one can write the code this way

  auto clamped_view = source | transformed([](auto i){ return max(64, min(128, i)); });

If that is as readable as the Python line is surely a matter of taste, and a matter of what kind of code one is used to.

3

It ultimately comes down to a matter of perspective.

List comprehensions, which is what your python example uses, tackle the idea of generating an array from a loop by putting the loop inside the array syntax.

If this was possible in C, it'd look more like this:

// clamped_list = [ max(64, min(128, i)) for i in source_list ]
int clamped_list[16] = { max(64, min(128, source_list[i])) for (i=0,i<16,i++) };

Assuming a fixed length of 16 and a type of int.

Instead, C, being a way older language, does things in a more imperative way, where the loop is on the outside and you're explicitly putting the elements into the array.

Another very common (and my personal favourite 😀) way of dealing with this is functional concepts like map functions, where you'd write something like (pseudocode)

clamped_list = map( source_list, (element) -> max(min(element, 128), 64) )

All 3 do the same thing in the end, but different programmers will find them differently easy to understand, based on many factors like experience with the domain or previously used programming languages.

The important thing to take away here is to always write idiomatic code. Trying to write python code in C will be as much frowned upon as writing C-Style python code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.