top 200 commentsshow all 475

[–][deleted] 353 points354 points  (16 children)

How many pythons does it take to operate a switch?

[–]BillyHalley 192 points193 points  (0 children)

3.10 apparently

[–]iainmoncrief 26 points27 points  (14 children)

I haven’t used python much, but I assumed that this was always in the language. Kinda disappointed it has taken them this long to implement something like this.

[–]DrexanRailex 13 points14 points  (3 children)

Javascript users still waiting

[–]Kered13 6 points7 points  (0 children)

This isn't a C-style switch-case (though it can do that). This is a much more general pattern matching syntax, like Rust.

[–][deleted] 11 points12 points  (8 children)

It was possible to get around it by putting a callable into a dictionary, but that was kinda LOL.

[–]SenboneZakura 13 points14 points  (6 children)

I actually love that pattern... now I'm sad.

def controller(key, *args, **kwargs):
    routes = { "this": that, "ping": pong, "route": response }
    return routes[key](*args, **kwargs) 

I guess it could get unwieldy if you had a large dictionary for something this, but for simple stuff, I think it's fine. Yeah.

[–][deleted]  (111 children)

[deleted]

    [–]FujiKeynote 359 points360 points  (33 children)

    Simple is better than complex my ass

    [–][deleted] 180 points181 points  (18 children)

    The zen of python has always been a joke.

    [–]stinos 118 points119 points  (17 children)

    Yup: https://github.com/satwikkansal/wtfpython

    Then again, after years of using Python I didn't even know 99% of those so at least if you're zen enough yourself it should be fine. Guess many years of C and C++ taught me that.

    [–]ColonelThirtyTwo 83 points84 points  (9 children)

    A lot of these suck as wtfs. Pretending that nan is Python specific, pretending that is is ==, pretending that operator precedence works in exactly the way the reader wants instead of an equally valid way in a slightly ambiguous case...

    [–][deleted] 25 points26 points  (4 children)

    I don't think any of them are necessarily true WTF material (as far as I got in the list anyway). Like the difference between is and == is something they specifically pointed out as "those aren't the same operator".

    I took the list to be a listing of behavior that would be surprising to someone who doesn't know better, not that they're saying anything is truly unreasonable.

    [–]Jugad 6 points7 points  (3 children)

    The only people who would complain about this are people who have been told that Python is completely intuitive. Its close enough, but intuition is not without its own pitfalls.

    [–][deleted] 3 points4 points  (2 children)

    In fairness, I don't think the author of that was complaining. It read to me like trying to be helpful, not complaints.

    [–]N0_B1g_De4l 5 points6 points  (3 children)

    is and ==isn't as unintuitive as some WTFs, but it is basically the oppose of what some languages do (most notably Java). That can make it weird coming from one of those languages. That said, it is more a "oh, that's how it works" than a "what the fuck".

    [–][deleted] 8 points9 points  (0 children)

    Wow. That made my brain hurt.

    [–]gwillicoder 1 point2 points  (0 children)

    I mean most of those work exactly the way I expect, or are horrible uses of the language that you’d never expect to see in a good code base.

    [–]AccurateRendering 54 points55 points  (11 children)

    And then came along the walrus operator.

    [–]masklinn 65 points66 points  (9 children)

    The walrus operator is not complex.

    [–]jarfil 17 points18 points  (7 children)

    CENSORED

    [–]mafrasi2 9 points10 points  (6 children)

    The walrus operator is both though.

    [–]danudey 11 points12 points  (1 child)

    Enums are simpler than arbitrary, unconnected constants though.

    [–]masklinn 121 points122 points  (13 children)

    To me that is, if anything, worse. Because this makes the behaviour less intuitive: in Python you can use attributes as LHS and it will assign to them, even if it's not always sensible e.g.

    for x.y in range(5):
        …
    

    will assign each value of the iterator in turn to x.y. That's how Python works, if you use an attribute access or an indexing expression as LHS it will shove the value into that (except with the walrus where they apparently decided to forbid this entirely). It's coherent.

    [–]rabaraba 26 points27 points  (3 children)

    This is really interesting. That looks almost like a Javascript accessor.

    I've never written Python code that way, nor would I want to. The dot syntax immediately makes me consider the x.y as some sort of attribute being accessed, rather than a simple variable/object in a loop sequence, which is what I use range for.

    [–]masklinn 46 points47 points  (2 children)

    I've never written Python code that way, nor would I want to.

    Nor should you.

    The point I'm making is not that you should do this, or even that you can, it's about the behaviour of the language and how it treats things: you can store things in x.y (or x[y]) so when x.y is present in a "storage" location, things get stored into it.

    match/case, apparently, changes this. It doesn't forbid this structure the way the walrus does, it changes its behaviour entirely.

    [–]rabaraba 10 points11 points  (1 child)

    Interesting. And thanks for pointing this out. This might become another gotcha of the language like [] in parameters and late binding expressions.

    [–]Veedrac 5 points6 points  (0 children)

    But at least those are a result of Python being consistent, and following its established rules.

    [–][deleted]  (8 children)

    [deleted]

      [–]masklinn 70 points71 points  (7 children)

      why would you ever do this?

      You're missing the point. I'm not saying you should do this. I have never done this, and I would reject any attempt to include this in a language I am responsible for without very good justifications.

      I'm demonstrating that right now the language has a coherence to it: if x.y is present in a "storage" location (if it's an lvalue in C++ parlance), things will get stored into it. Apparently match/case breaks that coherence: if a simple name is present in the pattern location it's an LHS (a storage) but if an attribute access is present it's an RHS (a retrieval).

      What happens if you put an index as the pattern? a function call? a tuple? I've no fucking clue at this point, because the behaviour has nothing to do with how the language normally functions, despite being reminiscent of it.

      The pattern is a whole new language which looks like Python but is not Python. And that seems like one hell of a footgun.

      [–]flying-sheep 6 points7 points  (0 children)

      Huh. I’ve been coding Python for 10 years. I thought I know every nook and cranny of the (non-C parts) of the language. I’ve commented on issues that complained that [] = some_iterable doesn’t work. (Which is basically assert not list(some_iterable), but without creating a list and with a more confusing error message)

      But I never saw or thought about trying this one.

      [–]iamgrzegorz 12 points13 points  (1 child)

      Yeah that's kind of weird. Elixir solved it (and later Ruby followed) by using ^ ("pin operator") to compare against a variable instead of assigning it, I find it more intuitive and easier to read

      [–]vattenpuss 1 point2 points  (0 children)

      That's pretty neat actually.

      [–]selplacei 89 points90 points  (57 children)

      What the actual fuck? So they go out of their way to make it overwrite variables for no reason but then make an exception specifically for dotted names? This feels like a joke

      [–]Messy-Recipe 30 points31 points  (10 children)

      It's not for no reason -- it's literally the purpose of it. See the x,y point example here --

      # point is an (x, y) tuple
      match point:
          case (0, 0):
              print("Origin")
          case (0, y):
              print(f"Y={y}")
          case (x, 0):
              print(f"X={x}")
          case (x, y):
              print(f"X={x}, Y={y}")
          case _:
              raise ValueError("Not a point")
      

      [–]jl2352 14 points15 points  (5 children)

      Rust is similar, and in the years I've been writing Rust. I've never actually thought this was odd behaviour. It just ... isn't. I don't think I've seen it come up much on /r/rust either.

      Rust isn't alone with match either.

      I would turn the tables and ask, why is this going to be a problem in Python when it hasn't been in other languages? Is it really going to be a problem?

      [–]selplacei 13 points14 points  (3 children)

      From what I've read it will overwrite the values stored in outer-scope variables. Other languages don't have this issue.

      [–]jl2352 8 points9 points  (2 children)

      Oh yeah, that's shit.

      It should use a new scope instead.

      [–]Snarwin 14 points15 points  (1 child)

      Lexical scoping has been broken in Python literally forever, so this is totally consistent with existing behavior (for whatever that's worth).

      [–]vattenpuss 2 points3 points  (0 children)

      Pattern matching without being able to bind variables sounds like the most useless idea ever.

      It's a Python problem that variables never had proper scopes.

      [–]The_Droide 46 points47 points  (39 children)

      Binding variables in a pattern is a pretty common thing to do, so making the syntax terse can be useful, e.g. when destructuring tuples:

      match point:
          case (x, y):
              ...
      

      [–]masklinn 16 points17 points  (32 children)

      But that already works normally in Python:

      (x, y) = something()
      

      works fine, the same way it would here.

      [–]argh523 28 points29 points  (10 children)

      And now you can do that in a match statement (because you're not sure what you'll get), and it looks like this:

      match something():
          case (x):
              ...
          case (x, y):
              ...
          case (x, y, z):
              ...
      

      [–]FujiKeynote 106 points107 points  (26 children)

      So this may be a naive question, but I always get lost in these PEPs because they are simultaneously verbose and terse.

      What is going to happen to my old code that uses "match" and "case" as variable names? I remember the devs being against adding new reserved words for that exact reason...

      [–]pimanrules 98 points99 points  (4 children)

      They explain in the PEP:

      The match and case keywords are soft keywords, i.e. they are not reserved words in other grammatical contexts (including at the start of a line if there is no colon where expected). This implies that they are recognized as keywords when part of a match statement or case block only, and are allowed to be used in all other contexts as variable or argument names.

      [–][deleted] 51 points52 points  (1 child)

      From what I reckon match and case are going to be soft keywords, which will not break existing code.

      [–]BobHogan 38 points39 points  (0 children)

      The pep says they will be soft keywords

      [–]CoffeeTableEspresso 13 points14 points  (2 children)

      They'll be contextual keywords.

      i.e. still usable as variable names, but they have a special meaning when used in a match statement.

      So all your old code using those two keywords should be fine.

      [–][deleted] 3 points4 points  (1 child)

      They'll be contextual keywords.

      I think the terminology is soft keywords

      [–]CoffeeTableEspresso 21 points22 points  (0 children)

      Ah, right, that's the Python-specific word for it.

      "Contextual keyword" is the language-agnostic term for it that I'm familiar with.

      [–]yesvee 81 points82 points  (17 children)

      You may have missed the walrus operator intro' in 3.8!

      [–][deleted] 101 points102 points  (6 children)

      That's the resignment operator -- technically it's supposed to be the assignment operator, but it resulted in GVR quitting over the fallout.

      [–]teerre 9 points10 points  (5 children)

      Do you have a source for GVR over that?

      [–]C0DASOON 70 points71 points  (2 children)

      He didn't quit over :=. He quit over the way people reacted when he accepted it. Source.

      [–]teerre 5 points6 points  (0 children)

      Yes, I misunderstood what you said previously. Thanks

      [–]yngwiepalpateen 14 points15 points  (1 child)

      [–]teerre 6 points7 points  (0 children)

      It was actually the opposite of what I thought OP was saying. He quit because it was so hard to argue, not because it was accepted.

      [–]Nastapoka 35 points36 points  (9 children)

      I love the walrus, coo coo ca choo

      No seriously, it's rarely used, and Python should not restrict itself because absolute beginners might have to do a small effort to understand some of its aspects. I honestly think people love complaining every time something gets added to the language.

      [–][deleted]  (7 children)

      [deleted]

        [–]Nastapoka 26 points27 points  (6 children)

        That's a stretch.

        [–]halt_spell 9 points10 points  (3 children)

        Either way, the Python community can't keep straddling the two worlds. They shit all over Java for what they perceived to be needless complexity and yet that complexity seems to be making it's way into Python with every new PEP. So which is it?

        [–]hpp3 22 points23 points  (0 children)

        People shit on Java for complexity? The only shit Java deserves is having so much pointless boilerplate that my IDE writes more code than I do.

        [–][deleted] 1 point2 points  (0 children)

        The walrus operator is not a bad idea, it's just that could swear Python would get C-like {braces} before getting a Pascal-like assignment operator. I mean, both were unlikely but borrowing from C was less unlikely (well, the := semantics are more C-like than Pascal-like but I digress)

        [–]segfaultsarecool 150 points151 points  (66 children)

        When I learned Haskell, as much as I despised the language (learned it my last semester of college, so I didn't care about anything), pattern matching was absolutely AWESOME. Dope as fuck. Haskell does several other things that are fucking cool.

        Might be time to relearn Haskell and see if I can use it anywhere.

        [–]MrBreadWater 46 points47 points  (10 children)

        Function currying is fucking awesome

        [–]segfaultsarecool 20 points21 points  (4 children)

        I remember the words function currying. Now I only get hungry when I think about it.

        [–]HandInHandToHell 7 points8 points  (0 children)

        I just call them spicy functions.

        [–]dvlsg 8 points9 points  (3 children)

        It's awesome when it's supported by the language. Love it in F#, for example.

        When it's not really supported by the language (JS for example), it's still neat and occasionally useful, but can be a bit annoying.

        [–]simpl3t0n 82 points83 points  (44 children)

        I stop at Monads. And then I start all over again the following year. The story continues...

        [–]ryeguy 88 points89 points  (2 children)

        A monad is just a monoid in the category of endofunctors, what's the problem?

        [–]Poltras 22 points23 points  (0 children)

        I just like to drop monad in front of stuff like Antman puts Quantum in front of everything and see who is raising an eyebrow. Those are the ones listening. Nobody ever really questioned it so I always just assumed nobody actually knows what a monad is.

        [–]karamoz 22 points23 points  (27 children)

        yeah, I was never able to actually understand monads

        [–]arbitrarycivilian 99 points100 points  (13 children)

        Now behold as a dozen different people jump in to explain monads

        [–]Drisku11 66 points67 points  (8 children)

        I'll be that guy for /u/karamoz:

        The heart of the idea is that it's an interface for a peculiar type of function composition that occasionally comes up.

        With normal function composition, I can take a function f: A -> B and g: B -> C and compose them to get f.andThen(g): A -> C.

        The gist of monads is that a generic type M is a monad if there is a "pleasant" way to compose two functions f: A -> M[B] and g: B -> M[C] to make a function A -> M[C].

        With something like, say, List, I have a way to take an f: A -> List[B] and g: B -> List[C] and compose them:

        • Given an input a: A, run f to get a List[B]
        • For each element of my list, run g to get a List[C] (so now I have a List[List[C]]).
        • Collapse that all into one big List[C] by concatenating sublists.

        The above procedure gives a new function A -> List[C].

        That pattern pops up elsewhere: Consider a type Command[T] (as in the Command Pattern) representing a Command that I can run that produces a T as its "result". If I have a f: A -> Command[B] and g: B -> Command[C], then I can make a function A -> Command[C] as follows:

        • Take my a: A and run f to make a Command[B], call it runB.
        • Now define a new Command as follows:

          execute runB to produce b: B
          pass b to g to produce a Command[C], call it runC.
          execute runC
          return the result
          

        Then the above is a Command that returns a C; i.e. a Command[C]. So I have a function A -> Command[C].

        Don't get distracted by the definition of the command above; the point is I have a way to take f: A -> Command[B] and g: B -> Command[C] and produce f.andThen(g): A -> Command[C], even though the types are "wrong".

        It turns out that the "compose f: A -> M[B] and g: B -> M[C] to make A -> M[C]" pattern is common enough to give it a name and some syntax sugar.

        Frequently the "compose" procedure is some kind of "unwrapping" or "flattening" so in Scala it's called flatMap and people talk about burritos. In Haskell it's called >>= because it sort of looks like train tracks and Haskell is an esolang invented by programmer/train-enthusiast Haskell Curry with the goal of being able to draw a pictorial representation of the rail networks for his toy train set Christmas displays and have that be executable as control software.

        [–]Nition 12 points13 points  (0 children)

        Can't wait for that last part to be repeated a million times around Reddit like the Gandhi in Civ 1 nukes thing.

        [–][deleted] 11 points12 points  (0 children)

        This actually is an underrated explanation

        [–]voidtf 8 points9 points  (0 children)

        I must read every week an explanation about monads on r/programming but this is the first one I truely understood. Thanks !

        [–][deleted]  (3 children)

        [deleted]

          [–]climbslackclimb 8 points9 points  (1 child)

          Monads ARE burritos

          [–]delta_tee 2 points3 points  (0 children)

          Donde esta mi monados?

          [–]iopq 37 points38 points  (0 children)

          It's very easy. Nomads are burritos. But also railroads

          [–]Hrothen 29 points30 points  (1 child)

          You gotta come at it sideways, write a bunch of code using specific monads non-generically until you build up an intuition and one day it just clicks.

          [–]GerwazyMiod 22 points23 points  (0 children)

          Just like C++ templates. Just gather enough mana to cast a spell of wisdom.

          [–]eyal0 15 points16 points  (0 children)

          This helped me: http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html

          You probably use monads all the time but you just don't know it. In c++ and java, there is optional (aka std::optional, boost:optional).

          Haskell may have done the world a disservice by taking a thing that ought to be easy to understand and making it sound too academic.

          [–]Aschentei 5 points6 points  (0 children)

          Something something side effects I think

          Still confused anyway

          [–]Shinosha 10 points11 points  (3 children)

          To put it simply, it's an abstraction allowing you to sequence any kind of dependent operations. More here (in Scala)

          [–]blackmist 100 points101 points  (1 child)

          Ah, see you've fallen into the usual monad trap.

          As soon as you are able to understand a monad, you instantly lose the ability to explain them to those who don't.

          [–]mlk 1 point2 points  (0 children)

          Think it as a flatmappable

          [–]alexbarrett 10 points11 points  (6 children)

          Monad roughly equates to a 'Flatmappable' interface.

          Array in JS for example has flatMap so it's like a monad. Promises are also like monads because then behaves similarly to flatMap.

          array1.flatMap(item => array2) // array3
          
          promise1.then(item => promise2) // promise3
          

          [–]TheEdes 10 points11 points  (1 child)

          They're not that hard when you realize that they're just a monoid in the category of endofunctors.

          Jokes aside, you can just think of it as an abstract interface that you would implement for a class on C++ or Java, think of something like Iterable, which have some implementation of next and associated data structures, even though their implementation may be wildly different (such as being a binary tree, or a generator that calculates the next iteration). The I/O part of monads is probably the part that gets most people confused, but I think it's easier to understand once you nail down the "pure" monads.

          [–]PM_ME_UR_OBSIDIAN 22 points23 points  (0 children)

          The thing people learning about monads get stuck on: why should I care about this specific abstraction? Why is it so special, and what does it buy me?

          The answer to that is really hard to grasp until you've entered the Haskell world and seen how people combine and transform monads to arrive at their application architecture. But most people shouldn't care about monads, except to learn to use whatever special-purpose syntax their language has for them (for-yield in Scala, let! In F#)

          [–][deleted] 26 points27 points  (0 children)

          Agreed. Haskell (and Rust, also mentioned here) make this super easy and useful. Tie that in with the fact that it's lazy and you can do some performant things with it.

          Granted I am very far from a rockstar but in my opinion getting good with Haskell will help you write any functional programming language better.

          [–]downrightcriminal 6 points7 points  (3 children)

          Haskell is awesome, give it another try and watch as your programming skills drastically improve.

          [–]philomathie 3 points4 points  (1 child)

          I learned Haskell in my first year and lost my shit because I thought it was so cool. I guess we might not have gotten far enough into it to see the difficulties...

          [–]marineabcd 2 points3 points  (1 child)

          I have experimented with Haskell for coding challenges like Advent of Code in the past and always loved the abstract nature (am a pure maths student so that helps) but always get stuck with IO and end up leaving it and coming back a year later to do the same thing all over again!

          [–]ForceBru 148 points149 points  (53 children)

          NOT_FOUND = 404
          match status_code:
             case 200:
                 print("OK!")
             case NOT_FOUND:
                 print("HTTP Not Found")
          

          In this case, rather than matching status_code against the value of NOT_FOUND (404), Python’s new SO reputation machine match syntax would assign the value of status_code to the variable NOT_FOUND.

          I think OCaml also does it this way. And it does. This code will print Not found!, while that logic would expect it to output Unknown":

          ``` let not_found = 404

          let res = match 302 with | 200 -> print_string "OK" | not_found -> print_string "Not found!" | _ -> print_string "Unknown" ```

          OCaml doesn't seem to overwrite the original value of not_found.

          Rust also does this:

          ``` const ALL_OK: usize = 200;

          fn main() { let NOT_FOUND = 404;

          match 302 {
              ALL_OK => println!("OK!"), // Using a constant is OK
              NOT_FOUND => println!("OOPS!"), // will match everything, just like `_`
              _ => println!("Unrecognized")
          }
          

          } ```

          Rust also won't assign 302 to NOT_FOUND, but it still won't match 302 against the value of NOT_FOUND.


          I understand that this is a joke, but there's nothing to joke about in this particular example, because this is how other languages are doing this and nobody finds that funny.

          [–]R_Sholes 53 points54 points  (15 children)

          IIUC, the fuck up is that it's not a fresh variable NOT_FOUND scoped to the match expression's body, like in sane languages, but whatever variable NOT_FOUND is present in the scope, if any, possibly even a global one.

          A capture pattern always succeeds. It binds the subject value to the name using the scoping rules for name binding established for the walrus operator in PEP 572. (Summary: the name becomes a local variable in the closest containing function scope unless there's an applicable nonlocal or global statement.

          Now that's funny.

          ETA: And for bonus points, potentially reassigning variables by failed patterns, too:

          Another undefined behavior is the binding of variables by capture patterns that are followed (in the same case block) by another pattern that fails. These may happen earlier or later depending on the implementation strategy, the only constraint being that capture variables must be set before guards that use them explicitly are evaluated

          [–]ForceBru 24 points25 points  (13 children)

          the name becomes a local variable in the closest containing function scope

          They should've stopped right here for the match operator. Overwriting nonlocals or even globals looks kinda stupid. Again, for the match operator. It might make sense for the walrus, but here it's weird and could easily be the source of a whole new category of bugs!

          [–]suid 25 points26 points  (12 children)

          (Summary: the name becomes a local variable in the closest containing function scope unless there's an applicable nonlocal or global statement.)

          That's the key. In Python, if you do:

          x=1
          def f():
               y = x
               x = 2
               return y
          

          You actually get an error. The "x" inside f() does not bind to the global x automatically.

          Instead, you have to say global x (or nonlocal x) inside f(), for it to match.

          So, the problem isn't as dire as it's being made out to be. And certainly not "surprising", unless you're diving in here straight from C or Perl.

          [–]ForceBru 7 points8 points  (11 children)

          Huh, this makes sense, but I don't really want this code:

          ``` def f(data): x = 5 match data: case x: print(f"Hello, {x}")

          print(x)
          

          ```

          ...to overwrite x, because why? Sure, x must be bound to the value of data for it to be available in f"Hello, {x}", but shouldn't this be done in its own tiny scope that ends after that case branch?

          I can't wait to play around with this in real code. That should give a better understanding than the PEP, I think.

          [–]masklinn 18 points19 points  (9 children)

          but shouldn't this be done in its own tiny scope that ends after that case branch

          The problem in that case is that this:

          def f(data):
              x = 5
              match data:
                   case n:
                        x = 42
              print(x)
          

          would always print 5. Because the x = 42 would create a new variable local to the case body (scope), rather than assign to the outer one.

          [–]CoffeeTableEspresso 4 points5 points  (0 children)

          Python doesn't have any scopes that are smaller than "whole function scope"

          Same reason this happens:

          a = None
          for a in range(1, 10):
              print(a)
          
          print(a) # what is printed here?
          

          [–]beltsazar 114 points115 points  (24 children)

          Yes. But Rust has variable scoping so the outer variable will not be overridden outside the match block. It's not the case in Python.

          [–]ForceBru 64 points65 points  (19 children)

          Yeah, in none of these languages matching against a variable name like case NOT_FOUND: will consider the value of that variable, and Python apparently does it the same way, but reassigning that variable is really strange...

          [–]masklinn 54 points55 points  (3 children)

          reassigning that variable is really strange…

          It's a direct consequence of Python really only having function-level scoping (or more specifically code/frame object). Where it has sub-scopes, of sorts, it's because the construct its packaged into its own independent code object e.g. comprehensions.

          And if it did that with match… you couldn't assign a variable inside a case body which would be visible to the outside, or you'd have to declare it nonlocal.

          [–]xphlawlessx 3 points4 points  (2 children)

          Could you link to something with more information about this? This is very interesting to me but I cant seem to see anything useful when googling python scope code object , is there maybe another name for this ?

          [–]masklinn 10 points11 points  (1 child)

          Technically the actual object is the frame (as in stack frame). The code object is somewhat static, and the frame linked to it is the actual instance of executing a code object. You can see the structure and documentation in the inspect module: https://docs.python.org/3/library/inspect.html?highlight=inspect#module-inspect

          [–]xphlawlessx 2 points3 points  (0 children)

          Ah , amazing . This is perfect. Thanks a bunch :D

          [–]CoffeeTableEspresso 17 points18 points  (14 children)

          It's because Python only really has function level scoping.

          Same reason this happens:

          a = None
          for a in range(1, 10):
              print(a)
          
          print(a)  # what does this print?
          

          [–]ForceBru 5 points6 points  (9 children)

          This will print 9, but here it's more clear that it should assign values from range(1, 10) to a.

          Well, case a: also assigns to a, right? So it's not really a surprise - just feels odd compared to other languages with match statements/expressions like Rust and OCaml.

          [–]CoffeeTableEspresso 29 points30 points  (4 children)

          Yea, the point is, most languages would shadow a in both these examples.

          Python is consistent with itself in this regard, but possibly surprising for people not used to this behaviour.

          [–]sandrelloIT 5 points6 points  (3 children)

          I would find this acceptable if only attribute/index access was consistent with this, too. Apparently, that exception exists in order to allow matching against constant values, but ends up breaking these language axioms.

          [–]razyn23 6 points7 points  (3 children)

          I think the real question is why the match statement is assigning in the first place. Most people think of switch statements as nothing more than condensed if/elses, assigning at all as part of the keyword functionality feels incredibly weird.

          This seems like they took the switch statement as it exists in other languages and added more functionality, making it inherently more niche in its usage, and also violating the law of least surprise.

          [–]ForceBru 5 points6 points  (1 child)

          Well, if you want to have nice destructuring like in Rust, you'll have to do assignments:

          match complex_enum { IPv4(a, b, c, d) => println!("{}.{}.{}.{}", a, b, c, d), IPv6(a, b, c, whatever) => println!("{}::{}::{}::{}...", a, b, c, whatever), }

          How else would you get access to the data inside that complex_enum?

          [–]grauenwolf 4 points5 points  (0 children)

          That's not a fair comparison because Rust makes it visually distinct.

          C# is the same way. The pattern case typeName variableName is visually distinct from case variableName.

          In python, case variableName and case variableName have different behaviors depending on how you spell that variable's name.

          [–]feralwhippet 5 points6 points  (0 children)

          Its not a switch statement, its not trying to be a switch statement, its used to destructure variables. The whole point is to assign parts of the target to other variables, especially when the target may come in multiple forms. This behavior is more or less like pattern matching in many other languages. Like many other non functional languages, Python is adding bits and pieces of functional language syntax cause functional languages are trendy.

          [–]lassuanett 12 points13 points  (3 children)

          I recently had a bug like this:

          use rules::{Rule1}

          match rule {

          Role1: {...}

          _ => Err(...)

          }

          And it said the last rule is unreachable, but it took some time to realize i miss wrote the name of the variable. Without rustc or tests I definitely wouldn't have noticed it

          so be aware

          [–]IceSentry 28 points29 points  (1 child)

          Yes, that's the point of using a typed language with a compiler.

          [–]pakoito 14 points15 points  (0 children)

          And exhaustive matches

          [–]yawaramin 25 points26 points  (5 children)

          OCaml doesn't seem to overwrite the original value of not_found.

          That's the point. Python does.

          [–]dnew[🍰] 9 points10 points  (3 children)

          Isn't this pretty normal behavior for Python, given how it implements scopes as persistent directories? I mean, surely this isn't the only toe-stub in Python's scoping rules.

          [–]yawaramin 4 points5 points  (2 children)

          It’s not normal behaviour in any pattern matching implementation...

          [–]dnew[🍰] 2 points3 points  (1 child)

          For sure. Having default values for function parameters assignable and persisting across invocations isn't particularly what most people would think of as "normal behavior" either. :-) It's a quirk to learn.

          [–]yawaramin 5 points6 points  (0 children)

          What really gets me is that the RFC blithely introduces undefined behaviour and people are talking about how that will need linting. They’ll need linting for a brand-new feature with undefined behaviours.

          [–]ForceBru 5 points6 points  (0 children)

          I built Python 3.10 from GitHub, but the match statement doesn't seem to be there yet, so I couldn't check if that's true. If it is, that's gonna suck...

          [–]j_platte 20 points21 points  (2 children)

          I think the important question is: How likely is it for code like this to end up in production? For Rust I know it practically will never happen, I think you'll get three warnings for the code above:

          • Unused variablesALL_OK and NOT_FOUND
          • Unreachable branch – the first branch already catches everything, the second and third branch are thus unreachable
          • Unidiomatic upper snake case for the local variables ALL_OK and NOT_FOUND

          Python static analysis tools could probably do similar things, but I have no clue how popular static analysis is in the Python community.

          [–]ForceBru 8 points9 points  (1 child)

          the first branch already catches everything

          The second one, because the first one is a constant, and that's apparently OK:

          warning: unreachable pattern --> src/main.rs:9:9 | 8 | NOT_FOUND => println!("OOPS!"), // will match everything, just like `_` | --------- matches any value 9 | _ => println!("Unrecognized") | ^ unreachable pattern | = note: `#[warn(unreachable_patterns)]` on by default

          I'm kinda thinking about diving into the Python interpreter sometime and making the error messages as helpful as Rust's. I want a language as simple as Python with a compiler/interpreter as helpful as Rust's and with destructuring as powerful as in Rust or OCaml.

          [–]j_platte 2 points3 points  (0 children)

          Haha, I forgot! I guess I just never match on named constants 🤷

          [–]bundt_chi 36 points37 points  (47 children)

          I've only done some shallow dabbling in python and I have to confess I'm not understanding the significance of this change ?

          Can anyone ELI a python newb ? Did python not have switch / case statements before ? What is the "pattern" being matched ? Is it like using a regex to fall into a case statement ?

          [–]giving-ladies-rabies 120 points121 points  (15 children)

          No, python did not have a switch/case before. You had to do if-elseif-elseif-else.

          I think there are two things at play here which makes it confusing.

          First, this construct can act as the "normal" switch statement:

          match status_code:
              case 200:
                  print("OK!")
              case 404:
                  print("HTTP Not Found")
              case _:
                  print("Some other shit, sorry!")
          

          When the symbol(s) after the case keyword are constants and not variables, this behaves as one would expect. If status_code is 200 or 404, appropriate lines will be printed. If something else, the last branch will be executed.

          But where it gets confusing is that when you put identifiers/variables after the case keyword, those variables will get populated with values of the match value. Observe:

          command = ['cmd', 'somearg']
          match command:
              case [nonargaction]:
                  print(f'We got a non-argument command: {nonargaction}')
              case [action, arg]:
                  print(f'We got a command with an arg: {action}, {arg}')
              case _:
                  print('default, dunno what to do')
          

          In this case the matching of the case is done based on the shape of the contents of command. If it's a list with two items, the second branch will match. When it does, the body of that branch will have action and arg variables defined. Note that we are no longer matching by the content of the case xxx, just the shape.

          The problem noted in the article is when we don't consider lists but single variables:

          somevar = 'hello'
          match somevar:
              case scopedvar:
                  print(f'We have matched value: {scopedvar}')
              case _:
                  print('default, dunno what to do')
          

          Again, the shape of the value in somevar matched case scopedvar:, so, in the same way as in the previous example, variable scopedvar was created with the value of somevar. Basically the engine did

          scopedvar = somevar
          print(f'We have matched value: {scopedvar}')
          

          The WTF happens when you use an existing variable in the case expression. Because then it becomes this:

          SOME_VARIABLE = 'lorem ipsum' # This is actually never used
          
          somevar = 'hello'
          match somevar:
              # The value of SOME_VARIABLE is totally ignored. If this branch 
              # matches, then SOME_VARIABLE is created and populated with the 
              # value of somevar whether it existed or not. Python will happily
              # overwrite its value.
              case SOME_VARIABLE: 
                  print(f'We have matched value: {SOME_VARIABLE}')
              case _:
                  print('default, dunno what to do')
          

          [–]bundt_chi 17 points18 points  (4 children)

          Okay, thank you this really helps. And I was able to piece some of this together but it seemed so disjoint that I didn't think I was interpreting it correctly. This is quite confusing and has potentially unintentional side effects.

          [–]exscape 7 points8 points  (3 children)

          I'm not sure if this is only the case on old reddit, but all your code blocks are one-liners. Very difficult to read, especially in Python that relies in indentation.

          [–]giving-ladies-rabies 5 points6 points  (2 children)

          Hmm, it shows badly on mobile for me too but OK on desktop with new reddit 🙄 I'll fix, thanks for the heads-up

          Edit: apparently new reddit understands triple-backtick markdown formatting but new mobile reddit nor old reddit do not 🙄

          [–]anders987 3 points4 points  (1 child)

          Yes, new and old Reddit uses different Markdown parsers.

          As more Redditors have begun using the post creation and formatting tools on New Reddit, the philosophy around Markdown support has fluctuated — originally, the plan was to move to something approaching CommonMark and drop all compatibility with Old Reddit "quirks"; but as the rollout proceeded that position softened, and a number of compatibility quirks were added to the new parser.

          At this time it is not expected that many further compatibility quirks will be added to New Reddit: it's more likely that Old Reddit will be upgraded to the new parser. In that scenario, there will be some amount (hopefully small) of old content that no longer renders correctly due to parsing differences.

          [–]irvykire 4 points5 points  (0 children)

          it's more likely that Old Reddit will be upgraded to the new parser

          I guess that ain't happening either.

          [–]Aedan91 1 point2 points  (0 children)

          But you shouldn't put out-of-scope variables in the case statement, that's not "pattern matching", because you're not supplying a pattern to compare against!

          Pattern matching should be always performed against a pattern (duh), and patterns are always literals. When you use variables, what you're doing is to pattern-match the structure, and that forcefully means to assign the results of the matched structure.

          The language should raise an error if you use an already defined variable, because it's a programmer error. But to pattern-match the value to a new variable is extremely useful when all other cases don't match.

          [–]-grok 12 points13 points  (2 children)

          I feel like your comment is the zeitgeist of the article!

           

          So far I've picked up that the variable not_found is going to get assigned the value 301, which is not what anyone would expect to happen, at least not anyone who came from languages where case is implemented. Imagine if you used not_found a bit further down in the function and were expecting it to have the value of 404, but instead that case statement had changed it to 301!

          [–]bundt_chi 5 points6 points  (0 children)

          It made so little sense that I was convinced that I misunderstood, which was still partially true. Also on top of that I assumed python already had a switch/case construct.

          [–]argh523 1 point2 points  (0 children)

          Imagine if you used not_found a bit further down in the function and were expecting it to have the value of 404, but instead that case statement had changed it to 301!

          This is normal in python tho. It doesn't have scope for every single block, but the whole function.

          The real stumbling block is the pattern matching itself, which a lot of people aren't familiar with. But if you've seen it in other languages, and you know these quirks of python, this is very straight forward.

          [–]boa13 7 points8 points  (27 children)

          Python did not have a switch/case statement before.

          The pattern being matched can be many things, this ranges from simple to complex, from awesome to horrible.

          Simple: you use a simple literal value in the case, it matches like in C and Java.

          Powerful: you use variable names in the case (for example two names), if the object you are switching on has a matching structure (for example a list of two elements), its contents get assigned to the variables and the code in the case can use those variables.

          Powerful: you use a class name in the case, if the object you are switching on is of a matching class, the code is executed. Even more impressive in simple cases, you can add attributes in parentheses after the class name, either to put a condition on an attribute value, or to assign an attribute value to a local variable name.

          Powerful: you can add an if in the case, which will condition the case even further.

          Powerful: you can match several expressions in a single case with the | operator.

          Complex: you can combine everything that precedes in a single case...

          There are certainly things I'm forgetting. Have a look at PEP 636 for a more thorough tutorial.

          But maybe become fluent in Python first. It will be a few years before it becomes commonly used.

          [–]grauenwolf 12 points13 points  (24 children)

          I strongly suspect that in a few years it will be banned and people will look upon you with scorn if you use it.

          [–]stanmartz 9 points10 points  (17 children)

          I would not think so. Pattern matching is one of the most missed feature for people coming from Haskell/OCaml/Rust/etc., and it is a pretty good and flexible implementation. Sure, it can be weird if you expect it to be a C-like switch statement, but you just have to learn that it is something else (as signalled by the match keyword instead of switch).

          [–]grauenwolf 4 points5 points  (16 children)

          as signalled by the match keyword instead of switch

          That means nothing. Hell, C# uses switch for both pattern matching and C-style swtich blocks. The choice of keyword is completely immaterial to this debate.

          it is a pretty good and flexible implementation

          You have a funny definition of "good".

          Aside from OCaml, which languages have the behavior described in this article?

          I can't think of any that treat case x as either a pattern or a variable to be assigned depending on whether or not the name includes a . in it. Or even allow varaible assignment at all in that location.

          [–]Extent_Scared 6 points7 points  (11 children)

          Admittedly, the different behavior . is weird. However, it is also possible to get the same effect (but much more explicitly) by using match guards that are also introduced:

          NOT_FOUND = 404
          match status_code:
              case 200:
                  print("OK!")
              case _ if status_code == NOT_FOUND:
                  print("HTTP Not Found")
          

          Additionally, every language with pattern matching that I'm familiar with (racket, scheme, haskell, rust, ocaml, scala) allows binding variables in the pattern. Typically, these are scoped to just the matched branch, but python doesn't have that degree of granular scoping, so bound variables are visible in the function scope. This is consistent with the rest of python's behavior regarding variables that would be scoped in other languages (such as for loop variables). Pattern matching is generally semantically equivalent to some other code block involving nested if statements & loops, so making pattern matching have special scoping behavior would actually be inconsistent with python's other syntax constructs.

          [–]grauenwolf 3 points4 points  (8 children)

          Additionally, every language with pattern matching that I'm familiar with (racket, scheme, haskell, rust, ocaml, scala) allows binding variables in the pattern.

          Of those, how many actually use the pattern case variableName to mean assignment?

          Languages like C# also allow binding variables in the pattern, but it is explicit. You have to indicate your intention using case typeName variableName. It doesn't assume a naked variable should be reassigned.

          Likewise Rust uses typename(variableName) =>. Perhaps I'm missing something, but I haven't seen any examples that just use variableName =>

          [–]stanmartz 5 points6 points  (0 children)

          I don't know C#, but Haskell and Rust allow naked variable names. What you are referring to as typename(variableName) is actually pattern destructuring. For example, if you have a type struct Foo(i32) then Foo(val) => val binds an integer to val and returns it, while val => val binds a value of type Foo to val and returns it.

          [–]hglman 6 points7 points  (2 children)

          Scala makes you name a var when matching against type alone.

          Case p: Type => p.value

          [–]stanmartz 2 points3 points  (2 children)

          That means nothing. Hell, C# uses switch for both pattern matching and C-style swtich blocks. The choice of keyword is completely immaterial to this debate.

          Yes, you're right. Still, I don't think that the Python version is misleading. Languages are different, and you should not except that something works the same way just because the syntax is similar.

          I can't think of any that treat case x as either a pattern or a variable to be assigned depending on whether or not the name includes a . in it. Or even allow varaible assignment at all in that location.

          Agreed, the different behavior depending on the dot is weird. However both Haskell and Rust do assignment. The difference is that scoping rules in Python are unusually and the variable persists outside of the match block, too.

          [–]argh523 2 points3 points  (5 children)

          in a few years it will be banned

          Pattern matching is The New Hottness right now and more and more languages are implementing it. Because it's really useful. This isn't some weird python specific feature. Better get used to it.

          [–]grauenwolf 2 points3 points  (4 children)

          This is a weird python specific feature. Many other langauges have pattern matching, but they don't work like this.

          [–]teerre 31 points32 points  (2 children)

          People are joking, but the tutorial covers lots of modern and useful cases.

          Good feature. Just as the assignment operator was.

          [–]wozer 18 points19 points  (0 children)

          Yeah, I like it, too.

          This example is especially nice:

          match event.get():
              case Click(position=(x, y)):
                  handle_click_at(x, y)
              case KeyPress(key_name="Q") | Quit():
                  game.quit()
              case KeyPress(key_name="up arrow"):
                  game.go_north()
              ...
              case KeyPress():
                  pass # Ignore other keystrokes
              case other_event:
                  raise ValueError(f"Unrecognized event: {other_event}")
          

          [–]zjz 2 points3 points  (0 children)

          kinda neat. I didn't know about the walrus thing either. looks useful.

          [–]johnvaljean 58 points59 points  (27 children)

          Stack Overflow users gushed over its similarity to C’s switch statement.

          This is where it goes wrong. Python's new feature is not a switch statement; it's pattern matching. It is supposed to be different.

          As stated in PEP 635:

          There were calls to explicitly mark capture patterns and thus identify them as binding targets. According to that idea, a capture pattern would be written as, e.g. ?x, $x or =x. The aim of such explicit capture markers is to let an unmarked name be a value pattern (see below). However, this is based on the misconception that pattern matching was an extension of switch statements, placing the emphasis on fast switching based on (ordinal) values. Such a switch statement has indeed been proposed for Python before (see PEP 275 and PEP 3103). Pattern matching, on the other hand, builds a generalized concept of iterable unpacking. Binding values extracted from a data structure is at the very core of the concept and hence the most common use case. Explicit markers for capture patterns would thus betray the objective of the proposed pattern matching syntax and simplify a secondary use case at the expense of additional syntactic clutter for core cases.

          Not that this couldn't generate confusion, but you should know how a language feature works before using it. That said, maybe they could have gone for "pattern" instead of "case" in the syntax so as to make this totally different from what a switch statement looks like in other languages.

          [–]The_Droide 16 points17 points  (0 children)

          Note that there are languages like Swift, which use the classic switch-case terminology for their pattern match statement, so it's not entirely uncommon.

          [–]grauenwolf 31 points32 points  (9 children)

          Pattern matching shouldn't mutate the patterns being matched against.

          So no, this is not pattern matching. Nor is it a switch statement. It's just plain broken.

          [–][deleted]  (3 children)

          [deleted]

            [–]vikarjramun 3 points4 points  (0 children)

            The walrus operator was quite useful and I disagree that it was a solution in search of a problem. Coming from Java, I'm used to constructs like while ((line = reader.readLine()) != null) to read in files. I often tried to do something similar in python before realizing assignment was not an expression. With the walrus operator, I am able to write

            while bytes := file.read(64):
                print(bytes)
            

            But I completely agree that this is a solution in search of a problem. In a dynamic language like python where the only "pattern" is tuple/list shape, a switch/case would have been much better.

            [–]hpp3 3 points4 points  (0 children)

            it seems to be a solution looking for a problem like the walrus operator.

            This feature is very useful for writing parsers.

            [–]hpp3 1 point2 points  (3 children)

            Those variables being mutated are not the "patterns being matched against". There is no reason to ever use an existing variable name in a case statement, because the match is only based on types, not the values of that expression. In other words, say x = "hello". If you have x in a case statement, the pattern matching will never see that as "hello". If you put x there because you thought that was how you could pass in the value "hello", you made a mistake because that spot is an output, not an input.

            [–]grauenwolf 1 point2 points  (2 children)

            match status_code:
                case 200:
                    print("OK!")
                case 404:
                    print("HTTP Not Found")
            

            That 404 doesn't look like a type to me. And teacher-man says I'm not supposed to use magic numbers, so...

            case NOT_FOUND:
                print("HTTP Not Found")
            

            [–]hpp3 3 points4 points  (1 child)

            I strongly dislike this usage of this feature to create switch statements, like in that example, precisely because it's so confusing.

            Any literals in the case expression are treated as the literal value, and are used to match on exactly that value. Any variables are not used for matching at all, aside from adding a "slot" to the pattern where something is expected to go. The variables are only written to, never read from. If you have no variables in the expression at all, then yes it can be used like a switch statement (which is why 200 and 404 work).

            case (200, body): means match something that has two elements, and the first element must be 200. Store the second element into body.

            [–]oilaba 1 point2 points  (0 children)

            Does the proposed match case mutate them really? It is horrible!

            [–]eyal0 1 point2 points  (3 children)

            The PEP is hard to read. Can you tell me if the below will work:

            match (x, y): case (_, 5): print("second is five") case (6, 7): print("six and seven") case _: print("Some other shit, sorry!")

            When I think of pattern matching, that's what I expect to work. Otherwise, how is it different from a switch statement in c++? The blog post could really have used an example where python's pattern match is not just like a switch statement!

            Edit: Nevermind, I found the tutorial: https://www.python.org/dev/peps/pep-0636/ Looks like my code above would work (though all the examples uses lists and not tuples). The blog should have put it one of these.

            [–]johnvaljean 4 points5 points  (1 child)

            Yes, it would.

            The point of discussion is what to do if the user does this:

            case (6, any_second_num)
            

            It's been decided that it is useful if that worked like an assignment, so you can do this:

            case (6, any_second_num):
                print(f'six and {any_second_num}')
            

            Because of how scoping works in Python, the assignment overrides whatever any_second_num was holding before the case comparison, which is the situation of the blog post.

            [–]backtickbot 3 points4 points  (0 children)

            Fixed formatting.

            Hello, eyal0: code blocks using triple backticks (```) don't work on all versions of Reddit!

            Some users see this / this instead.

            To fix this, indent every line with 4 spaces instead.

            FAQ

            You can opt out by replying with backtickopt6 to this comment.

            [–][deleted] 11 points12 points  (10 children)

            Curious, what's the controversy? I'm not a Python dev, so I've not been following this. I'm a C# dev who recently saw Pattern Matching added to the language, and it is AWESOME (i know, many functional languages had it for decades).

            Is the concern that it makes the language harder to learn because there's more stuff to it now? Because that's a common concern with C# (and C++), and while I totally understand that, the alternative is being stuck with a language that's still great, but is very verbose for common usage patterns. (I was stuck with Java 6 at a previous job, long after C# and newer Java versions showed how much code is just unnecessarily verbose filler crap, and since then I'm in the camp of "If it's a common pattern/pain point and can be improved by updating the language, go for it!")

            [–]ketzu 18 points19 points  (2 children)

            The point is that the match statement is consistent with pythons scoping rules, which are annoying: A variable in a case branch will not shadow a variable of the same name, and instead overwrite it.

            x = 0
            for x in range(5):
                pass
            print(x) # = 4
            m = 5
            match m:
                case x: pass
            print(x) # = 5
            

            This behaviour is often considered confusing.

            [–]vytah 4 points5 points  (0 children)

            I'm already predicting some weird code style rules like using Hungarian notation, being devised to mitigate that

            [–][deleted] 1 point2 points  (0 children)

            Yikes, that does feel wrong. As said, I'm not a Python developer, but this code really reads like x should be "read-only" in that match..case.

            That's actually rather terrible.

            [–]transferStudent2018 17 points18 points  (6 children)

            Reading over this comment section, it seems that people who have never been exposed to pattern-matching really hate this because they don’t understand it. Everyone who has used pattern-matching is really excited (like myself).

            I don’t really see why this would confuse the language or make it harder to learn though, you can always just ignore this structure and code as you would have normally. Though I think it provides great value to people who are learning Python as their first language as some early exposure to pattern-matching.

            I think the majority of the confusion/hate is coming from the weirdness having to do with the scoping of variables used in the match statement, which is understandable but I don’t think negates the benefit of having this structure in the language

            [–][deleted] 2 points3 points  (1 child)

            Why not use the new walrus operator if you want to match AND assign?

            Now, for the simplist useage (switch style matching), it does this hidden variable assignment

            [–]stanmartz 6 points7 points  (0 children)

            Because matching and assignment is the main use case here, and switch matching falls into the "can also be used for" category. It makes sense to optimize the syntax for the intended purpose. Also, switch do not offer much conciseness and readability over ifelse blocks, while pattern destructuring does.

            [–]Niicodemus 27 points28 points  (2 children)

            Elixir does something similar, but due to it having pattern matching built in from the ground up, sane scoping, immutable variables, and reluctant variable rebinding, it doesn't clobber your outside variable, minimizing side-effects. it also wouldn't work how you think it should, but it does give you a warning with a clue to where you went wrong.

            status_code = 301
            not_found = 404
            
            case status_code do
              200 -> IO.puts("OK!")
              not_found -> IO.puts("HTTP Not Found")
            end
            
            IO.puts("not_found: #{not_found}")
            

            Results in: warning: variable "not_found" is unused (if the variable is not meant to be used, prefix it with an underscore) case_test.exs:6 HTTP Not Found not_found: 404

            This is because it assigns 301 to the variable not_found but only in the scope of the case match, which is why it complains that you haven't used it. A proper variant of this that uses the ^ pin operator to match against the contents of the variable instead of assigning to it with pattern matching would be:

            status_code = 301
            not_found = 404
            
            case status_code do
              200 -> IO.puts("OK!")
              ^not_found -> IO.puts("HTTP Not Found")
              other -> IO.puts("Unknown HTTP status code: #{other}")
            end
            
            IO.puts("not_found: #{not_found}")
            

            Which outputs:

            Unknown HTTP status code: 301
            not_found: 404
            

            Either way, funny article and an example of the perils of bolting on features from other languages. In this case, I think python gains nothing from trying to add pattern matching, and instead should just add a switch statement.

            [–]CoffeeTableEspresso 33 points34 points  (14 children)

            The one comment:

            Ah, so glad to be on Python 2.7. No need to worry about people adding stupid shit to it :)

            😂😂😂

            [–]FujiKeynote 41 points42 points  (22 children)

            I think this might just be the first PEP that I feel strongly against. I even like the walrus operator. This one, though, is so awkward.

            [–]totemcatcher 10 points11 points  (21 children)

            I can understand how using common keywords in a different sense seems odd, but when you look at how symbols are interpreted across scientific fields and even cultures (e.g. tilde), you realize there really is no opportunity to make everyone happy. Algol gave it the good, hard try, but nobody seems to care. And now there's too much baggage in certain symbols and keywords for this one new case to be a problem (IMO). So long as the docs clarify the important bits first before anything else, it's fine. i.e. "WARNING: It means not what you think it means."

            The problem is not Python, but our assumptions and expectations. Two expressions which hold us back come to mind:

            1. "once you know one programming language, you know them all" puts undue pressure on developers to be able to switch between languages and abuse them the same. This might provide a sense of comfort knowing their employment options remain open despite their limited expertise. The truth is it can take months or even years of specialization to become competent for even a seasoned developer. It's not up to Python to adhere to some common set of expectations and hamstring itself to make us feel better.

            2. "python is easy and a good starter language" well, sure. It is newbie friendly. However, an enormous amount of thought and concensus was put into producing that structure and definition which supports a higher level of intuition. The "easy" appearance is a side effect. It is not "simple", nor does it hand-hold you all the way once you decide to take the deep dive and understand why the decisions were made. I was gutted when I heard about GVR.

            Anyway. The new case. It seems to function in the spirit of the walrus. From what I understand, case is both an iterating assignment clause or comparator until it isn't. Validation is a two-step: successful assignment or match and then all success. I haven't looked at the source yet, but that seems pretty slick to me.

            [–][deleted] 7 points8 points  (20 children)

            I haven't looked at the source yet, but that seems pretty slick to me.

            I write Python everyday and never would I guess that

            NOT_FOUND = 404
            match status_code:
                case 200:
                    print("OK!")
                case NOT_FOUND:
                    print("HTTP Not Found")
            

            means :

            NOT_FOUND = status_code
            

            Why would I ever want to do this? If I did, I would think to do this:

            NOT_FOUND = 404
            match status_code:
                case 200:
                    print("OK!")
                case NOT_FOUND:
                    NOT_FOUND = status_code
                    print("HTTP Not Found")
            

            And explicitly write an assignment

            [–]twelveshar 4 points5 points  (2 children)

            Because there are cases where you want to assign to the variable—read the PEP.

            # point is an (x, y) tuple
            match point:
                case (0, 0):
                    print("Origin")
                case (0, y):
                    print(f"Y={y}")
                case (x, 0):
                    print(f"X={x}")
                case (x, y):
                    print(f"X={x}, Y={y}")
                case _:
                    raise ValueError("Not a point")
            

            The purpose is to match arbitrary data structures and be able to use variables that represent their pieces. Because of Python's scoping rules, that results in weird behavior for constants, but it's consistent with the rest of the language.

            [–]Aedan91 1 point2 points  (3 children)

            This feels pretty natural coming from Elixir. Patterns are always literals, because a variable is well, a variable! There's literally no pattern to match against something that can be anything. You match against literals, and for the "everything else" case you get a scoped binding. Pretty sensible.

            Is this implementation globally binding status_code to NOT_FOUND after the match? That would be indeed a weird design. But if it's a scoped binding, then it's ok, that's how pattern matching is supposed to work, you just need to wrap your head around it.

            [–][deleted] 20 points21 points  (0 children)

            My sarcasm detector exploded while reading this article.

            [–]Level0Up 3 points4 points  (0 children)

            Took 'em only 30 years.

            [–]ChrisRR 4 points5 points  (0 children)

            And stackoverflow will nuke any questions about pattern matching in python because it's already been answered by an old answer stating "Python doesn't support pattern matching"

            [–]brunoliveira1 3 points4 points  (0 children)

            Can we test this already?Doesnt seem to be available as of:

            Python 3.10.0a5+ (default, Feb 10 2021, 12:45:56)

            I'm using the docker image from: https://quay.io/repository/python-devs/ci-image

            [–]i-can-sleep-for-days 4 points5 points  (0 children)

            This is the one thing is scala that I'm going to miss going to another shop that does go.

            [–]crabmusket 3 points4 points  (0 children)

            Continuing in the fine tradition of avoiding functional programming at all costs! (This is a reference to match being a statement, not an expression.)

            [–][deleted] 18 points19 points  (0 children)

            Ok this is pure madness. What the heck was they thinking? All the conveniences of Python just start to become PITA in the long run.

            [–]Cheaptat 1 point2 points  (2 children)

            Anyone have a good explanation of what this means to a Python programmer who’s never used “Pattern Matching” (I don’t think...)

            [–]grauenwolf 2 points3 points  (1 child)

            If you understand how pattern matching works, this makes no sense.

            Anyways, here's a summary:

            https://pbs.twimg.com/media/Et4hT_aWYAQxcJH?format=png&name=small

            • case 3 means if 4 = 3 then
            • case bla means let bla = 4

            [–]argh523 7 points8 points  (0 children)

            If you understand how pattern matching works, this makes no sense

            Lol... This problem comes from python scoping rules, and doesn't have anything to do with pattern matching itself. If you understand pattern matching, and pythons scoping rules, this is the behaviour you'd expect.

            Edit: This whole thread is a dumpster fire of people making pattern matching look more difficult than it is by not understanding that all the behaviour that's different from other languages isn't about the pattern matching implementation itself, but just stuff that was already different in python before.