• huppenzuppen (unregistered)

    This form of ternaries is not unique to Python, VHDL has a similar construct

  • LZ79LRU (unregistered)

    The language was named after the Monty Python Flying Circus who specialize in satirical WTF comedy. Is it really that surprising when the code produced in it resembles the same?

  • Foo AKA Fooo (unregistered)

    "Each rule is identified by a number, which does a wonderful job making the code more cryptic. Are there at least 241 rules, or is the numbering sparse? What does rule 241 represent? Absolutely no idea."

    Every good Ferengi knows the rules and numbers by heart (https://memory-beta.fandom.com/wiki/Ferengi_Rules_of_Acquisition). Hmm, number 241 is missing on that page, but now we see it must be about illusion of choice in marketing or such.

  • some guy (unregistered)

    So python thinks that the true condition is the more likely one? I prefer clarity of the condition over having to guess which outcome is more likely such that I can formulate the condition accordingly.

    It could also be that the getcall has a side effect, but that leads to more WTFery.

    Final nitpick: why is this not "Representative Line"?

  • NoLand (unregistered)

    This has already been a feature in Algol. (Compare the Revised Report on the Algorithmic Language Algol 60, 4.5 Conditional statements.)

  • NoLand (unregistered)

    Correction: it's actually defined in "3.3. Arithmetic expressions", which may include a Boolean expression.

    And, if you thought Python was bad, have a look at this Algol beauty provided in the Revised Report ("3.4. Boolean expressions"):

    if if if a then b else c then d else f then g else h < k
    

    (Any if-else pair evaluates to a value, and you can nest them, as deep as you want.)

  • (nodebb)

    There's one advantage to the current code: letting get raise (throw) an error (exception). If "dead code removal" were applied here to collapse the ternary to False, the error would never get raised. Of course, no programmer should be expecting that error that would prevent dead code removal, but this website never fails to prove us wrong.

  • (nodebb)

    Pythonic way is synonymous with "write semi-crazy crap on purpose".

    No thanks.

  • (nodebb) in reply to NoLand

    Fun fact: there exists a language with "no" reserved words: IBM's PL/I from the mid-60s. The idea was that new keywords could be added to the language in the future without invalidating old programs. This has the "neat" benefit that this monstrosity is valid syntax (correct me if the ELIF/ENDIF keywords are wrong):

    IF IF = THEN THEN THEN = ELIF ELIF ELIF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIF
    

    That should be read as (in C# where the "at sign" allows using keywords as variable names):

    int @if, then, elif, @else, endif;
    ...
    if (@if == then)
        then = elif;
    else if (elif == then)
        then = @else;
    else
        @else = endif;
    
  • (nodebb)

    *fumes in one-way-to-do-it*

    Python's ternary expression is one of the weird choices of python that keep me annoyed over what is otherwise my least disliked language for actually getting things done.

    Throughout python, you usually have a pattern of “evaluate in order written”. You have

    if A:
        B1
    else:
        B2
    

    or with loops

    for item in A:
        B(item)
    

    i.e. A is evaluated before B. But when you go to expressions, it is suddenly reversed.

    out = B1 if A else B2
    out = [B(item) for item in A]
    

    This reversal becomes even more obnoxious, when you want to do more complex things.

    >>> for i in "AB": 
    ...     for j in "12": 
    ...         print(i+j, end=" ")
    A1 A2 B1 B2
    

    so far clear, but what if you use the same order in a generator or list expression?

    >>> print(" ".join(i+j for i in "AB" for j in "12"))
    A1 A2 B1 B2
    

    Okay, nice so far. And what, if it is a nested list?

    >>> print([[ i+j for i in "AB" ] for j in "12" ])
    [['A1', 'B1'], ['A2', 'B2']]
    

    Due to this, the "flat" generator expression is always trial-and-error for me.

    Additionally, it undermines intellisense features of IDEs; In loop expressions you write the item before the source, so by the time you have written

    out = [i
    

    the IDE cannot yet decide what type i will be.

    I would much have preferred if Python had opted for expressions of the form

    out = if A then B1 else B2
    out = for item in A collect B(item)
    

    but history is strange.

    I've recently also learned that (mostly for historical reasons, see PEP 285 – Adding a bool type (Python 2.3)) Python draws a surprisingly fuzzy line between booleans and integers.

  • NoLand (unregistered) in reply to colejohnson66

    I guess, this is one of the Wittgensteinian influences on PL/I. (The Vienna group did engage in some Wittgenstein studies, and this is pretty much a language game, AKA Sprachspiel, where meaning is derived from use.)

  • (nodebb) in reply to colejohnson66

    Fun fact: there exists a language with "no" reserved words:

    There is another one. The following is valid Fortran:

    program program
        integer :: if = 1
        integer :: else = 2
        if(if == 1) then
           print *, if
        else
           print *, else
        end if
    end program program
    

    In Fortran, keywords are not keywords, unless they are in places, where they are expected. Otherwise they just ‘degrade’ to an identifier. Curiously though, this is not entirely consistent; The following would be incorrect syntax:

    program program
        integer if = 1
        integer else = 2
        if(if == 1) then
           print *, if
        else
           print *, else
        end if
    
    end program program
    

    with error messages from as

    ! Intel
    a.f90(4): error #5082: Syntax error, found IDENTIFIER 'IF' when expecting one of: ( : % [ . = =>
            integer if = 1
    ----------------^
    ! Gfortran
    a.f90:4:18:
    
             integer if = 1
                      1
    Error: Syntax error in data declaration at (1)
    

    Addendum 2023-05-04 07:51: ~~from as~~ such as

  • (nodebb) in reply to R3D3

    Oh, don't forget about Smalltalk. Theoretically it doesn't have any reserved words; practically the compiler optimizes "nil" and "==", but the rest can be redefined. Even true and false are just global constants.

    So you can write: true become: false

    ... and your whole system will stop working immediately.

  • (nodebb) in reply to Melissa U

    Lisp is the same way. 1 is not really the integer 1, it's a function that returns the integer 1. And Lisp lets you redefine functions.

  • (nodebb)
    for example, you don't use switch statements in Python, you put callable functions in a dict instead.

    I've been writing Python for quite a while now, and I've never seen anyone do this.

  • Smalltalkers do: [:it | All with: Class, (And love: it)] (unregistered) in reply to Melissa U

    There are a few keywords in the loose sense R3D3 describes:

    self

    super

    currentContext

    more surprising to me, when I started, was the fact that you can even re-define elements which are often regarded syntax: +,-,*,/,<,<< and many more, yes even "==". Most compilers implement some heuristics when to bypass the optimizations.

    Some things you cannot override without patching the compiler are:

    :=,;,[,],#

    But even patching the complier would not be out-of-the question. It was done for Squeak upon my request for "isNil" to be optimized the same way "ifTrue" was, i.e. performing the same as "== nil".

  • Gubbo (unregistered) in reply to Dragnslcr

    And there's 'match' now? Which is surely just admitting defeat and adding a switch case but pretending it's something else

  • LZ79LRU (unregistered) in reply to Dragnslcr

    I have. And a lot of other things. This includes an unfortunate developer who got squished by a giant foot.

  • Barry Margolin (github) in reply to Dragnslcr

    1 is not really the integer 1, it's a function that returns the integer 1.

    Not in any Lisp dialect I've ever used (MacLisp, Common Lisp, Scheme, Emacs Lisp to name a few).

    Functions are named by symbols. While it's possible to create a symbol whose name is a number, you have to use escaping to refer to it, by writing either |1| or \1. If you just write 1 by itself, it's parsed as an integer and you can't use it as a function name.

    (defun 1 (x) (print x))
    

    is not valid code.

  • Barry Margolin (github) in reply to colejohnson66

    There's one advantage to the current code: letting get raise (throw) an error (exception).

    get() doesn't raise an exception when the key isn't found, it returns a default value (the second argument, which defaults to None). The exception is raised when you use subscript notation, rules[241].

    The author of the code made a classic beginner WTF, writing something != X or Y when they mean something not in (X ,Y)

  • (nodebb) in reply to LZ79LRU

    Well, sure, stupid programmers do stupid things. That still doesn't make it the thing you're supposed to do.

    I definitely agree that the order in Python's ternary is weird, though.

  • efahl (unregistered)

    The important thing here is that "get" is an overloaded method on the "rules" object, which when presented with "241" causes a database restart.

  • Davidmh (unregistered)

    Python does have a switch statement since 3.10, it is called match case.

    And the example you showed? I don't think anyone would ever think of that as pythonic.

  • WTFGuy (unregistered)

    Ref

    So python thinks that the true condition is the more likely one? I prefer clarity of the condition over having to guess which outcome is more likely such that I can formulate the condition accordingly.

    What does likelihood of being true or false have to do with the structure of the statement? Construct your condition for clarity and put the true and false results in the syntactically required locations. Anything else is paying foolish attention to low-level concerns from within a high level language. Don't do that.

  • matt (unregistered)

    Along the lines of code smells, can we talk about punctuation smells? In many, many articles on this site, including multiple times in this one, there is poor usage of what should be a dash, and it limps along with limited legibility but it stinks. (As with many bad coding practices, I used to be guilty of it too, and then I graduated high school.) And there's a better, more legible, traditional way (plus a few better compromises). It makes sense that this site and its authors should want to learn it. Reasonable analogy, huh?

    A single hyphen after a word and then a space is not a dash. It looks silly and unbalanced and it does not help guide the reader. (A single hyphen with no spaces is even worse, of course, but you don't seem to do that much.) For the usage typical to these articles, setting off a parenthetical remark or abruptly switching topic, an em dash (—) with no spaces, or in typesetting very thin spaces, is best. There's an HTML entity for it, too. But if one wants to stay within a basic character set, a -- (usually without spaces), or even a - spaced on both sides, will do, and will be much more readable. Sometimes, the half-spaced hyphens seen here would work better as colons, with the same spacing, usually after a phrase introducing a longer explanation.

  • some guy (unregistered) in reply to WTFGuy

    My point being, if a human reads the python line, they would probably assume that the true case is the most important bit. Then they read the condition and have to rearrange their mental model to patch in the condition.

    But of course, real python programmers do not have that problem. /s

  • (nodebb)

    | (correct me if the ELIF/ENDIF keywords are wrong):

    Here is the syntactically correct PL/I code that corresponds to the C code exhibited:

    DECLARE (IF, THEN, ELIF, ELSE, ENDIF) BINARY FIXED(31); IF IF = THEN THEN THEN = ELIF; ELSE IF ELIF = THEN THEN THEN = ELSE; ELSE ELSE = ENDIF;

  • xtal256 (unregistered) in reply to Melissa U

    Yes and it uses the sane ordering of condition and values, so that the expression would be written:

    welcome_mt := (rules get: 241) ~= 'yes' ifTrue: [ false ] ifFalse: [ false ]

    and it would be immediately obvious how stupid and redundant it is.

    Smalltalk is the only language I've seen that doesn't do things in an ass-backwards way, and yet no one uses it.

  • löchlein deluxe (unregistered) in reply to Dragnslcr

    Well, I've done that, but with the experience of a Senile Software Engineer, I now think of it as a lazy ad-hoc command factory pattern and not a kludge for a missing switch statement.

  • Nick (unregistered)

    The real WTF here is the assumption that just because the rule ID happens to contain digit, all rules IDs are numbers.

    Clearly, this is a shopping cart system, and this rule is the “2 items for the price of 1” rule, aka “2-4-1”, or just “241” to avoid looking like maths.

    Other rules in the system probably include “BOGOF”, “342” and “1/2OFF”

  • LZ79LRU (unregistered) in reply to Dragnslcr

    Python is way more problematic than most languages though. I mean, look at its main "feature", significant whitespace. It is literally a violation of the principal of separation of concerns built into the language.

  • (nodebb)

    Python's ternary is not the real WTF.

    The Real WTF is that Python's ternary is a NEW thing since version 3. Before, the author had flatly refused to add one, which led folks to write this Pythonic monstrous workaround:

    a and b or c

    Which reads as (a ? b : c). And a lot of subtle "hilarity ensues" edge cases. Ironically this thing has the right order.

  • (nodebb) in reply to Mr. TA

    This is a very narrow view of python. Have you actually done any working in the language of python? Here is a non-pythonic way to write a loop.

    for i in range(10): print(i)

    Here is the same loop, but in a pythonic way.

    print([i for i in range(10)])

    Clearly we are all opinionated python programmers.

  • (nodebb) in reply to LZ79LRU

    Sorry, but the spacing is making it easier to reading.

  • seebs (unregistered)

    I suspect this is a misunderstanding on the writer's part of how "or" works.

    I think they intended "if [expr] != 'yes' or None" to mean "if expr is not either 'yes' or None".

    So they were thinking "if [expr] != ('yes' or None)", unaware that Python would read this as "(expr != yes) or None".

  • LZ79LRU (unregistered) in reply to Nagesh

    No it does not. What forced spacing does is force you to write programs that are completely unreadable. Anyone who has had to work with actual real production code and not quick scripts and hobby code will attest to that.

    In order for code to be readable and understandable thew engineer writing it needs to be able to insert whitespace, new lines, indentation and other forms of formatting freely so as to create a properly flowing and visually comprehensible form.

    Sadly I can't give you examples as this place does not allow code formatting.

  • (nodebb) in reply to Nagesh

    Here is a non-pythonic way to write a loop.

    for i in range(10): print(i)
    

    Here is the same loop, but in a pythonic way.

    print([i for i in range(10)])
    

    Clearly we are all opinionated python programmers.

    Uhm... what? Those are two different loops with different outputs, and both look perfectly pythonic to me.

    Sorry, but the spacing is making it easier to reading.

    Code formatters fulfill the same job though. Just because there are beginners and (for whatever reason) lecturers that do bad code formatting doesn't make it a good idea to make it compulsory.

    It's also not like it is a complete solution. Python prescribes indenting for block syntax, which essentially only solves the

    if(COND) {
        THEN
    }
    

    vs

    if(COND)
    {
        THEN
    }
    

    choice. I haven't really seen anything else in the wild, except as a joke or outlier. But just as you could quarrel about {\n vs \n{, you can quarrel about whether to put an empty line at the end of a block (to make it more visually distinct) or not, and when.

    It also doesn't do anything to help with formatting of multi-line expressions (e.g. nested function calls or (nested) list/dict literals, or just long expressions/statements.

    It also sabotages some useful syntax. For instance, an equivalent of Javascript's

    const selected_items = getAllItems().filter(e => /* ...*/).map(e => /* ...*/)
    

    is more limited, because it doesn't allow for fully functional code blocks, but only for lambda expressions, where neither raising exceptions nor logging statements work well. Instead you have to do something more awkward like

    def some_function_1(e):
        ...
    def some_function_2(e):
        ...
    selected_items = get_all_items().filter(some_function_1).map(some_function_2)
    

    That assumes having some sort of "piping" library involved though. With pure python it would be <!-- to be continued -->

  • (nodebb)
    selected_items = map(
        some_function_2, 
        filter(
            some_function_1, 
            get_all_items(),
        )
    )
    

    which I consider significantly less clear, even after years of using Python. Note that my JavaScript expertise is rather basic, I've never used it nearly on the scale I've used Python.

    Additionally, this dichotomy of lambda vs makes refactoring often more painful then necessary, when you have to switch from a lambda expression to a named (local?) function, which often requires rewriting the structure of the code, rather than just the implementation details. Also makes for less clear diffs.

    Speaking of diffs, another point where significant whitespace can be a pain :/

  • LZ79LRU (unregistered)

    And of course there is the simple issue of long lines of code.

    We all know that expressive function and variable names which are the cornerstone of all good self documenting code. You know, AbstractWidgetMakerFactory.MakeWidgetMaker().MakeWidget(widgetTemplateName). That sort of stuff.

    Try writing code like that without being able to break it into lines at will for readability and see where that takes you.

    Furthermore even without good naming practices just regular code can, if you are unable to freely format it pose a serous usability issue to a lot of people. I know plenty of developers, who thanks to spending years or decades in front of a monitor (who would have imagined) have less than perfect eyesight. These people need to zoom their font size up to 12, 14 or some times even more in order to read.

    And for them the ability to freely split code into lines freely is the difference between being forced to constantly scroll sideways and being able to work freely.

    Pythons use of whitespace as a syntactic element in its grammar is the programming equivalent of one of those training walkers you can buy for babies. Great if you are just learning to walk. But constraining and painful if you want to actually get anywhere.

  • CWiz (unregistered)

    I have seen some C programmers write python code returning 0 or -1 from functions and effectively don't test the return value either. So we don't know whether the function succeeded or failed in what it originally meant to do.

    Another gem

    hit = False
    def func():
        if name.find("something.exe") != -1 or cmdline.startswith("something ") or cmdline.find("something.exe")!=-1: hit=True
    
  • Alessandro (unregistered) in reply to colejohnson66

    My God: I actually programmed in PL/1 on IBM Series 60. How old am I?!? I didn't know about this feature. I remember the 20 minutes to compile 100 lines of code, the keyboard's relay and its loud "click", and the velociraptors biting my feet...

Leave a comment on “Python's Tern”

Log In or post as a guest

Replying to comment #:

« Return to Article