I agree. The more mature developer understands all languages have their strengths and weaknesses. I really like Python myself, and it's in my top three list of languages I like (Scala, Ruby and Python -- not in any order).
Even PHP, which is not a well designed language at all, has at least attempted to make core language improvements in PHP7 and has supersets like Hiphop/Hack which use the same syntax and avoid most of the insane typing pitfalls.
And in this case, the reality is the ternary operator is just a pretty bad syntax. Most teachers generally tell you to stay away from them and a lot of style guides do too.
Python tried to make it more english-like just like many other of it's syntax (using and/or/not instead of && || !) and in this case it feels strange to experienced programmers who are not used to it. That being said, if you show this and javascript one to someone who's new to programming, they'll for sure understand the Python one better.
Still, again that's where style guides come in. I've been coding Python for years and rarely ever used the ternary.
I can't think of a language that belongs less in that dichotomy than Python. Programmers used to gush over it like crazy; they only stopped because it became ubiquitous enough that there was no point. Javascript, PHP, and C get an order of magnitude more hate.
The difference to me about this is that it seems PEP308 was explicitly created to spite those wanting a conditional operator, whereas many other "incredibly poor design decisions" like e.g. PHP's conditional operator having the wrong/unintuitive associativity happened more accidentally/without explicit intent.
Python is an imperative language. The control structure, based on indentation, is well matched to Python's imperative character. Attempts to retrofit a functional style to Python fit Python's syntax badly. Lambdas, conditionals, and list comprehensions look painful if they extend beyond one line, because the indentation assumptions of Python just don't fit.
As a practical matter, only use Python's functional features in one-liners.
I think being "functional" or otherwise has nothing to do with it. Even in imperative languages you must use expressions, and it is there that the conditional operator is most useful.
Indeed. I find the huge "some_method" example pretty much invalid because I'd assign that dict to a variable first, so the line would be a nicer "return my_dict if y else {}". What person, style guide or PEP is forcing anyone to write some_method's return statement?
I don't get the ideal of saving space and writing huge one-liners (that in Python you'd have to break up over more lines). As they say on IRC: "if you're worried about using too many lines, I can send you a file full of blank ones."
You've identified a real issue (the functional features work badly except as one liners), and a good fix (only use them as one liners).
I think the cause has everything to do with some poor choices around the parser and syntax, however, and nothing to do with its imperative nature as such.
I'm not sure why the author calls the two branches of in if/else the consequence and the final_consequence. That naming suggests that you do both of them, with the latter coming after the first, which is of course not how an if/else works.
I'm sure that nearly everyone reading the article knows how an if/else works, so that naming won't actually lead them astray, but it just feels ugly.
The difference between Ruby and Python here is that Ruby has a perfectly functional ternary operator that works as one would expect. It also allows some other crazy syntax which can be appropriate in some cases, but it doesn't force you to use it in all cases.
Python does force you to use their awkward conditional expressions; there is no normal ternary operator.
There's a big difference between a language which lets you write hideous code, and a language which forces you to write hideous code.
Is that intended as a defense of Python's ability to enable clear, concise, expressive code? :)
More seriously: Yes, you are obviously correct. But while you can use if/else, you can't use a ternary, and you can in Ruby. That's why people complain about the lack in Python but don't wrote the same article about Ruby.
"Clear, concise, expressive code" isn't something that applies to ternary operators. They were always meant as shortcuts and, as such, will never be as clear as if else statements, practically by design.
Though I don't really dislike Python's approach so what do I know.
You can definitely use too many ternary operators, but I think when used sparingly they can be clear and tasteful. As long as you don't start nesting them or repeating them one after the other you should be fine.
The trailing if can sometimes be confusing in Ruby but the else clause is the thing that's just blatantly anti-intuitive about the Python syntax at issue. Ruby doesn't allow else there because that would be insane. Python chose the insane convoluted and confusing syntax over a well understood and accepted ternary operator.
I use ruby's trailing conditionals exactly this way all the time - it makes me happy to see someone else using the same pattern. I also use it at the start of functions in order to sanity check arguments:
def do_something(arg1, arg2)
return false if arg1.is_invalid
return false if arg2 < sane
...
end
The trailing `unless` is also handy, and I find it a bit nicer to read compared to a negated `if`.
I agree that it reads naturally, but when dealing with those kinds of sentences, they always need to be put into conditional form (A->B). Even when reading text the order of those words matters. Consider:
"I'm going to die ... (gasp) ... if you don't grab me some Subway. I'm starving"
The sentence is funny because the apodosis is misleading. Even thought that might be a valid narrative approach when writing a comedy, I don't think it's an appropriate coding one.
As a fellow member of the hates-Python-but-not-enough-to-quit-my-job-over-it club, comprehensions are just the worst. Uses variables before defining them, can't be readably broken over multiple lines, overloads multiple pieces of syntax, reads inside-to-out instead of left-to-right. Ugh.
1: When I hit the opening bracket, I don't know whether I'm looking at an array literal or a comprehension. I have to read ahead a few tokens to disambiguate.
2: When I hit `transform(bar)`, I don't know what `bar` is either. I have to jump forward to disambiguate again.
3: Once I hit `for bar in` I realize I'm in a comprehension, but that's still not enough to figure out what `transform(bar)` is acting on. I have to read to the end of the `if` condition, then backtrack to the beginning.
bonus reason: why not `list(baz)`? Is there actually some logic to when Python uses methods and when it uses global functions? If so, please tell me.
another bonus reason: when I was learning Python, I found the keyword reuse genuinely confusing. I would try to write for loops like `for x in my_list if condition(x):` and couldn't figure out why it didn't work because I knew I had seen that syntax somewhere before.
Now, your particular example isn't that bad, but if you make it just a little more complicated you get something like this aberration:
foo = [transform(bar) if bar.property else bar for bar in baz.list() if condition(bar)]
Nothing will convince me that you're not a mutant if you think that's clear and concise.
Which to me parses very easily into meat-brain logic, in small chunks, with a single pass:
-Start with `baz`
-Consider each item
-Select only those `bar`s that meet `condition`
-Map each `bar` to...
-`transform(bar)` when `property` is true; itself otherwise (okay, natural language actually more closely resembles Python-style ternaries here. Sue me).
P.S. apologies for the multiple edits; I am an HN formatting noob.
> When I hit the opening bracket, I don't know whether I'm looking at an array literal or a comprehension. I have to read ahead a few tokens to disambiguate.
All research I have seen says that proficient readers of a language read closer to a line at a time than a token at a time, so I suspect that not really a problem so much as a rationalization of aesthetic preference.
> bonus reason: why not `list(baz)`? Is there actually some logic to when Python uses methods and when it uses global functions?
list is a built-in type, and (as is usually the case for Python types) list() is the type constructor. There are other global functions in Python and there is room for debate over whether they make sense over methods, but constructors for built-types are pretty clear.
baz.list() is a method on baz. Assuming baz is iterable, list(baz) is valid, it's just different than baz.list().
maybe it's bad form but I generally prefer to break my list/dictionary/set comprehensions into various lines. For me it increases the readability
foo = [
if condition(bar) transform(bar) else other_transform(bar) # 1: storage
for bar in base.list() # 2: iteration
if bar_tester(bar). # 3: filtering
]
to each his own I suppose, until the PEP8 police come and get us all!!!
Filters and ternaries aren't the same kind of thing (ternary if/else are analogous to SQL SELECT-clause case statements, filter if is analogous to SQL WHERE-clause; the positions in Python conditionals, which are very much like SQL SELECT statements, follows that analogy.)
Yea, I like list comprehensions. I mean they make sense once you're use to reading the syntax. That's just like the following in Scala:
foo = baz.list().filter{ bar => condition(bar) }.map(transform(_)).toList
If it's your first time looking at Scala, you'd probably be like, "What the fuck is that shit?" but as you learn the language, these types of patterns become common and make a lot of sense.
I mean it's not like weird rules in natural language. At least the grammars for computer languages are strict, with very few edge cases (compared to human/spoken language).
Does Scala have line continuation? I got really used to pipes from R (which I guess come from F#/Elixir), which in turn got me really comfortable with multi-line statements like:
Instead of asking "what is awful about X", how about considering "what would make it better"?
I think this would be easier to read, given that what's inside the [] now looks almost exactly like the equivalent code without using a list comprehension:
foo = [for bar in baz.list() if condition(bar) transform(bar)]
(Disclaimer: I am not a regular Python user. Maybe that ordering conflicts with a syntax for something else.)
I come from a mathematics background and when I first saw comprehensions I really liked them. They read remarkably similar to Set Builder Notation [0]. I think so long as you don't go crazy they are perfectly readable. But at the end of the day the onus is still on the developer to ensure their code is readable.
Bachelor's in math myself, although my math interests and my programming interests have admittedly (and regrettably) not intersected very much in my career. I do tend to think about problems more "imperatively" in my head and then struggle sometimes to translate those concepts into declarative math notation when doing proofs; if I were able to make that leap more naturally maybe I'd benefit from the notational similarities more.
Comprehensions were inherited from the math and science community where such constructs are used a lot. It happens that Python is very popular in those communities.
And does not allow access to the list being created from within the list comprehension. Which makes a whole slew of things hard to impossible using comprehensions.
ruby blocks are exceptionally difficult to rationalize at first (well, at least they were for me). I get that they are beautiful (because they are) but it was a major stumbling block on my way to understanding ruby.
A cute feature of this syntax: in "p if c else q" you can think of "if c else" as an infix operator like + and the rest. This operator has an algebra worked out by John McCarthy -- e.g., it's associative. Of course I doubt this will help the poster to like Python more, but it might amuse others.
I thought it was a bug, then I understood it was a feature (after talking to him).
GvR hate ternary operators, the python 3nary is midfix, it sux, but it removes the possibility for people to do long chained if/then/else in unreadable ways.
If you think I am right and it sux, understand I think forbidding this kind of operator is like hating shortcuts and goto.
Since I love shortcuts, goto and chained ift and python, I logically found ways to not care of PEP308 because it is a non problem.
Dispatch tables, and/or used wisely ... there are a lot of ways to not care about PEP308.
I think it could be ambiguous in certain situations. Let's say I wanted to conditionally set a variable to one of two negative constants. In the current syntax, that would look like:
x = -1 if y else -2
However, with your syntax, it would look like:
x = if y -1 else -2
Which could be possibly confused with trying to subtract 1 from y.
I've been doing Python professionally for 13 years (among other languages, but Python is in the one I've used more). I like the language but I also hate some things (mainly, the GIL, the lack of static typing now lessened thanks to mypy and the slowness), but I never tough of the ternary if as evil or found a single instance where it confused me.
I like Java, Javascript, Go (which holds a special place in my heart), and C++ even. And I'm being a bit hyperbolic -- I don't hate Python, but sometimes I feel that the language works against you, and not with you.
You like JavaScript, the language where parenthesizing an expression can change the value of that expression[1]:
> {} + []
<- 0
> ({} + [])
<- "[object Object]"
or how the identity x|0 == x doesn't hold for all "integers"[2]:
> 4294967296 | 0
<- 0
or how function definitions aren't grammatically statements, making this[3]:
function foo() {
function bar() {} // legal
if(1) {
function baz() {} // illegal!
}
}
this just always bites me:
> "a string" instanceof String
<- false
(but seriously, I don't understand why JavaScript needed the object/primitive distinction. I get it for Java, but not in JS.)
> sometimes I feel that the language works against you, and not with you.
JavaScript is the language where you ask it, "here, I want to know which of these guns most adequately shoots my foot."
ES6 improves things considerably; in 10 years we'll have good browser support for basic data structures, like Map and Set.
[1]: I'm joking. Read it first, then return to this footnote. The sleight of hand is this: the first expression parses as an empty code block, followed by a unary plus taking an empty list. The second parses as binary addition. There's also a terrible amount of coercion taking place. This also "means" that binary addition "isn't" commutative: {}+[] and []+{} evaluate to different values; the same trickery is involved.
[2]: the binary or operator, |, silently converts the operands to a signed 32bit integer for the duration of the expression. Note that signed 32bit integer is a type not available to the JavaScript programmer — it only arises internally! (The only numerical type available is "Number", which is an IEEE double.)
[3]: the grammar for functions is essentially "list of statements or function definitions", so function definitions are only valid at the "top level" of a function.
True, JS is a pain in the butt. But I think it's fun: it feels fluid and that you can do almost "anything" with it (for better or worse). In Python, I feel that I'm fighting against arbitrary goalposts or that I'm wracking my brain parsing some logical puzzle (like in the PEP example).
And don't get me started with Java's verbosity. Half the time Java seems completely unusable without an auto-completing IDE.
And in all honesty I am a big Python fan, but only because it seems like the lesser of all evils in programming. There are some great designs out there, but Python is that toolkit that just seems to be pragmatic enough to be useful and idealistic enough (hello, significant whitespace) to encourage expressive algorithm design.
I started taking Scala jobs a while back and I don't think I'd every want to go back to a Java shop.
That being said, some things in Scala are difficult to wrap your head around, and anything made by TypeSafe/Lightbend should die in a fire, but overall I still enjoy coding in it.
Substitute {{x}} for Python and you have a template for a Hitchhiker's Guide entry for just about every programming language, ever.