I refer to The Blub Paradox -
http://www.paulgraham.com/avg.htmlI have used both languages extensively, off and on for nearly 20 years. I've also used C, C++, Java, and built some small projects with Clojure and Elixir.
Of course people have differing sensibilities when it comes to aesthetics, but at a certain point equally observed options can be compared - with one being recognized as better.
I don't put down Python or Pythonistas because I dislike them; I just voiced my frustration that so many companies choose Python when I obviously believe there are better choices (particularly similar complexity/feature options like Ruby).
Clumsy, an incomplete comparison:
Python
OOP
Having to explicitly pass self.
Not having public/protected/private members. Ruby does, including convenience attribute reader/writer functions. And in Ruby you can still provide your own custom methods. In Python, you have to keep an internal _attrname and then have some set_attrname function if you want to provide special logic on your setter.
General recommendation to not use dunders, except that you should define dunders such as __init__ and __str__ for many classes. Ruby's class initializer is an optional method called... "initialize". (Principle of least surprise.) It also has a standard of to_something() methods which return the object in different formats, such as to_str, to_i, to_f, etc. More on this later.
Built-in functions:
sorted(), a past tense verb, doesn't "sorted" a list, it gives you a sorted copy of a list. Not to be confused with list.sort(), which also sorts but returns nothing and mutates list.
next(), which mutates the iterator you pass it. Most other built-ins do not mutate their arguments.
int(), float(), sorted(), next(), etc. all only work for certain argument types. The way to know is just to know. In Ruby, these would be object methods. And you could know if you could do foo.to_i (convert to int) just by asking "abc".respond_to?(:to_i).
Incidentally, Python's int("abcd") raises an exception, while int("1") works as expected. You can't ask the string "can you be converted to int?" Now maybe converting strings to ints is unwise in general, and that's fair. But the ergonomics here are... not good.
And regarding sorted() vs foo.sort(), in Ruby we have foo.sort (non-destructive, returning a sorted list), and foo.sort! (mutates in place). The ! indicates a more significant impact of the function compared to the non-bang version.
Elsewhere in the threads I illustrate list comprehension vs Ruby's collection methods and blocks.
Ternary operator:
Many languages: flag_set ? 1 : 0
Python: 1 if flag_set else 0
This is an if else. So written as an if else, it would be:
if flag_set
1
else
0
The Python ternary operator rearranges this logic.
But! In Python you can't do "1 if flag_set". Ironically, that's fine in Ruby.
Speaking of block return values... (from the imperative if/else above)...
Ruby:
x = if flag_set
1
else
0
end
You cannot do this in Python because conditional blocks do not return a value.
It is common in Ruby to say x = case(foo) ... and each match will have a value. The value of the expression in the case which matches is used to set x. In Python (now that you finally, as of 3.10 actually have a switch statement), you must repeat the x = expr for each match.
I will say I'm amazed that after so long of no switch statement in Python, they chose to build one with pattern matching reminiscent of Elixir's.
If I were to dig through my memory I could find more examples of Python oddities. My decision to merely compress it all into one word - clumsy - was to avoid getting into these details, particularly since the assumed audience was Ruby users based on the topic.