546 comments

[ 3.1 ms ] story [ 307 ms ] thread
I’ve followed these things over the years. Had many discussions with people, including a few Python luminaries, but I honestly never expected this day would come. I fell in love with pattern matching when I learned Erlang and Elixir and it’s bugged me ever since that I didn’t have any built in equivalent in Python, until now!
Python 2 had pattern matching on tuples in function arguments; I was quite annoyed, coming from Erlang, when I discovered that was dropped in 3.0, in part because "no one uses it".
This is not truly Erlang style pattern matching. And Python can never have that either because of its design.
Interesting discussions over on Twitter about the new parser might be letting out the complexity genie and how some think that might not be the greatest. https://twitter.com/ramalhoorg/status/1358168753100496907
Not really. Its a string of preferences for Python's development. I hope the author pursues those endeavors and encourages others to join him. But they're not an argument against pattern matching.
Talking about preferences still qualifies as interesting discussion.
As someone who hasn’t been following closely, was the PEG parser necessary for pattern matching or is that an unrelated change?
IIRC one of Guido’s reasons for switching to a PEG parser was to enable the addition of pattern matching.
Can anyone confirm this is actual pattern matching and not the frankly ridiculous procedural version that was originally proposed?

That proposal, to deliberately hobble pattern matching by making it a statement, was an egregious ideological campaign with genuinely absurd consequences.

Not sure what you mean, but the accepted PEP does appear to be a statement. What was the alternative?
In many languages match is an expression, so you can do things like `x = match y { ... }`, which is a very common use case.

I don't write much Python these days, but it being a statement rather than an expression is disappointing

It forces you to use a separate "output" variable or `return`.

I presume that the alternative to a statement is an expression. For example, the ternary operator

   x = (a if c else b)
is an expression, where the conditional equivalent

   if c:
       x = a
   else:
       x = b
is a statement.
match is a statement, but the syntax is very readable.

https://www.python.org/dev/peps/pep-0636/#adding-conditions-...

> the syntax is very readable

Yet it can't be used in a lambda, it can't be assigned to a variable, it can't be used as an argument, it can't be called as a function, it can't be put in a datastructure, it can't be raised, it can't be returned, it can't be yielded, and so on for all the things that can't be done to statements.

Heck, we can't even match on a match!

I've been trying to find the motivation for making it a statement. Do you have links to any prior discussion?

I maintain a transpiler that converts python to 6 type safe languages. Keeping match semantics similar makes it easier to transpile.

Strong agree that this is hobbled by being a statement.

Pattern matching is great for dispatch and overloading, but because it's a statement there's no way for users to add overloads.

There will have to be some `matches(pattern, object)` function in CPython. Why not make this available to users? Why make patterns syntax, not objects?

(comment deleted)
[Expanding my comment from another post on this¹]

Direct links to the PEPs themselves, to perhaps save others a little time. Although the LWN write up linked in TFA is a very nice introduction to the topic and its big discussion in the community.

Accepted:

Specification https://www.python.org/dev/peps/pep-0634/

Motivation and Rationale https://www.python.org/dev/peps/pep-0635/

Tutorial https://www.python.org/dev/peps/pep-0636/

Rejected:

Unused variable syntax https://www.python.org/dev/peps/pep-0640/

Explicit Pattern Syntax for Structural Pattern Matching https://www.python.org/dev/peps/pep-0642/

¹ https://news.ycombinator.com/item?id=26073700

...

I'll take my down votes. Please move on opinionated HN-ers.

(comment deleted)
By that same logic wouldn’t

   def f(x):
look too close to function invocation for your taste?
(comment deleted)
Pattern matching is what I missed most when switching from Swift to Python a year ago. Really happy to see this day come!
Can't wait to see all the errors and questions when users start using this for the missing switch statement, and not realizing it doesn't work for non-dotted constants.

Because if I didn't misunderstood, this is not correct, is it?

  NOT_FOUND = 404
  OK = 200

  match getCode():
      case NOT_FOUND:
          return default()
      case OK:
          return response()
Yup, that's what the PEP seems to suggest.

This along with mutable default values is going to be one of the nastier Python warts.

Not to mention that expressions after the case keyword have completely different semantics from expressions ANYWHERE else. a | b is not the bitwise or of a with b.
You can already define __or__ to do whatever you want!

This is already special-cased in the standard library for, say, set union (where admittedly the concept is more similar to bitwise or).

... except in case expressions

Thanks for proving my point though!

This part is truly strange. This example from the tutorial drove it home for me:

    match event.get():
        case Click(position=(x, y)):
            handle_click_at(x, y)
        case KeyPress(key_name="Q") | Quit():
            game.quit()
where `Click(position=(x, y))` is not at all what one would expect.

What I still don't understand is: does the new syntax magically do this to a user-defined class? i.e. if I say

    foo = Click(position=(x, y))
vs

    ...
    case Click(position=(x, y))
those identical expressions, for the same value of Click, have widely different semantics?

edit: typo fix

You are (sadly) correct. The latter example checks that the type of `event.get()` is a Click, then that it has a `.position` attribute that be restructured into a two-element tuple `(x,y)`. It's equivalent to `ev = event.get(); if isinstance(ev, Click): (x,y) = ev.position`
Correct.

> Patterns may use named constants. These must be dotted names to prevent them from being interpreted as capture variable:

NOT_FOUND would always match as a capture variable, so you'd always return default().

I fully expect this to be covered by linters, but not with further language support.

yeah, I would also expect the linters to catch this. has some way to allow use of non-dotted constants been considered? something like window.global_variable in JavaScript? Or would I need to do something like this:?

  NOT_FOUND = 404
  m = {"NOT_FOUND": NOT_FOUND}
  match value:
    case m["NOT_FOUND"]:
      pass
You can do

    NOT_FOUND = 404
    match value:
      case v if v == NOT_FOUND:
        pass
On its own this is a convoluted alternative to `if`, but it can be useful when we have lots of patterns.

Alternatively, we can avoid using a separate `v` variable and just use `value` directly:

    NOT_FOUND = 404
    match value:
      case _ if value == NOT_FOUND:
        pass
We can even match on a trivial value, and do all of the interesting checks using guards:

    NOT_FOUND = 404
    match None:
      case _ if value == NOT_FOUND:
        pass
This latter form acts like Lisp's `cond`, and I've used it a few times in Scala, e.g.

    () match {
      case _ if foo >  bar => ...
      case _ if foo <  bar => ...
      case _ if foo == bar => ...
    }
But all of these alternatives are strictly less intuitive than the original expression. A big reason why I got into Python was because it’s the language that can be “read by anyone”, even those who do not know Python. This latest PEP, coming on the heels of several others like it, shows how alarmingly far from the ideal we’ve come since 2.7.
> A big reason why I got into Python was because it’s the language that can be “read by anyone”, even those who do not know Python.

Yes! For the first 2 decades of its life, people loved Python because it was like “executable pseudocode”. Unfortunately over the last 5-10 years it seems to have given up on that idea, instead becoming more and more like “whitespace-sensitive C++”.

> But all of these alternatives are strictly less intuitive than the original expression.

I agree; I was just trying to show how pattern guards work. I would only resort to pattern guards if I need a `cond` (like my last example), or on a few cases of a more 'natural' pattern match, e.g.

    match foo():
      case OK(msg):
        return success(msg)
      case NotFound(_) if isQuery:
        return success("No results matched query")
      case NotFound(msg):
        return failure(msg)
      ...
If I were to implement your original example (i.e. 'which of these constants do I have?'), I'd probably still go with a "dictionary dispatch" (especially since it's an expression, so it works in lambdas, etc.):

    return {
      NOT_FOUND: default,
      OK: response,
    }[getCode()]()
(comment deleted)
plus the distinction of using dotted constants makes no sense, that can just mean a variable in another module
Well, of course. It is a pattern matcher. case NOT_FOUND: is a pattern that matches anything and binds it to NOT_FOUND. If you want to match the content of a variable you need to signal that in some way. In scheme that usually means unquote:

    (define not-found 404)
    (match status
      (,not-found (do-stuff))
      (else (display "you might believe this is an else clause, but it actually just matches everything and binds it to else")))
IMO the match statement has some very unintuitive behaviour:

    match status:
        case 404:
            return "Not found"

    not_found = 404
    match status:
        case not_found:
            return "Not found"
The first checks for equality (`status == 404`) and the second performs an assignment (`not_found = status`).
That's how structural pattern matching works in ML-like languages, where it's been in use for 40+ years.
Don't those languages typically have immutable variables so you would not be able to rebind a constant by accident?
(comment deleted)
This is usually orthogonal to mutability.

In other languages that support match, whether functional or not, you are not changing the value of the variable, but you are shadowing/rebinding the identifier inside the scope of the match clause.

Python `match` statements do not introduce a new scope.

The second example does indeed change the value of `not_found`.

(comment deleted)
Oh dear, that is indeed very bad.
What's the rationale for not introducing a new scope?

The only clause I can find in the PEP is

> A capture pattern always succeeds. It binds the subject value to the name using the scoping rules for name binding established for named expressions 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.)

This seems... incredibly bad.

It's consistent with how scoping works in loops and `with` clauses. Agreed that it's more problematic here, though.
"Introducing a new scope" is not a concept that exists in Python, you only have function scope and module scope.
There are some exceptions to this - for example, the iteration variable(s) in a list comprehension are only in scope within the comprehension.
Thanks, I didn't realize that. That makes their choice really baffling.
The explanation is that (in Python 3) list comprehensions are really just syntactic sugar for generator expressions. In Python, "scope" is really a synonym for "dictionary attached to some object", so generators can have local variables (since they have their own internal scope), but purely syntactic constructs cannot.
Worth noting that this was _fixed_ in Python 3.

In Python 2.7 you get this:

    >>> my_list = [x for x in 'hello']
    >>> x
    'o'
Having only function, module, class, generator, etc. scope in Python before this PEP might have made sense, but they really should have added pattern matching scope to keep things sane here.
That works the same as the rest of the language, doesn't it?

(That is, for, if, while, etc. also don't introduce new scopes.)

So you can easily create a variable in a match case
That's a very problematic design decision, imo...
Doesn’t other languages have block scope and shadowing is not an issue, but in Python it will reassign instead of shadowing?

In my mind that is not well thought out.

Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.
(comment deleted)
Apparently it's reassigning the existing not_found variable from the outer scope, not binding a new not_found variable in an inner scope.
I just wrote a comment with the same complain and similar example!

I also think the same as you wrote, the second check will be a hard to spot mistake

I think your example makes it clearer that it won't be a very subtle a bug to find, but rather completely broken behaviour where only the first case is ever triggered. That should be much simpler to test against. Granted, this breaks down if you're matching by the number of values, possibly other cases.

To be honest, I feel I like the (sort-of-) "forced namespacing" of matching constants this brings. It should be an easy habit to discipline too, the rule being very simple:

"if you don't want to use literals in your matching, you must plug them in an enumeration"

Too bad that enumeration can't be a PEP 435 enum without adding an ugly `.value` accessor to each case, though.

Why is it performing an implicit assignment instead of an equality check?
A bit part of the appeal of pattern matching in other languages is support for destructuring, where you implicitly assign variables to members of a data structure you're performing a match on. For example, in ML-ish pseudocode:

len([]) = 0 len([first|rest]) = 1 + len(rest)

That's a trivial example. The second PEP (pattern matching tutorial) has several other examples:

https://www.python.org/dev/peps/pep-0636/#matching-multiple-...

So, if you use a variable in a pattern, it's an implicit assignment. If you use a literal, it's a value to be matched against.

I agree that the trivial case (a single variable with no conditions) may be confusing before you know what's going on, but I think the alternative, where a single variable is a comparison but multiple variables (or a structure) is an assignment isn't necessarily better.

> A bit part of the appeal of pattern matching in other languages is support for destructuring, where you implicitly assign variables to members of a data structure you're performing a match on

I would clarify that to implicitly introduce variables.

Consider the following:

    $ python3
    Python 3.8.6 (default, Dec 28 2020, 20:00:05) 
    [Clang 7.1.0 (tags/RELEASE_710/final)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> def foo(x):
    ...   return ([x for x in [1, 2, 3]], x, (lambda x: x+1)(x), x)
    ... 
    >>> foo(42)
    ([1, 2, 3], 42, 43, 42)
The list comprehension did not assign the variable x to 1, 2 and 3; it introduced a variable called x, but our original (argument) variable is unaffected (since the second result is 42). Likewise, the nested function doesn't assign its argument variable x, it introduces an argument variable x. Whilst this affects that nested function's result (we get 43), it doesn't affect our original variable, since the final result is still 42.

This match syntax seems to be assigning, rather than introducing, which is a shame.

(comment deleted)
I think the general criticism of the match statement is just baggage from C overexposure. See the keyword "case" and the brain immediately snaps to thinking we're in a bog-standard C-style switch statement.

It's not a switch! Nowhere does it say switch. It's structural pattern matching!

EDIT: The lack of block scoped variable thing does seem like a wart right enough.

OK, but from a functional programming point of view (where structural pattern matching comes from), "case" should bind a value to a name, not mutate the value of an existing variable. That seems nuts to me.
I don't get it, isn't that exactly what it's doing here?
It's mutating (not shadowing) `not_found` with the value of `status`. That can cause trouble if you rely on `not_found` keeping the initial value later somewhere. Which you would, e.g. with the global constant of `NOT_FOUND`.

Honestly I think the issue is so troublesome only if there's a single case to match, though. With more expected cases it should cause pretty obvious bugs (easy to catch).

(comment deleted)
Reassigning not mutating
Reassigning to a variable is how you mutate a variable, isn't it?

The value being [im]mutable is a different topic entirely.

(comment deleted)
There aren't actually "variables" in Python in the sense of named values, instead there are namespaces where values are stored and looked up under string identifier keys. (Just to add spice, some values, namely function and class objects, do get their names embedded in them, but this is only used for debugging. They are stored in enclosing namespace dicts like other values.):

    >>> def f(): pass

    >>> g = f

    >>> g
    <function f at 0x000001B4A686E0D0>

    >>> locals()
    {'__annotations__': {},
    '__builtins__': <module 'builtins' (built-in)>,
    '__doc__': None,
    '__loader__': <class '_frozen_importlib.BuiltinImporter'>,
    '__name__': '__main__',
    '__package__': None,
    '__spec__': None,
    'f': <function f at 0x000001B4A686E0D0>,
    'g': <function f at 0x000001B4A686E0D0>}
Wait, what's your definition of variable?

Because what you're describing fits perfectly into what I would call a variable. There is a mapping from an identifier to a slot that can store a value (or reference), and that mapping is stored in a specific scope. I would call that mapping a variable.

I'm not sure exactly why you mentioned objects that have names embedded in them. Is that relevant to the definition you're using?

> Wait, what's your definition of variable?

With full formality, the definition of "variable" depends on context. In assembly language and C variables are names for storage locations in RAM, in Python they are name bindings in a dict data structure.

One distinction we could make is whether the names are compiled away or kept and used at runtime.

In any event, the important thing is to keep clear in one's mind the semantics of the language one is using. In Python you have values (objects of various types: ints, strings, tuples, lists, functions, classes, instances of classes, types, etc.) some of which are mutable and others are immutable, and you have namespaces: dicts that map strings to values. These namespaces have nothing to do with the location of the values in RAM.

So it doesn't really make sense in Python to speak of "mutate a variable", you can mutate (some) values, and rebind values to names (new or old).

> I'm not sure exactly why you mentioned objects that have names embedded in them. Is that relevant to the definition you're using?

Not really, it's just another little point that sometimes confuses some people when they are coming to grips with the idea that in Python values are not associated with names except by namespace bindings. There are some values (function and class objects) that do get associated with names.

I suppose, but it's very strange to say "Assigning to a variable is classified as mutating the variable in some languages but not others, even though the underlying mechanism works exactly the same way."

Shouldn't terms like this be cross-language?

The underlying mechanism doesn't work the same though, e.g. in C assigning to a variable "mutates" the contents of RAM, in Python it mutates the namespace dictionary (which of course also results in some RAM contents changing too, but through a relatively elaborate abstraction.)

> Shouldn't terms like this be cross-language?

Variables in C are different beasties than variables in Python, and variables in Prolog are different from both, and none of those are like variables in math. It's a source of confusion that we use the word "variable" for a host of similar concepts. The word "type" is similarly overloaded in CS. (See https://www.cs.kent.ac.uk/people/staff/srk21/research/papers... )

FWIW, I'm just explaining what wendyshu was on about above. :)

That's not exactly how C works, but I wasn't going to use C as my primary example, I was going to use Rust. You have to write "let mut" to let a variable be reassigned/mutated, and variables in Rust are almost identical to ones in Python as far as being an abstract 'namespace' binding.
> That's not exactly how C works

Sure, I elided a bajillion details, and who knows what the CPU is doing under the hood anyway? :)

I've never used rust (yet) so I can't really comment on that.

FWIW namespaces in Python aren't abstract, they exist as dicts at runtime. You can modify the dict you get back from locals() or globals() and change your "variables" in scope:

    >>> d = locals()
    >>> d['cats'] = 'meow'
    >>> cats
    'meow'
Does it shadow the outside variable or mutate it?
Mutates it. Python `match` statements do not introduce a new scope.
Ouch, that's indeed pretty bad. I do expect `not_found = status` (that's how pattern matching works in several other languages), but it should be in its own scope, so that `not_found` is still `404` outside of the `match` block!
In those other languages, if `not_found` is equal to 404 before the match block, how do you write a clause that only matches if `status` is 404?

Do you have to use the integer literal 404 instead of the named integer `not_found`?

Depends on the language, but a lot of them use guard clauses (so case ... if cond =>).
(comment deleted)
Elixir uses the “pin operator”

“Use the pin operator ^ when you want to pattern match against a variable’s existing value rather than rebinding the variable.”

    iex> not_found = 404
    404
    iex> ^not_found = 200
    ** (MatchError) no match of right hand side value: 200
https://elixir-lang.org/getting-started/pattern-matching.htm...
Exactly, Elixir is usually the only example given. So, how it makes sense to blame Python here is unclear. There's a proposal to add similar "sigils" to Python pattern matching, it just didn't make it yet.
In OCaml and F# you can use `when`:

    match status with
    | n when n = not_found -> ...
Python doesn't have block scope. This can lead to some surprising behaviour:

  x = -1
  for x in range(7):
      if x == 6:
          print(x, ': for x inside loop')
  print(x, ': x in outer')
This outputs:

  6 : for x inside loop
  6 : x in outer
For consistency, I think it makes sense for match to behave in a similar way.
(comment deleted)
I don't know if I even know what "being consistent" would mean given the example here:

https://news.ycombinator.com/item?id=26082225

We don't shadow variables when they are re-used in comprehensions for example:

    >>> i = 4
    >>> [i for i in range(10)]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> i
    4
Given they're already accepting the oddity of having "case 404:" and "case variable:" mean very different things, I think they should have just gone the whole way and _not_ mutated outside variables. There seems to be little consistent with this addition to the language.
> We don't shadow variables when they are re-used in comprehensions for example:

I think "for example" makes this sound more general than it is. List comprehensions are one of the rare few constructs that introduce a new scope. The match block is more syntactically similar to if/while/etc. blocks, and so it makes sense that it would follow similar scoping rules.

This is not to say that I agree with the design as a whole. I think they should have went with:

  case obj.some_value:
      ...
  case some_value:  # behaves the same way as the previous
      ...
  case _ as variable:
      ...
  case (variable := _):  # an alternative to the previous syntax
      ...
I.e. I think the complete situation is messy, but it's not the variable scoping rules' fault.
> I think "for example" makes this sound more general than it is. List comprehensions are one of the rare few constructs that introduce a new scope.

The rarity of the construct doesn't matter honestly. Are you saying that list comprehensions should _not_ have introduced a new scope because it was unusual?

> The match block is more syntactically similar to if/while/etc. blocks, and so it makes sense that it would follow similar scoping rules.

Syntactically yes, but semantically it is very different and the semantics must be taken into account as well. The example given at the top of this thread (case 404: and case variable:) is enough to convince me that having variable scoping is a brain-dead obvious requirement.

> I think the complete situation is messy, but it's not the variable scoping rules' fault.

I agree with that statement. I think that improving the design/semantics would be more effective than just adding some more scoping rules in. In fact, I don't think this PEP should have been accepted in this current form at all. But given the current design, block-level scoping is appropriate. Given another design like maybe those you mention might not require that, but I think focusing on the fact that python doesn't have block-level scoping makes no sense. The python language before this PEP is not the same as the one after this PEP. The new one should have block-level scoping in this instance.

> The match block is more syntactically similar to if/while/etc. blocks, and so it makes sense that it would follow similar scoping rules.

I would argue that a stack of 'case's are a 'lambda' (and in particular a single 'case x: ...' is equivalent), and hence they should have function scoping like 'lambda' does.

'match' is a distraction; it's just a function call written backwards, i.e. `match x: f` is equivalent to `f(x)` (in this case Python happens to require `f` to be implemented using the stack-of-'case's form, but that seems incidental; similar to how 'raise' and 'except' are a general form of control flow, despite Python requiring values to be wrapped in something descended from 'Exception')

> I think they should have just gone the whole way and _not_ mutated outside variables. There seems to be little consistent with this addition to the language.

Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.

It would make even more sense for Python not to adopt language features that can't be made to behave consistently with the language's existing syntax and semantics without introducing horrendous footguns.

I've been programming in dialects of ML for much longer than Python, so I absolutely appreciate the attraction of this feature. But, the opinion I'm coming to as I think more about this is:

  1. I *love* pattern matching.
  2. But not in Python.
Exactly. At this point it feels like Python is LARPing at being an ML. Without even block scoping, this is just asking for trouble.
Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.
Actually, the opposite. Call me an old crank but I feel like Python should stay Python and not try to (badly) tack on ideas from ML.
No, not interested in block-level scoping in Python. Why on earth would I ask a programming language I rely on for getting important work done to introduce so massive a breaking change as changing its scoping style?

"I don't think feature X is a good fit for Python because it interacts poorly with existing Python features Y and Z" is not a tacit statement of support for changing features Y and Z. It's a statement that means exactly what it says.

Like I alluded to in my grandparent post, I use other languages that are not Python, and like their features, too. That does not necessarily mean I want all my favorite features from those pulled into Python. Nor do I want my favorite features from Python pulled into other languages.

The best analogy I can think of is a college friend once developed this sort of transitive theory of food that went, "If A tastes good with B, and B tastes good with C, then A must taste good with C." This resulted in a number of questionable concoctions, like serving cottage cheese on top of chocolate cake because they both taste good with strawberries.

For my part, as I maintain some complex academic research software, I would be much more interested in continuing support for Python 2, and more comprehensive numerical, math and vector libraries for Racket.
To be fair, lots of languages have been moving closer to ML recently (and new languages tend to look more ML-like than in the past). That includes the adoption of pattern matching, but also things like value objects, first-class functions, sum types, named tuples, algebraic datatypes, type inference, etc.

I don't think that's a bad thing. I do think care should be taken when incorporating features from other languages, to see how it interacts with existing features, what the best form of that feature would be, and perhaps whether some different feature could achieve a similar goal.

(For the latter point, I find it unfortunate that languages which already contain 'try/catch' have been introducing 'yield' and 'async/await' as separate features; rather than generalising to 'shift/reset' and implementing all three as libraries)

I haven't been programming in languages with pattern matching longer than Python and I still agree with you. Pattern matching is awesome, but it doesn't suit Python at all. I hope they reconsider adding it.
No, we'll rather fix issues people see with it.

Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.

> It would make even more sense for Python not to adopt language features that can't be made to behave consistently with the language's existing syntax and semantics

Current match behavior is consistent with existing syntax and semantics.

And no, not adopting new features known to be generally useful across programming languages is not "better". Instead, better to continue elaborating the language even after the initial pattern matching support is added.

Interested in block-level scoping in Python? Please post on the python-ideas mailing list.

Why did you chop the last four words off of that sentence you quoted? All it accomplishes is making it so that the response rebuts a statement that wasn't actually made.
Reassignment not mutation
Ah yes, you’re right.
(comment deleted)
(comment deleted)
(comment deleted)
> EDIT: The lack of block scoped variable thing does seem like a wart right enough.

This is not specific to "match" statement, but is a general issue in Python. And thus, needs to have a general solution, orthogonal to pattern matching. Are you interested? Please post to the python-ideas mailing list.

Reading the discussion, I think the criticism is more motivated by the subtle but crucial differences to most functional programming languages.
I was thinking more about the common criticism that something like `case Point2d(x, y):` "looks like an instantiation" and hence an equality check.

I actually replied to the wrong comment after reading several that visually looked similar at the time, so apologise for causing confusion in this subthread.

(comment deleted)
Oh, yikes. I understand what's happening here but this is going to bite a lot of people.

I might be misreading the grammar but it looks like you can produce the desired effect by using an attribute, e.g. the following would perform an equality check, not an assignment:

  match status:
      case requests.codes.not_found:
          return "Not found"
The tutorial seems to confirm this: "This will work with any dotted name (like math.pi). However an unqualified name (i.e. a bare name with no dots) will be always interpreted as a capture pattern"
(comment deleted)
Yes, apparently your example will work correctly.

Now think what will happen if you need to move that not_found variable to the same file as that code (so it will no longer be a dotted name). If you do it manually you need to be extra careful, if you use an automatic tool either it will reject the change or will need to create a dummy class or something in the process.

The inconsistency just makes it worse and will lead to even more bugs. I foresee lint rules prohibiting match statements in the near future.
Couldn't they have used "case" and "capture" clauses instead?

  match status:
     case not_found:
         return "Not found"
     capture success_code:
         return "Returned success code: %s" % success_code
or the "_ as x" or a myriad other ways to make capture explicit...
A separate statement doesn't compose with complex patterns. I'd prefer always requiring the walrus operator to capture:

    match response:
      case (not_found, msg := _):
        return f"Not found: {msg}"
      case (error, "No swizzle available"):
        return "We lack swizzle. :-("
      case pair := (_, "Swizzle"):
        log(f"Unexpected swizzle: {pair}")
        return f"Swizzle?"
      case ((code := _), _):
        return f"Success: {code}"
That looks pretty counter intuitive even after this explanation
(comment deleted)
That's not too bad if it would be a syntax error to either set or shadow an existing variable with the match statement. Apparently it isn't, which is concerning. Personally I think I may have preferred something like:

    match status:
        case == not_found:  # Check for equality
            ...

    match status:
        case as not_found:  # bind status to variable not_found
            ...
At least the former should be an option instead of using a dotted name IMO.

You know, a lot of potentially confusing behavior would be avoided if programming languages had the sense to make variables read-only by default and disallow variable shadowing altogether.

I've been scanning the docs and this syntax is hard for me to understand, however your version makes sense right on first glance. I'd go with that.
I really like shadowing, since it prevents me making mistakes all over the place by referring to the wrong thing. If I introduce a new name, I have two names cluttering up my namespace, and might pick the wrong one by mistake; for example if I validate 'myInput' to get 'myValidatedInput', later on I can still refer to 'myInput', which would be a mistake, and may end up bypassing the validation. On the other hand, I can shadow 'myInput' with the validated result, meaning that (a) I can no longer refer to the value I no longer want, (b) there's only one suitable "input" in scope, so it's easier to do things correctly, (c) I don't have to juggle multiple names and (d) it's pure and immutable, and hence easier to reason about than statements (like 'del(myInput)' or 'myInput = validate(myInput)'.
>I really like shadowing, since it prevents me making mistakes all over the place by referring to the wrong thing. If I introduce a new name, I have two names cluttering up my namespace, and might pick the wrong one by mistake;

Compared to having two versions of the same name, one shadowing another?

Yes. For example:

    def neighbourhood(position):
      return map(
        lambda position: EDGE if position is None else position.absolute,
        position.neighbours
      )
The inner lambda is shadowing the name 'position'. This does two things:

1) It declares that the lambda doesn't depend on the argument of 'neighbourhood'

2) It prevents us referring to that argument by mistake

Compare it to a non-shadowing version:

    def neighbourhood(position):
      return map(
        lambda neighbour: EDGE if neighbour is None else position.absolute,
        position.neighbours
      )
Oops, I've accidentally written 'position.absolute' instead of 'neighbour.absolute'!

This version doesn't make any declaration like (1), so the computer can't help me find or fix the problem; it's a perfectly valid program. A static type checker like mypy wouldn't help me either, since 'position' and 'neighbour' are presumably both the same type.

It's not even clear to a human that there's anything wrong with this code. The problem would only arise during testing (we hope!), and the logic error would have to be manually narrowed-down to this function. Even if we correctly diagnose that the 'if' is returning a different variable than it was checking, the fix is still ambiguous. We could do this:

    EDGE if position is None else position.absolute
Or this:

    EDGE if neighbour is None else neighbour.absolute
Both are consistent, but only the second one matches the shadowing example.
> Oops, I've accidentally written 'position.absolute' instead of 'neighbour.absolute'!

I'm going to be honest here, the number of times I've made that kind of mistake is absolutely dwarfed by the number of times I have used the wrong variable because I had accidentally shadowed it.

Neither mistake is super common, but I can't recall ever writing 'position.absolute' instead of 'neighbour.absolute' unless I legitimately needed both position and neighbour in scope and the problem was hard to reason about. I can recall accidentally reusing a variable like 'x' as an iteration variable and then using the wrong 'x' because I forgot, and I can also recall misunderstanding what some piece of code did because I thought 'x' was referring to the outer scope but I had missed that it was shadowed by another declaration. Shadowing has caused me many more problems than it solved, at least in my own experience.

>Oops, I've accidentally written 'position.absolute' instead of 'neighbour.absolute'!

That's a contrived example though, if I ever saw one.

I don't think that's the kind of issue people commonly have, compared to misuse of shadowed variable further down the scope.

And for your example, a better solution would be for the close to declare what it wants to use from its environment. Python doesn't allow this syntax, but some languages do:

def neighbourhood(position): return map( lambda neighbour use (): EDGE if neighbour is None else position.absolute, position.neighbours )

Now the compiler can again warn you, since you're only allowed to use neighbour in the lambda.

(comment deleted)
Yes, and this is incredibly disappointing.

Couldn't we achieve the same functionality with a little less ambiguity using the following syntax?:

    not_found = 404
    match status:
        case not_found:
            return "Not found"
            
        case _ as foo:
            do_something(foo)
            return "Match anything"
it even works for the example in PEP 365

    match json_pet:
        case {"type": "cat", "name": _ as name, "pattern": _ as pattern}:
            return Cat(name, pattern)
        case {"type": "dog", "name": _ as name, "breed": _ as breed}:
            return Dog(name, breed)
        case _:
            raise ValueError("Not a suitable pet")
This would be really confusing to anyone who is used with match statements in other languages
For 2 seconds until they learn what it means. If anything, it's more explicit what exactly it does.

There are 100s of things in Python who would be confusing to people used to other languages, this is not one of them.

The general tripping up of binding vs mutation vs assignment vs initialization is a pervasive Python issue. This just continues to double down on exacerbating the problem.
Comparing garbage syntax to worse syntax and saying “it’s not as bad as that” isn’t the way one improves upon a language.
Which is neither here, nor there.

This syntax is clearly an improvemen to the PEP syntax.

And parent said it's "confusing". Well, it's less confusing than the PEP syntax.

Nobody compared it to not adding it at all, or was concerned with improving the language aside from the pattern matching case here.

Almost everyone in this discussion is making exactly those comparisons. Including yourself. When you're discussing usability issues due to changes to the syntax, the perspective of non-exclusive developers vs full time Python devs doesn't change the underlying context of the discussion regarding the change in usability.

And I stand by my position that defending a bad decision because of the existence of worse decisions is a really piss poor counterargument.

Disclaimer: I'm a language designer myself so I know first hand how hard it is to get these things right.

>And I stand by my position that defending a bad decision because of the existence of worse decisions is a really piss poor counterargument.

This thread was just about the two alternatives (the PEP and explicit capture), not about the PEP in general, or about defending the PEP or even saying that the better alternative is "good". We just say it's better than the PEP. Not sure how you read that into what we wrote.

>Disclaimer: I'm a language designer myself so I know first hand how hard it is to get these things right.

Then go make that argument in some thread in this post discussing the PEP proposal in general?

> This thread was just about the two alternatives (the PEP and explicit capture), not about the PEP in general, or about defending the PEP or even saying that the better alternative is "good". We just say it's better than the PEP. Not sure how you read that into what we wrote.

Bullshit. You said, and I quote, "There are 100s of things in Python who would be confusing". That's what I was responding to. And my point stands: just because there are other pain points in Python doesn't mean we should accept more pain points into it.

> Then go make that argument in some thread in this post discussing the PEP proposal in general?

I’d prefer to make that argument in the thread where I was already talking you about the PEP proposal in general. ;)

But the existing semantics are very confusing to anyone who is used to Python, which seems like a bigger problem.
I don't agree, I'm used to Python and this is not confusing to me because I have learned to use match statements, you will do too.
I dig it. 'as' primes my brain to something going into a scoped context.
It’s fine. Python just introduced expression assignment. Why not use that?

    match subject
        case [first := _, rest := _]
            return [rest, first]
Well get to Perl eventually
Note that the original PEP622 relied on ":=" initially, but they found some corner case with it (apparently, operator precedence related) and switched to "as" in PEP634.
That works too but I guess I am still getting used to the walrus operator. What really bugs me about this PEP is they are going through all this effort to introduce match but they don't have match assignments, which is personally one of my favorite things about match.
I'm still wary about this change making it in to Python, but I like this suggestion. It makes the assignment clear. The way it's currently specified would definitely trip me at some point.
I like your proposal. The `as` keyword is very readable to me.

I would have preferred `as` instead of the walrus operator too.

This is how pattern matching works in any language (except for some niche languages that allow nonlinear patterns, like Curry).

Many of the comments here reveal a bizarre parochialism.

In that case, what’s the correct way to write a clause that only matches if `status` is equal to 404? Do we have to use the integer literal 404 instead of a named integer?
Yes, of course. Again, this is how it works in basically every mainstream language with this feature.
Really? It’s best practice to use a magic number rather than a name?

That breaks one of the most fundamental rules for writing good code.

If you really need to compare against a variable, use an "if". The primary benefit of pattern-matching is destructuring.

EDIT: It looks like you actually can match against constants with this PEP, as long as you access your constant with a dot (e.g., HttpError.NotFound). This seems like a perfectly reasonable solution to me.

No it’s not because it’s none obvious and requires a fair amount of boilerplate code. Both of which are usually idioms Python normally tries to avoid.

I guarantee you this will trip up a lot of developers who are either learning the language for the first time or who Python isn’t their primary language.

Worse still, the kind of bugs this will lead to is valid code with unexpected pattern matching, which is a lot harder to debug than invalid code which gets kicked out with a compiler error.

There are PatternSynonyms in Haskell.
It will be to stick your constants in a module and use module.constant to match them. Or use an enum. Or hang them off a class.
Not the scoping rule. For example, in Haskell:

    let x = foo
     in (
       case bar of
         x -> x,
       x
     )
This will give `(bar, foo)`: for the first element the `x` in the case will match against `bar` and return it, then that `x` will be discarded as we leave its scope; the second element uses the binding of `x` in the `let`.

According to the Python semantics we would get `(bar, bar)`, since we only have one `x` variable. When the case pattern succeeds, the existing `x` is updated to refer to `bar`. Hence we get the `bar` we expect in the first element, but we also get `bar` as the second element, since that `x` variable was updated. (Note that Python guarantees that tuple elements are evaluated from left to right).

That's just an inevitable consequence of the fact that, in Python, "scope" is synonymous with "dictionary attached to some object." This is already how for-loops work.
It's not "inevitable". They could have required local variables in case statements to be local to that case statement. It would have required changes to the "scope is synonymous with dictionary attached to some object" idea or maybe it would have required a dictionary to be attached to a case statement. I personally think local scope should have been viewed as a hard requirement if they were to introduce this to the language.
Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.
I'm interested in sane semantics. In this case, that calls for block-level scoping. Those who introduced pattern matching should have understood that the lack of block-level scoping _before_ this PEP does in no way support the continuing of the status quo. The language after this PEP has changed and has turned into one where block-level scoping is appropriate in this case.

I'm honestly _not_ interested in block-level scoping in this case because I would _never_ have wanted this PEP to be accepted. This feature was quite controversial on the various python mailing lists, and yet the steering committee accepted it anyway. The steering committee might consider leading with a bit more humility and _not_ accepting such controversial PEPs. This is an example of language devolution and not evolution.

It occurs to me that there's a nice way to understand this from what's happened in Scala.

Scala has always had built-in syntax for pattern-matching, like:

    foo match {
      case bar => ...
      case baz => ...
    }
However, Scala also has a thing called `PartialFunction[InputType, OutputType]`, which is a function defined 'case by case' (it's "partial" because we're allowed to leave out some cases). This is essentially a re-usable set of cases, which we can apply to various values just like calling a function.

For example we can write:

    val f: PartialFunction[A, B] = {
      case bar => ...
      case baz => ...
    }

    f(foo)
Scala also allows us to attach extra methods to certain types of value, via 'implicit classes' (which were added late on in Scala's history, although similar patterns were available before). As of Scala 2.13, the standard library attaches a method called `pipe` to values of every type. The `pipe` method simply takes a function and applies it to this/self. For example:

    val f: PartialFunction[A, B] = {
      case bar => ...
      case baz => ...
    }

    foo.pipe(f)
However, now that we have these two things (`PartialFunction` and `pipe`), it turns out we don't need explicit syntax for `match` at all! We can always turn:

    foo match {
      case bar => ...
      case baz => ...
    }
Into:

    foo.pipe({
      case bar => ...
      case baz => ...
    })
Hence Scala, in a round-about way, has shown us that pattern-matching is essentially a function call.

When it comes to Python, it doesn't even need to be a discussion about block scope; it's equally valid to think of this as function scope (like Python already supports), where `case` acts like `lambda`, except we can define a single function as a combination of multiple `case`s (like in the Scala above).

As said many times already, then you have the opposite problem - how to get value from "inner" to "outer" scope. If we talk about function scope, then it requires "nonlocal" declaration in the inner scope. From Python, too many declaration like that are syntactic litter. It has a scoping discipline which allows to avoid them in most cases, and that works great in 90% of cases (popularity of Python and amount of code written in it is there proof).

Yes, there're still remaining 10%, and pattern matching kinda drew attention to those 10%. I'm interested to address those, and invite other interested parties to discuss/work together on that. The meeting place is python-ideas mailing list.

> If we talk about function scope

Note that I'm not simply saying 'match should have function scope', I'm saying that 'case' is literally a function definition. Hence functions defined using the 'case' keywork should work the same as functions defined using other keywords ('def', 'lambda' or 'class').

> you have the opposite problem - how to get value from "inner" to "outer" scope

The same way as if we defined the function using 'lambda' or 'def' or 'class'

> it requires "nonlocal" declaration in the inner scope

That's not a general solution, since it doesn't work in 'lambda'; although this exposes the existing problem that there is already a difference between functions defined using 'def'/'class' and functions defined using 'lambda'. Adding yet another way to define functions ('case') which defines functions that act in yet another different way just makes that worse.

> I'm saying that 'case' is literally a function definition

And I don't agree with saying it like that. I would agree with "a 'case' could be seen as a function definition". In other words, that's just one possible way to look at it, among others.

Note that from PoV of the functional programming, everything is a function. And block scope is actually recursively lexical lambda.

And OTOH function inlining is a baseline program transformation. Currently in Python, whether a syntactic element (not explicitly a function) gets implemented as a function is an implementation detail. For example, comprehension happen to be implemented as functions. But just as well they could be inlined.

Note that function calls are generally expensive, and even more so in Python. Thus, any optimizing Python implementation would inline whenever it makes sense (called once is obviously such a case). (CPython hardly can be called an optimizing impl, though since 3.8, there's noticeable work on that).

I mentioned that in other comments, and can repeat again, there were 2 choices: a) add initial pattern matching to reference Python implementation; b) throw all the work into /dev/null and get back to dark ages where pattern matching is implemented in hacky ways by disparate libs and macros. Common sense won, and a) was chosen. Pattern matching will be definitely elaborated further.

> This feature was quite controversial on the various python mailing lists

I'm also on various Python lists, and what I saw that various details were controversial, not pattern matching itself. Mostly, people wanted pattern matching to be better right from the start, just like many people here. Well, I also want Linux version 234536464576.3.1-final-forever, but instead run 5.4.0 currently, and install new versions from time to time. The same is essentially with Python too.

> throw all the work into /dev/null and get back to dark ages where pattern matching is implemented in hacky ways by disparate libs and macros.

How does not accepting this PEP throw anything away? It's literally right there. It's still hosted there on the PEP site. Those who want pattern matching can continue to refine the work. "Common sense" requires understanding the current work is a sunk cost and in no way supports its introduction into the language.

> I'm also on various Python lists, and what I saw that various details were controversial, not pattern matching itself.

The details of the PEP are the problem, not the idea. Not accepting this PEP is not the same as rejecting pattern matching. This is only one possible implementation of pattern matching. It's also a bad one and one that makes the language worse. Rejecting this PEP allows a better implementation in the future.

> It's still hosted there on the PEP site.

On the PEP site, https://www.python.org/dev/peps/ , there're a lot of deadlocked PEPs, some of them a good and better would have been within, than without.

> Rejecting this PEP allows a better implementation in the future.

Let's count - 3rd-party patmatching libs for Python exists for 10-15 years. And only now some of those people who did their work as "third parties" came to do it inside mainstream Python.

The "future" you talk about is on the order of a decade. (Decade(s) is for example a timespan between 1st attempts to add string interpolation and f-strings landing).

I myself was ardent critic of PEP622/PEP634. I find situation with requiring "case Cls.CONST:" to match against constants to be unacceptable. But I'm pragmatic guy, and had to agree that it can be resolved later. The core pattern matching support added isn't bad at all. Could have been better. Best is the enemy of good.

> On the PEP site, https://www.python.org/dev/peps/ , there're a lot of deadlocked PEPs, some of them a good and better would have been within, than without.

If it's deadlocked, it really _shouldn't_ be added.

> Let's count - 3rd-party patmatching libs for Python exists for 10-15 years. And only now some of those people who did their work as "third parties" came to do it inside mainstream Python.

What's wrong with multiple implementations? Maybe people want different things? Besides the implementations' existence shows that lack of language support isn't something that blocks the use of pattern matching. Also moving it into the language doesn't mean people will work on that one implementation. Haven't you heard that packages go to the standard library to die? Why would it be any different in the python language. Besides I'm sure that the 3rd party libs will continue to be used anyway.

> But I'm pragmatic guy, and had to agree that it can be resolved later. The core pattern matching support added isn't bad at all. Could have been better. Best is the enemy of good.

I'm pragmatic too. I understand that I can do everything that this PEP introduces without the change to the language. I also understand that this PEP could continue to be worked on and improved. It's true that best is the enemy of good. I (and obviously many others here) believe that this is _bad_.

> What's wrong with multiple implementations?

It's absolutely great, and I'm saying that as someone working 5+ years on an alternative Python dialect (exactly with a motto of "down with toxic lead-acid batteries").

> Also moving it into the language doesn't mean people will work on that one implementation.

Only on that one - god forbid. But gather around that particular implementation to make it better and polish rough edges - for sure. (While the rest of impls will remain niche projects unfortunately.)

> I (and obviously many others here) believe that this is _bad_.

And me, and many others, believe it's good ;).

Well I guess the most useful information I've gotten out of this thread is that there are many other implementations already. I'll try to remember that the next time I see someone use the PEP version in one of my python projects so I can recommend them to use one of the third-party libs. I see no reason to believe they'd be any worse than this.
The fact that you weren't even aware that 3rd-party pattern matching solutions for Python existed before, makes me hard to believe that will put your actions where your words are. Mere searching on Github would gives 156 hits: https://github.com/search?q=python+pattern+matching . Divided by 2 for mis-matches, it's still sizable number of projects.

And that's problem #1 - you'll have hard time to choose among them (even though there're projects with 3.3K stars; but that of course doesn't mean such a project is the "best"). And secondly, many of them are indeed "worse" in the sense they're less general than the PEP version. Third common problem is sucky syntax - unsucky one require macro-like pre-processing of the source, and sadly, that's not a common norm among Python users (it should be, just as the availability of the block scope). I bet you will chicken out on the 3rd point, if not on first 2 ;-).

So yes, "official" support for pattern matching was in the dire need to organize the space. Now, 3rd-party libs can clearly advertise themselves as "We're like official patmatching, but fix the wart X/Y/Z". Bliss.

> The fact that you weren't even aware that 3rd-party pattern matching solutions for Python existed before, makes me hard to believe that will put your actions where your words are.

Well of course I won't use it myself. I don't find it necessary in python. My simple policy will be stand against any usage of this language feature in any code I write or contribute to. Those who want to use cases can either use other language features or third-party libraries which I'd have to study as well. Are you seriously looking down upon me because I haven't used third-party libraries that I consider unnecessary?

> And that's problem #1 - you'll have hard time to choose among them

This point is nonsense. All this shows is there is no agreement on how a third-party package should implement this feature. If anything, it argues against its inclusion in the language.

> And secondly, many of them are indeed "worse" in the sense they're less general than the PEP version.

All this says is that the PEP version isn't the worst implementation out there. It in no way implies that it should be included in the language.

> Third common problem is sucky syntax

So far this is the only time in all your posts in this thread that I've seen you give one reasonable argument. Congrats it took you long enough. So I'll give you this. Make the semantics non-idiotic (i.e. at least fix scoping as well as don't treat variable names and constants differently) and I'll accept it. I'm personally not against pattern-matching. I don't consider necessary by any stretch, but if its design makes sense it is at worst benign.

> So yes, "official" support for pattern matching was in the dire need to organize the space.

It's funny how the vast majority of feedback I see on the internet argues otherwise. It seems pretty clear this was neither needed not implemented well.

Anyway I'll bow out here. You seem less interested in learning what people outside of python-list actually care about or want and more interested in explaining why python-list's position is right. It requires impressive lack of self-reflection. Anyway pattern matching is in. The current form will make python a little worse as a language, but it's still overall quite good language. Maybe improvements will be made to make it tolerable (though I doubt it if your attitude is representative of python-list/python-dev/etc.). If stupidity like this keeps up the language will just slowly devolve, but it's not likely to be a bad language for many many years yet and well there are always other languages to choose from. It's unreasonable to expect a group to make good decisions forever.

> My simple policy will be stand against any usage of this language feature in any code I write or contribute to.

Dude, you're just like me! I have the same attitude towards f-strings ;-). Except I know that I will use them sooner or later. But I'm not in hurry. You maybe won't believe, but I found a use even for ":=" operator.

> So far this is the only time in all your posts in this thread that I've seen you give one reasonable argument.

Oh, you're so kind to me!

> You seem less interested in learning what people outside of python-list actually care about or want and more interested in explaining why python-list's position is right.

I'm a flexible guy. On Python lists, I'm argue against f-strings, assignment operators, and about deficiencies in proposed pattern matching. On interwebs with guys like you, I'm arguing trying to help them see the other side. And no worries, your opinion is very important to me.

> Let's count - 3rd-party patmatching libs for Python exists for 10-15 years. And only now some of those people who did their work as "third parties" came to do it inside mainstream Python.

Well, somewhat tongue-in-cheek, why not introduce a macro system into Python which allows to experimentally implement such syntactic changes as a library?

First of all, macro systems for Python exist for decades (just as long as pattern matching, and indeed, many patmatching implementations are done as macros). One well-know example of both is https://macropy3.readthedocs.io/en/latest/pattern.html

Secondly, there's a PEP to embrace macros in CPython (instead of pretending they don't exist, and leaving that to external libraries): https://www.python.org/dev/peps/pep-0638/

But the point, you don't need to wait for official PEP to use macros in Python. If you wanted, you could do that yesterday (== decades ago). And I guess in absolute numbers, the same amount of people use macros in Python as in Scheme. It's just in relative figures, it's quite different, given that there're millions of Python users.

So, if it isn't finished, why do people not use pattern matching implemented as a macro, until things like scoping are ironed out?
For as long as you're a human and belong to category of "people", you can answer that question as good as anyone else. And your answer is ...?

(Just in case my answer is: https://github.com/pfalcon/python-imphook , yet another (but this time unsucky, I swear!) module which allows people to implement macros (among other things)).

> Well, I also want Linux version 234536464576.3.1-final-forever, but instead run 5.4.0 currently, and install new versions from time to time. The same is essentially with Python too.

Just one thing, if mainline Linux would work like Python in respect to stability of APIs and features, you could start to debug and re-write your system after minor kernel upgrades. Linux does not break APIs, and this is possible because people are very careful what they implement - they will need to support it for an indefinite future.

Of course you can make patched branches and experimental releases of the kernel, these exist, but few people will use them, for good reasons.

The Linux kernel has a document imaginatively called "stable API nonsense": https://www.kernel.org/doc/Documentation/process/stable-api-...

But the talk was not about that, it was about the fact that we want to get "finished software", but as soon as we ourselves deliver software, we vice-versa want to do it step by step, over long period of time. One day, we should get some reflection and self-awareness and understand that other programmers are exactly like ourselves - can't deliver everything at once.

What you cite appears misleading to me - the text by Greg Kroah-Hartman talks very clearly about interfaces within the Linux kernel, not interfaces between kernel and user space, such as the syscall interface, which are stable. If you want to read the position of the lead Linux kernel developer on breaking user space APIs, here it is, in all caps:

https://linuxreviews.org/WE_DO_NOT_BREAK_USERSPACE

And here is the rationale why:

https://unix.stackexchange.com/questions/235335/why-is-there...

- you see that it makes perfect sense for a programming language like Python, too, to make only backwards-compatible changes (except perhaps if there are severe problems with a release).

In the same way, it does not matter how things are implemented within Python, but it matters a lot that the user interfaces, which includes in this case the syntax of the language, are stable.

And the fact that Python contrary to that does break backward compatibility - sometimes even in minor releases -, and continues to do so, is a reason that for my own projects I have come to the point at avoiding python for new stuff. There are other languages which are more stable and give the same flexibility, even at better runtime performance.

> you see that it makes perfect sense for a programming language like Python, too, to make only backwards-compatible changes

That's exactly what Python does of course (except when purposely otherwise, like 2->3 transition). And of course, that policy is implemented by humans, which are known to err.

> for my own projects I have come to the point at avoiding python for new stuff.

But who are you, do I know you? I know some guy who said that about Python and now develops his own Python-like language. Is that you? Because if you just consumer of existing languages, it's something different, there always will be a new shiny thingy around the corner to lure you.

> There are other languages which are more stable and give the same flexibility, even at better runtime performance.

Yes, but from bird's eye view, all languages are the same, and differences only emphasize similarities. So, in a contrarian move, I decided to stay with Python, and work on adding missing things to it. Because any language has missing things, and Python isn't bad base to start from at all.

List comprehensions were changed in Python 3 to avoid this, so I don't think it's quite that simple.
In Python 3, list comprehensions use generators behind the scenes.
> That's just an inevitable consequence of the fact that, in Python, "scope" is synonymous with "dictionary attached to some object."

What object is the scope of a comprehension (in Py 3; in py 2 they don't have their own scope) a dict attached to? And, if you can answer that why could there not be such an object for a pattern match expression?

> What object is the scope of a comprehension (in Py 3; in py 2 they don't have their own scope) a dict attached to?

The generator object that gets created behind the scenes.

> And, if you can answer that why could there not be such an object for a pattern match expression?

There could be, I suppose, just as there could be for "if" or "for". If Python decided to have lexical scoping everywhere, I would be in favor of that (but then people would complain about breaking changes). In lieu of that, I like the consistency.

>The generator object that gets created behind the scenes.

So like a match object that could get crreated behind the scenes?

If you have automatic block-level scoping, then you have the opposite problem - you need to do extra leg-work to communicate a value to the surrounding scope.

Anyway, anyone agrees that block-level scoping is useful. Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.

>If you have automatic block-level scoping, then you have the opposite problem - you need to do extra leg-work to communicate a value to the surrounding scope.

In the general case, you just declare the variable in the surrounding scope and then affect it in the lower one, no?

Right. But the whole idea of Python scoping rules was to not burden people with the need to declare variables (instead, if you define one, it's accessible everywhere in the function).

But yes, block-level scoping (as in C for example) would be useful too in addition to existing whole-function scoping discipline.

Again, I'm looking for similarly-minded people to move this idea forward. If interested, please find me on the python-ideas mailing list for discussing details.

> then you have the opposite problem - you need to do extra leg-work to communicate a value to the surrounding scope.

that would be far less likely to break things in an unexpected way, as in "explicit is better than implicit".

I am also wondering whether what is really missing here is perhaps a kind of imperative switch/case statement which has the explicit purpose of changing function variables.

Well, this "inevitable" consequence also infects pattern matching.
> This is how pattern matching works in any language

No, its not, but no language (at least that I am aware of) except python does pattern matching + local variables + not introducing a new scope with the pattern match.

Ruby is the closest, but it does introduce a new scope while providing a mechanism for binding variables in the containing local scope. (As well as a method to “pin” variables from the containing scope to use them in matches.)

Not introducing a new scope with a match is unfortunate, but it's also consistent with how every other language feature interacts with scoping.

> (As well as a method to “pin” variables from the containing scope to use them in matches.)

This is a good idea, I agree -- at least for Python, where you would obviously just call __eq__.

EDIT: It looks like you actually can match against constants with this PEP, as long as you access your constant with a dot (e.g., HttpError.NotFound). This seems like a perfectly reasonable solution to me.

> Not introducing a new scope with a match is unfortunate, but it's also consistent with how every other language feature interacts with scoping.

Except comprehensions, which changed in Py 3 to have their own scope, rather than binding control variables in the surrounding (function or module) scope as in Py 2.

> It looks like you actually can match against constants with this PEP, as long as you access your constant with a dot (e.g., HttpError.NotFound). This seems like a perfectly reasonable solution to me.

It would be except:

* You can't access function-scoped identifiers that way.

* You can't access module-scoped identifiers in the main module that way.

* You can't conveniently reference identifiers in the current module that way. (I think you can use the full module path to qualify the name in the local module, but that's both awkward and brittle to refactoring, and there's pretty much never a reason to do it for any other purpose.)

Interested in block-level scoping in Python? Please post on the python-ideas mailing list. Thanks.
>Many of the comments here reveal a bizarre parochialism.

This seems like a bizarre misunderstanding of Python scoping rules, and how this can be a problem here.

Are you sure the second one isn’t just declaring not_found as a stand-in for 404 so the case statement two lines below can refer to business logic rather than a “magic” constant?

I would NOT expect for the line “case not_found:” to reassign the status variable to 404 regardless of what it was before.

I can’t see how or why that would be intended behavior.

(comment deleted)
> Are you sure the second one isn’t just declaring not_found as a stand-in for 404

That's what it looks like, but no, the refactoring really does change an equality test into an assignment.

It doesn't reassign the 'status' variable, it reassigns the 'not_found" variable.
(comment deleted)
Assignments going wrong is par for the course in python! I still am surprised it is taught to beginners...
I will be waiting until pylint detects these sorts of footguns...
According to the specification pointed to by choeger in this comment https://news.ycombinator.com/item?id=26086589 , failed matches like that can also assign a value to existing variables, which seems even more problematic to me.
Thus, will be fixed sooner or later. (Sad it's not fixed right away, but neither me nor you proposed patches.)
A lot of early Python's appeal was in its simplicity. I miss that era. Soon Python is going to resemble C++'s bloated feature set where you're not supposed to use parts of the language because "that's the old way of doing things".

Edit: Actually that's already true with string formatting.

I agree completely.

I hate how different reading C++0x and C++17 is. I hate reading a "C++ style guide" for a new project.

I already feel that pretty soon, "Python Style Guide" will be a necessary evil...

Really? I program Python for over a decade now and compared to other language it was always the one where style somewhat was the smallest issue with that language (compared to the C, C++, C#, Javascript, Java I have seen).

Might be that the whitespace indentations focus the mind. Also: PEPs already act as style guides. And if you are worried to do it wrong, just run it through a code formatter like black. It really isn't that much of a problem IMO.

I think grandparent is talking about something else. Large C++ codebases often have a guide that says which parts of the language to use and not to use. That can be useful if you have a team with people with different levels of expertise in the language, or expertise in different parts of the language. C++ has grown to be a language with a lot of different parts to choose from.

And it looks like Python is heading in the same direction. It's not difficult to imagine a future where some Python projects are going to want such guides too.

Thanks for the clarification.
grandparent?
The parent comment of the parent of my comment, and by extension the writer of that comment.
It's difficult to balance it out correctly. F-strings were a significant improvement and I still think it was a great idea to include them and trade off some of the language simplicty for the added convenience. This new pattern matching... I'm not convinced yet.
Personally I don't feel f-strings are an improvement at all, but I think I can see why people prefer them. But a significant improvement, that I don't see at all. Can someone explain why e.g.

    f'gcd({a}, {b}) = {math.gcd(a, b)}'
is so much better than

    'gcd({}, {}) = {}'.format(a, b, math.gcd(a, b))
In the .format() version, the formatting and the values are separate and that makes it more clear to see both the formatting and the values. I really like that separation. When the expressions get more complex, .format() doesn't really lose any clarity while f'' gets pretty unreadable quite fast IMO. Yes, you can simplify by assigning the expressions to variables, but .format() doesn't even need that.
I find the f-string version better because I read from left to right and find it unnatural when my eyes need to jump around several times over the same expression in order to parse it. In your example that effect isn't too strong because there isn't a lot of additional text in the string and it's very clear what's going on either way.
> In the .format() version, the formatting and the values are separate and that makes it more clear to see both the formatting and the values.

It makes it harder to see which values actually go where though. I really dislike the separation.

Thank goodness we still have both ;)
> I really dislike the separation.

That's obviously a pretty fundamental difference in point-of-view. And it looks like your point-of-view is shared by a large majority.

"The reasonable man adapts himself to the world. The unreasonable man persists in trying to adapt the world to himself. Therefore, all progress depends on the unreasonable man." -- George Bernard Shaw

I try to be reasonable, so I guess I should try to come to terms with f-strings. In any case please don't depend on more progress.

> It makes it harder to see which values actually go where though.

You can name the variables in the format string and then pass the expressions as keyword parameters.

You can do that with f-strings too... but you don't need to because they're generally already clearer.
Once you get over five arguments in str.format(), it gets weird. I reverse engineered a product that required a lot of string formatting, and f strings made things more readable and debuggable.
Just wait until you get Python 3.9 and can do:

    >>> a, b = 2, 4
    >>> f"{math.gcd(a, b) = }"
    'math.gcd(a, b) = 2'
Sometimes you just want to quote something in a string real quick. In some cases you want to do something slightly more complicated.

And I really dislike format where the arguments are given by position. For short strings it's not going to matter but for long strings you're bound to mess something up sometime. Just imagine writing some kind of long email template and ending up with something like:

    '''Dear {}{}
    

    I write you on the {} to inform you...

    Kind Regards,
    {}'''.format('Mr. ', 'President', '1st of April', 'Ohio', '12', ..., 'Automated mail bot')
Okay it's a bit contrived but any long multiline formatted string is going to be hard to read without using keywords.

And once you insist things are named then f-strings look a lot neater since you get:

    f'gcd({a}, {b}) = {math.gcd(a, b)}'
v.s.

    f'gcd({a}, {b}) = {gcd_a_b}'.format(a=a,b=b,gcd_a_b=math.gcd(a, b))
Of course sometimes a template is the appropriate tool, but usually because it's going to be used more than once.
The first is more readable and performant, the second is much more error prone. One of the things folks complain about in Python is runtime errors, this is one part of the solution.

Is that enough?

I don't know about performant. If the difference is significant, that can be something to take into account, depending on the situation. Otherwise readability is more important.

So we come to readability, and I have to say that to me .format() is more readable. Honestly.

Regarding runtime errors, I presume you mean .format() doesn't prevent mismatches between the number of placeholders and the number of arguments, as in '{} {}'.format(42)? That's indeed a point in favor of f'', that I hadn't previously considered.

The eff string uses a special instruction that makes it not need to do expensive function lookups at runtime. These are costly in Python. Better in loops.

Readability is a win in my book. You mention too much is happening in your example string. Ok, perhaps it is. But remember, just because the interpolation supports expressions, doesn't mean you have to put them in there. I'd argue your function call would be better done on the line above, with only the result put in the string. This is the most readable version so far I think:

    c = math.gcd(a, b)
    f'gcd({a}, {b}) = {c}'
Syntax highlighting helps out as well.

Yes about placehoders. Also that in some cases of format() each variable is repeated a number of times:

    'hello {name}'.format(name=name)
When this is changed it is a bug magnet. DRY in a nutshell. Rarely is a feature a home run like this one. Not to mention I was using it in batch files, shells, and perl twenty-five years ago.
As I see it the less cognitive overhead I have when reading code the better. The more I have to think about the code I am reading the more likely I am to make a mistake because my brain is not a machine. With f" I simply read the code left and right and understand the output as I go. When I see the output I'm interested in I stop and see the context instantly. With .format() I read left and right, stop once I get to what I'm interested in, go back to count the {}s, remember the count, and finally count the arguments. Multiple places to make a mistake. And doing this when refactoring code is how you get bugs.
Thank you!

Like you - and I just learned about the existence of f-strings tonight - I find .format() to be much more readable and logical than f-strings.

My first impression reading about f-strings was "ew."

It's not a case of "I don't like it because it's new". To my jaundiced and admittedly subjective eyes, I'm still finding .format() better, precisely because it doesn't lose any clarity in exactly the way you stated.

In other words, I just don't get the point.

Furthermore! f? Really? What the f does f mean? The str() function is the str() function. As well as print(). As well as .format().

f? please

Obligatory Bjarne Stroustrup quote:

"There are only two kinds of languages: the ones people complain about and the ones nobody uses."

I think it's an interesting point you make, because Python is actually one of the few languages that tried to break away from its past (the whole 2.7 to 3.7 fiasco), but it seems the need for a language to be stable is just much, much greater – so features are just piled onto the existing standard instead.

If COBOL taught me one thing, it's that I'm pretty sure in 60 years time there will still be critical infrastructure running on Python 2.7.

That quote is seriously overused, to the point where valid criticisms will be met with disapproval simply because the language falls under condition (1).
There are quotations people complain about and ones that no one recognizes.
> There are only two kinds of languages: the ones people complain about and the ones nobody uses.

My old boss used to say this thought terminating cliche whenever I encountered a problem with PHP. It's a rather useless form of binary thinking, since it completely ignores scale and severity.

It's similar to statements like "all politicians are bad", which put the likes of Nick Clegg in the same bag as, say, Pol Pot. It's counter-productive to any meaningful discussion.

(comment deleted)
> It's a rather useless form of binary thinking, since it completely ignores scale and severity.

You may also be interested in: "there are 10 kinds of people - those who understand binary, and those who do not"

I also think using any quote as a thought terminating cliché is flawed. In that case I wouldn't blame the quote though, but the person using it. (I assume your boss probably had an interest in terminating thought. Perhaps the project was invested in the language and it was just not feasible to change? Perhaps he didn't have an answer to your valid criticism? Just speculation...)

In most cases I have encountered people uttering this quote they were using it as a mere observation of the statistical fact that a large group of users will inevitably lead to a larger amount of complaints. And the conclusion was usually something like: "Don't blindly disregard a language, because many people hate it. Always take a closer look if the tool is up to the job."

The issue was not the breaking changes, the issue was that it was done poorly. In particular that v2 and v3 code could not coexist in the same project.
I think that they did it about as well as they could given the driving motivation behind the change. The semantics of existing syntax changed with Python 3: 'foo' is a string of ascii bytes in Python2, but a string of unicode characters in Python3. Treating a string as both ASCII and Unicode within the same project, depending on which codebase it originated in, seems like a recipe for disaster.

Java => Kotlin is an example of a migration that's relatively painless, by design, and lets you incrementally convert projects over, source file by source file. But the Kotlin designers intentionally left alone certain misfeatures of Java to make that happen. It's still UCS-2 rather than UTF-8, for example, and it syntactically papered over nulls rather than actually fixing them with a true Optional monad like more recent languages, and its coroutine/async/await semantics aren't as elegant as Go or ES6 or Python 3.6. Changing runtime semantics of an existing language ecosystem is hard.

I understand your feeling and I would tend to agree for things in other domains.. but for a programming language, I find that if:

(1) It doesn't make the language less efficient

(2) The new feature is coherent with the rest of the language

(3) It doesn't make learning the language harder

(4) It brings joy in my life (as f-strings do)

Then.. it's ok :-)

I have never seen f-strings! It's my lucky day! https://xkcd.com/1053/

Time to refactor the project I am currently working on that is loaded with `str.format()`

f-strings are truly amazing. They're very painless. The only gotcha I've run into is accessing a dict from inside one. You need to watch out for your quotations :).
My most recent project makes liberal use of `f"""..."""`
Be aware that the only time this won't work is if you need to re-render the f-string with different context. For example you can do this:

    s = "this is {}"
    print(s.format("possible"))
but you cannot do that with f-strings because they are resolved on definition, so you could do something like this:

    for message in messages:
        print(f"The message is {message}")
But not this:

    s = f"The message is {message}"
    for message in messages:
        print(s)
This is the same drawback of JavaScript's Template Literals [0].

EDIT: Fix syntax in examples.

[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Take a look at flynt, a tool that can convert older string formatting code to the newer fstring style. I've found it very helpful!

https://github.com/ikamensh/flynt

Can confirm. I replaced 1.5k instances in our code base in seconds using flynt and it knew the ones that couldn't be swapped out directly. Really great tool. You only ever need it once in your life, but that one time it's amazing.
Having not written Python in a long time until recently, I discovered fstrings. Are they the best choice for (nearly all) string formatting at least in new code? They seem to work pretty similarly to eg Ruby’s string interpolation.
Yes. I’m not sure about speed, but syntactically they are far superior and better for all cases, unless of course you’re wanting to pass templates as an input, but that’s pretty obvious.
Have you seen this? :)

    x, y = 1, 2
    print(f'{x=}, {y=}')
I haven't but that is awesome. That is a much better syntax than just outputting data with `print(x, y)` and remembering which came first (when debugging of course).
There are some 'invisible' downsides to adding new features, which often get overlooked:

- Tooling, code analysers, etc. have to be updated to understand the new features. Tools which are dead or "finished" may stop working.

- The possibility that someone might use a new feature can break guarantees that are relied upon by existing code. An obvious example would be adding try/catch to a language: in languages without this, a function call is guaranteed to either return control to us, or to run indefinitely. Libraries might rely on this to clean up file descriptors, etc. afterwards. Such libraries would be broken by the mere existence of try/catch; even if we never actually use it.

That said, I'm a big fan of pattern-matching, so it would be nice to have in Python, rather than crude approximations like '{pattern1: A, pattern2: B, pattern3: C, ...}[myVal]'

Pattern matching will definitely make Python harder to learn, though. It’s not particularly coherent with the rest of the language either - the match statement has been described as “a DSL contrived to look like Python, and to be used inside of Python, but with very different semantics” [0].

[0] https://discuss.python.org/t/gauging-sentiment-on-pattern-ma...

I find it both incredibly humorous and saddening to see Python devs excited about finally getting string interpolation.
String formatting with f"" has been the new sheriff in town for a while and it is really killer.
Yes, and it also takes approx 11 seconds to learn, since str.format already had exactly the same syntax, just slightly more verbose.
I never understood why they didn't remove the previous ones.

Isn't there supposed to be only one obvious way of doing these things?

You can't just remove things, that'd break stuff!
That is why scala has scalafix[1] where library maintainers can provide rules along with newer versions of libraries that can be used to automatically, either at build time or out of band on the source, translate old constructs/patterns in the new.

See it as deferred refactoring for library maintainers. Library maintainers that work in a monorepo with their users get this refactoring feature for free.

[1]: https://scalacenter.github.io/scalafix

I have two thoughts on this: One is that tools like scalafix no-doubt rely heavily on static analysis to ensure correctness of their program transformations. I'm not sure a hypothetical "pythonfix" tool will be quite as feasible for untyped code bases.

And also, the issue isn't really with library maintainers when it comes to Python syntax changes. It's with all the user code written in Python. I tried converting my programs from Scala 2.12 from 2.13, and I failed so hard. I couldn't justify the time commitment, even with tools like scalafix to automate stuff. It's just not that smart.

All this is to say, let's not pretend that breaking changes to a programming language are ok. If you have to do break stuff once or twice in the lifetime of your language, fine. But accepting breaking changes as the status-quo is a failure of a language, by definition, IMO.

lib2to3 exists and is used by tools to do this kind of automatic refactoring for python. The idea of having a migration path to the "blessed" way to do things is pretty neat!
This is why the language is getting bloated. People shouldn't be so obstinately against breaking changes.
This is the same orange site that thinks the Python 2 to Python 3 breaking changes are still relevant and worth arguing about in 2021.
This is quite an alarming attitude that seems to be most common among the Python community. It seems like the 2->3 transition filtered out devs who felt strongly about backwards compatibility and the remaining devs have relatively little patience for objections against breaking changes.

There is a huge benefit to keeping existing code working as it is. Both for businesses and the ecosystem. It benefits everyone if the language does everything it can to keep that intact.

As an occasional python developer - I remember you could use `from future import <function>` to use python3 features in python 2.x. perhaps it would be possible to introduce a `past` package to include deprecated language features? of course this would still break compatibility in some cases, but would make it much easier to update them
> perhaps it would be possible to introduce a `past` package to include deprecated language features?

Deprecated language features are, by definition, still present and need no special incantation to miss, though they may produce Deprecation warnings to encourage you to update your code. Removed features would, I guess, be something you could do this for, except that generally the whole benefit of removing features is getting rid of the supporting code and the effort of maintaining it and it's interactions with new code.

  greeting = "hello {}"
  greet = partial(str.format, greeting)
  greet("world")
How would you pass a variable format string to f"" ?
Or read the string to be formatted from a file, to name another use case.
You can use variables in the formats.

  >>> flt = 1.2345
  >>> two = 2
  >>> f"{flt:.{two}f}"
  '1.23'
That is the opposite of the problem being posed. The question is how you define `greet` using f-strings.

  greet = lambda x: f"hello {x}"
Why is the example

  greet = partial(str.format, greeting)
  greet("world")
as opposed to just

  greeting.format("world")

?
The real question is why they didn't just write:

    greet = greeting.format
It was implying you could use the str.format function as argument to other computations, something you cannot do with f-strings.
I mean, if you're comfortable with `functools.partial`, lambdas are no great stretch:

  greet_fstring = partial(lambda _,s: f"hello {s}", greeting)

  greet = lambda x: f"hello {x}"
What if "hello {x}" is not a static string but was read from somewhere else?

Surely this would not work:

  greeting = "hello {x}"
  greet = lambda x: f greeting
Something along the lines of...

    code = 'f"{input()} {x}"'

    lambda x: eval(code)
(Please don't do this)
Yes, I'll stick to using str.format :)

    code = 'f"here it is {eval(input())}"'
    (lambda: eval(code))()
k, there we go
You can’t use f-strings for that.
That was exactly my point.
That isn't the purpose for f-strings. You still have % and .format
Python already broke backwards compatibility once. It didn't got over well
I would support removing the weird '%' operator, but str.format definitely still has uses. For one you can't make templates using f-strings since they are immediately evaluated.
Yes, f-strings are now the obvious way to format strings _if_ the template is fixed (not variable at runtime) _and_ variables are defined in the current scope. There are many many cases where this does not apply: hence str.format is still required.
I believe it's been a cause of care for very easy and expressive string formatting syntax in other languages
And probably other situations, like perhaps corporate environments, where you're not supposed to use the new more advanced parts because not everyone on the team knows them. Python is evolving into several different languages. "There should be one-- and preferably only one --obvious way to do it." Yeah right. I'm waiting for a PEP that deprecates PEP 20, "The Zen of Python". It's become more and more clear that the core developers don't follow it all anymore.
I think this comment is super exaggerated. In my experience pretty much all features in Python 3 by far are really welcome. The only features you're usually not supposed to use are very obvious ones, inspect, metaclasses and nested comprehensions, which are super uncommon and feel like dark magic that you would obviously not want to use, or just shit ugly code in the case of nested comprehensions.
I agree with this up through Python 3.7. The recent updates though seem to be unnecessary or have obtuse syntax.
do you have specific examples? honestly I was against the walrus operator but having played with it for just two seconds I think it's really great and that people are making a bigger deal than needed about this
Yes, I think the general consensus on metaclasses is that if you don't already know what they are, then you probably don't need to use them. There's some metaclasses in Django, but it's pretty easy to see how you're expected to use them from examples, and you don't need to really understand what's going on.
What's the problem with nested comprehensions? How else do you create bi-dimensional stuff?
Not the same kind of nesting, is it?

  [[None for _y in range(y)] for _x in range(x)]
for two dimensions is fine, while

  [None for _x in range(x) for _y in range(y)]
(to create a flattened 2-dim list? excuse the dummy example) is just begging for later headache. Explicit looping is much more readable. Note the flipped order of expressions -- these two statements create the same iterators.

Not to mention, you can even reuse the name for extra evil:

  [i for i in range(2) for i in range(3)]
results in

  [0, 1, 2, 0, 1, 2]
Fwiw, I find creating a 2d array and then flattening (`itertools.chain.from_iterable([[None for _y in range(y)] for _x in range(x)])` to be both readable and reasonably concise, though yes explicit loops are also fine.
nice trick.. though if you are relying on the 'generator' aspect of comprehensions this may be problematic (will create in-memory copies)
I thought I’d learn Perl once so I started reading a book on it. I was shocked that literally the bottom half of every page was footnotes. I gave up after a few pages.

Too much mental overhead. Other people love that kind of stuff though.

This is the first time in the history of the internet that anyone has complained about too much documentation.
I was a Perl developer/scripter for the better part of 3 years - wrote a 2-way LDAP<-> HRIS synchronization system in it, complete with customizeable Schemas. Then, 3 or so years into my Perl experience, in which I still needed to look at template code every time I did a HashOfArray/ArrayOfHash, I tripped across some python code explaining how everything was objects.

Within a few seconds I tested that theory, by populating a list with dicts and voila - just worked. Close to the last day I ever touched Perl.

The python language expansions that have come recently, f-strings, walrus operator, and now matching - are great in that they don't make the language more complex - all three of these are easily explainable in a few minutes to the novice, and once they understand it, they can quickly (and profitably) incorporate it into their code.

I wan't there to be a steady drumbeat of these improvements that let me write more elegant code, more concisely.

Try and find a single python developer who would give up their f-strings now.

Yeah, this. I love Python, and I hated Perl. Even Java was a pleasure compared to Perl.

At one time, a trainer was trying to tell us Ruby was the new hotness (2009 or 2010, I think). I liked Ruby OK, but Python seems cleaner and we stuck with it. I have not felt the slightest regret.

> in which I still needed to look at template code every time I did a HashOfArray/ArrayOfHash

Honestly, that sounds like someone that didn't bother to learn what's actually going on and understand what the language was doing. I mean, that's fine, we all do that... but I wouldn't blame the language for that. Perl makes nested data structures very easy and obvious once you learn what's going on. You just need to actually learn it and not rely on the little shortcuts Perl allows you to use to never make that next step to learning exactly why.

Perl will "just do what you mean" so much that it papers over some of the early learning friction points enough that they build up to a later point. At that point, people either throw up their hands and move on because it's annoying when some shortcut they've relied on isn't working in this one weird instance, or they learn what's really going on (Hint: it's almost always either about understanding references or context at this stage) and it's no longer a problem.

> Try and find a single python developer who would give up their f-strings now.

Things like f-strings and list comprehensions in Python are the exact thing that Python devs used to point out as too complex in other languages (I mean, f-strings look to be pretty much just string interpolation). I think that's evidence that perhaps a language isn't always best suited for all audiences. Python used to be aimed at learning and easy of understanding and use. Now that it's often more targeted for complex engineering projects, you get stuff sneaking in that was specifically avoided by design initially.

"Honestly, that sounds like someone that didn't bother to learn what's actually going" - Yes, exactly! I never learned what was going on. I pinged our JAPH that was kind of my mentor, and asked how he had ever grokked it - and his answer was, he hadn't - he just yanked his template code each time as well.

The point I'm trying to make is not that I'm a good or knowledgeable developer (neither of which I am, despite having written tens of thousands of lines of perl) - but that the core essence of Python, is that people can use it quickly and profitably without being one. The cognitive hurdle to start using objects in python is tiny - and, once you get that - a lot of the stuff that you would hope works, just does. The language is very friendly to novice coders, and it's lack of implicit (for the most part) actions avoids a lot of unclear side effect.

For more complex projects, things like decorators and generators and type-hints, which are advanced, are available if you need them - but you can go a long way (sometimes forever) without ever touching them - that's not the case with simple data structures - you pretty much need to start working with HoA in perl if you want to do complex things, and I know people who have used perl for the better part of a decade, who have never done so - and were blocked from doing more interesting things.

That saddens me. It's not actually that complicated, but I think Perl's usage numbers have degraded enough that unless you're frequenting one of the online spots where those knowledgeable about the language gather, you're unlikely to encounter them in the wild anymore, and thus aren't getting a useful explanation.

The simple answer that probably would have solved almost all your problems is that you can use the reference syntax for defining things and it will look like and function the same as JavaScript 95% or more of the time, and the only time you'll need to do anything is when passing it to something that expects an actual array or hash. I mean, you can get away with taking JSON and changing colons into fat arrows and booleans into 0 and 1 and it will just work as a Perl data structure as is like 99.99% of the time. Data structure manipulation works similarly as well for the most part.

Oh my goodness kbenson - we had this identical conversation 7 years ago on HN. https://news.ycombinator.com/item?id=7869603
Ha, I always wondered how long it would take for me to repeat a conversation here with someone! I figured it was only a matter of time. Now I know how much. :)
I've always viewed javascript as verbose crippled perl with good vendor support. I mean, `"use strict";` ...
sounds like you shoulda been using objects to bind data. Moo is one of the best OO toolkits out there. Can whip up really good long lived, testable, easy to understand stuff fast with extensive use of `lazy => 1`
I programmed in Perl 5 for several years. Ugh. That language was hard to read, and some of the features (list vs. scalar context, for example) were awful. I have never wanted to write another Perl program. Capable, but painful to maintain.
Thought I'd try to balance out all the Perl hate in the sibling comments.

I still use, and love Perl. If you stick close to the original inspiration of using it to process text, it flows very naturally and isn't hard to read or understand.

I do get that the proliferation of sigils ($,@,%), breadth of built-ins, things like $_, complex dereferencing, and overall terseness are a visual putoff.

It may be that I still like it because it uses mostly the same functions as 'C'...things like stat(), getpwent(), ioctl(), and so forth that were already in my head. And it was way less tedious for strings and text, and no malloc/free to keep track of.

I'm glad I'm not the only one around still using and enjoying Perl. I'm just starting to play around in Python now with the plan to rewrite/replace a few older Perl scripts so while I may not get much use out of Perl going forward, it's been great for tasks like log file parsing and reporting.
First off I'll say that I'm very sympathetic to your point of view, and mostly agree with it.

But... after I learned Scala a bunch of years ago, working in any language without pattern matching has felt like significantly more of a chore than it used to be. It's like you've been doing something the hard way for years, someone teaches you an easier, better, more readable way, and just when you get used to it and love it, then tells you that you can't use it anymore.

Certainly that's not true of all possible high-mental-load new features that could be added to Python. But I don't want to believe that features that drastically increase ergonomics shouldn't be added to a language because they make the language less simple.

This feature could make a lot of code that people write quite often a fair amount simpler, though. I'm looking forward to this feature quite a bit.
Maybe the problem with C++ is that it tries too hard to preserve backwards compatibility? I think it makes some of the newer features clunkier. If the C++ standards folks did something like Google's Abseil and provided an upgrade tool while discontinuing old features they might have ended up with a better language. I understand something like that might not work for everyone, but from what I've gathered in talking to other devs, there is a strong distaste for the current state of C++.
> Maybe the problem with C++ is that it tries too hard to preserve backwards compatibility?

That's a funny one. The whole history of C++ is being backward compatible, originally with C.

I need to justify six digits in Germany - we're not the Bay Area.
I think str.format() was an improvement on from % formatting, and that the f strings are an improvement on str.format(). It's readable, concise, and it's familiar from other scripting languages.

I think the advantage of switch statments and pattern matching is that you know the scope of the thing you're switching or matching on, but you don't have that restriction in if/elif/else blocks. There's nothing keeping me from adding a condition like, `elif day == "Tuesday":`, that has nothing to do with the task at hand. When I see switch or match, I feel like I know what I'm getting into.

sorry for the downvote: please propose solutions that allow the language to evolve and not stagnate.

example: pylint-type tools that flag old ways of doing things, and suggest replacement syntax (e.g. https://pycodequ.al/docs/pylint-messages/r1717-consider-usin... )

I wouldn't treat 'evolution' as an inherently good thing; i.e. change for its own sake.

My first thought whenever someone proposes changing a language is "could that be done with a library instead?". If it's possible then I think libraries are preferable to language changes; if it's not possible and we decide to change the language, then it may be better (if possible) to make a change which fixes whatever prevented us from implementing such a library. Only if we can't do that would I resort to adding individual features to the language.

For example, Scheme has really good pattern matching, despite it not being part of the language. In fact, there are so many pattern matching libraries for Scheme that there are now attempts to standardise their APIs https://srfi.schemers.org/srfi-200/srfi-200.html

In scheme, or most other Lisps for that matter, you extend the language by writing a library, as the language extension tools are built-in and quite easy to use (macros). Very few other languages give you that kind of power, and certainly not python.
Already true with string formatting, map/filter versus comprehensions, plenty of other things.

Find me a modern language where that isn't true, though.

If developers actually knew what simplicity was they'd stop worshipping it.

Brainfuck is an extremely simple language. I mean, it's perfect. Only 8 instructions! Only 1 pointer! Turing complete! Why isn't everyone writing their code in this language?

A bicycle attached to a wheelbarrow is extremely simple transportation. You can get anywhere you need to go, you can carry heavy loads, it's easy to use, easy to fix, cheap. Why would you use anything else?

Wood is a great building material. You can build a building several stories tall, it's cheap, plentiful, easy to work with. We should just standardize all construction on wood.

Water is the simplest beverage. It's clean, healthy, easy to ship, easy to store. We don't need to drink anything else.

I regularly read cutting edge research papers whose code examples are expressed in lambda calculus, a language from the 1930s with 3 syntax rules and 1 reduction rule.
Not a powerful argument because in 90 years it's still not mainstream and only exists in papers and blog posts. On the other hand pattern matching is widely used. 'Simple' can be 'complicated' in another way, otherwise we'd prefer to write machine code.
I don't really agree. I helped steer my organization to Python more than a decade ago -- its mix of capability and readability made it a great fit, and it remains so. Nothing I saw in this made me worried that people would struggle to read it. F-strings are great, a huge improvement on needing to say .format() all the time, and format was an improvement on the original use of % (more descriptive).

C++ is a wholly different beast. Template metaprogramming achieves great things, but at significant cost to readability.

This has been true for many, many years with "old-style classes".
That's been true for a very long while - in Python 2.1 (released just shy of 20 years ago), they added "new-style classes", class Foo(object), and for the entire life of Python 2 you weren't supposed to use "old-style classes", class Foo, because they behaved in confusing ways.

(Python 3 got rid of old-style classes and made the two syntaxes equivalent.)

This argument is totally valid, but I think the problem is that it's universal. It seems like it's equally valid as an argument against any feature. So to really apply it in practice, we need to go further, and come up with some way to quantify the value a new feature brings, so that we can compare that value to the downside of more bloat.
This is generally how the evolution of technology works.

If somebody whipped out a 3½ inch floppy, wouldn’t you say “that’s the old way of doing things?”

I wouldn't say it is true with string formatting. I would tell a new dev to use the cool new syntax if I saw them using old syntax, but I don't think using the old syntax is painful at all to read.
My concern with Python’s feature bloat is the opposite. That there aren’t parts you’re not supposed to use - developers are expected to know all of it.

Three string formatting approaches are all in wide use. There are now two different ways to do something as fundamental as assignment. Some classes use explicit methods like __eq__, others hide them by using @dataclass. Async-await adds coloured functions [0], along with “red” versions of numerous language constructs.

That’s not to mention type annotations, which I won’t deny are popular but are incredibly complex.

[0] http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...

Come on down to golang town. Though I worry when the introduce generics things will spiral out of control.
Go has a different kind of complexity from Python, though. It makes you deal with low-level details like pointers and casting between different-sized integers, none of which are necessary in Python.
Pretty cute syntax, but not what I'd wanted. It takes something that is already trivial to write and makes it simpler. Not a lot of "bang for your syntax bucks" so to speak. E.g the interpreter example from the PEP could have been written:

    parts = command.split()
    if not (0 < len(parts) < 3):
        raise TypeError(...)
    cmd, arg = parts
    if cmd == 'quit':
        ...
    elif cmd == 'get':
        character.get(arg, current_room)
    elif ...:
        ...
For more advanced examples, you'd use dictionary dispatch anyway. Will this syntax work with numpy?
Your code is buggy, though. It will only ever accept two parts because of `cmd, arg = parts`, so `["quit"]` will fail, and each command might accept a different number of parts, so you can't just test `0 < len(parts) < 3` for all of them. The pattern matching version is harder to get wrong.
what happens if len(parts)==1 ?

in this case pattern matching would have helped

Your snippet would be condensed into 1 indentation block with pattern matching. In scheme it'd be even more explicit that we're deconstructing command, therefore much much easier to follow logic. No need to worry about cmd, args, etc.

  (match command
    (("quit")    (display "Goodbye!"))
    (("look")    (describe current-room))
    (("get" obj) (character-get obj current-room))
    (("go" dir)  (set! current-room (room-neighbour dir)))
    (_           (display "Invalid command")))
The comments on LWN are just hilarious.

> I'm going to submit a PEP to add a kitchen sink to Python.

I personally would opt for the sink with the snake, as it is more Pythonic, despite not being the nicest and cleanest solution (is that a Pythonic property as well?).

Even better is the reply: "Python already has async"
Wow, this is perhaps the worst idea introduced to Python in a long time. So many ambiguities, so much inscrutable syntax. What a shame.
> There should be one-- and preferably only one --obvious way to do it.

Unless we keep adding more ways to do the same thing of course.

There have always been 100 ways to do everything in python. People just constantly spout the zen thing to look smart.
There have been multiple ways to do the same thing but that shouldn't be an excuse to keep adding features that don't contribute anything meaningful apart from expanding the syntax.

The zen thing is cited so much because it's often inconsistent the language itself.

And here I was thinking python was finally adding built-in, native, regex matching to the language, the one last reason I still use perl sometimes because of the quasi java-level verbosity and heavy-handedness of the re python package.

But no, it's just about adding switch to python ... frankly, who cares.

What do you mean with built-in native? Isn't `import re` kinda built-in? It is not an external dependency. So you could even run something like this from the terminal and get 1234 replaced by 2021:

echo "1234foo" | python3 -c 'import re, sys; t=sys.stdin.read(); print(re.sub(r"(\d{4})", "2021", t))'

I believe they mean adding a literal syntax specifically for regular expressions, ala perl and ruby, so you could write something like:

print(/\d{4}/.sub("2021"))

The backslash makes that example a little ugly, but maybe a delimiter other than "/" would work better. It's too bad that r"" is already taken for raw strings.

Could be x"" for regeX or p"" for Pattern
In perl:

print($1) if(/^hello (w[^d]+d)/);

Do the same in python and pipe the code to wc -c

Oh, and note the absence of any "import" statement or quotes in the perl code.

Python is not has heavy-handed as C++ for regex parsing, but not by much.

This was my train of thought as well.

I think I'm ambivalent towards this. Handling different types of inputs like that is kind of fun. I don't really expect to need to know this one looking at coworkers code in the wild.

> The Python steering council has, after some discussion, accepted the controversial proposal to add a pattern-matching primitive to the language.

Controversial is correct. A poll showed a majority (65%) of core developers opposed to this particular proposal, and a plurality (44%) opposed to pattern matching altogether.

https://discuss.python.org/t/gauging-sentiment-on-pattern-ma...

> 34 voters
Is that not a reasonable sample of the 87 core developers eligible to vote?

https://discuss.python.org/groups/committers

It is a rather poor sample, and a sign that either more people than those 87 should be allowed to vote or there is something wrong with how the vote was conducted.

An explicit "do not care" or "none of the above" option in that poll would have shed more light into this.

If they randomly chose 34 people to poll it would be decent sample. But now it's not a sample at all. The responders are not representative of the non-responders.
It's controversial, granted, but I don't think that's a fair reading of the poll. Here's another:

* A majority (56%) of responders want some form of pattern matching.

* Exactly half of all responders, forming nearly 90% of the above majority, are fine with that form being PEP 634.

* There are differing opinions about supporting PEPs, but a supermajority (70%) of those who agree with PEP 634 are fine with it alone.

The fact that it was possible to express more nuance when agreeing with PEP 634 shouldn't diminish their voice against those who reject the idea altogether.

> Exactly half of all responders, forming nearly 90% of the above majority, are fine with that form being PEP 634.

That half includes those who voted for "accept 634 + 640" or "accept 634 + 642", whom I doubt are entirely happy with the decision to accept PEP 634 and reject 640 & 642.

My point was that you cannot claim "65% of responders opposed this particular proposal". I understand, they're possibly not entirely happy. But the poll is flawed that way: "accept 634" should not mean "reject 640 & 642". I presented my reading as an alternative, but the poll isn't substantive.

That said, I interpret this part:

> PEP 642’s proposed syntax does not seem like the right way to solve the jagged edges in PEP 634’s syntax

to mean that this is merely an initial spec. Those unhappy with the syntax can avoid using it for now, there are no breaking changes. It took a couple iterations to refine the async syntax too, remember, and (IMHO) we arrived at a clean-enough version of it. I have hope!

Going with the decision approved by 34% is a bad move. There's nothing wrong with going with it eventually, but this is a sign that as of the poll, there is no consensus and more discussion / more options should happen.

If you decide to act on this 34% anyway, you are only just telling your community that their opinion doesn't really matter. Which might explain why 60% of the eligible voters did not bother.

> but this is a sign that as of the poll, there is no consensus

agreed. I'm being pedantic on more specific conclusions, but it wasn't the best move. Even if I'm somewhere between "fine" and "happy" about this, personally.

Maybe adding new syntax should require a 2/3 majority or more.
The poll is from end of November, there was a lot of discussions since then.
So, regardless of one's feelings on this particular feature, I think we can see what the loss of a BDFL means: feature bloat. It was once one of the core tenets of python that there be one, best way to do a given thing, because that meant that when you read _another_ programmer's code, you could usually understand it easily. This is as opposed to languages like Perl, javascript, or R, which for all their great traits are each essentially several different languages masquerading as a one.

It's probably inevitable, once you do not have a single person to say no to new features for things that can already be done, that you end up with feature bloat. So, no need to rant about it. But it does effectively demonstrate what the impact of a BDFL is, and what is missing once you don't have one (or at any rate he's not really D any more).

Did you notice that Guido was one of the authors of the pattern matching PEP?
I'm mixed on this one. While I love pattern matching in SML and Rust, I don't know if it's right for Python.

Like other comments have said, I loved and learned a lot of Python from looking at a code base and understanding everything immediately because there wasn't much to learn on the language end of things. It was nice and small. Not that the addition of pattern matching is a death sentence for simplicity, but I fear that it may lead to some other extraneous features in the future.

I like powerful, featureful languages, but I personally like Python as a simple language that anyone can learn 80% of in a busy weekend.

Will I use pattern matching? Maybe. I'll definitely play around with it. Now if we could get some algebraic datatypes then I think I'd start contradicting myself...

True, but honestly pattern matching is so cool that all languages should have it and ditch switch statements.
Thankfully Python has avoided switch statements. Hopefully pattern-matching will take the place of if/elif/elif/else chains, or dereferencing dictionary literals (e.g. `{case1:foo, case2:bar, ...}[myVal]`)
> After much deliberation, the Python Steering Council is happy to announce that we have chosen to accept PEP 634, and its companion PEPs 635 and 636, collectively known as the Pattern Matching PEPs

This is why I'm still enamored with Lisp. One doesn't wait around for the high priests to descent from their lofty towers of much deep pontification and debate with shiny, gold tablets inscribed with how the PEPs may be, on behalf of the plebes. One just adds new language feature themselves, eg. pattern matching[1] and software transactional memory[2].

1. https://github.com/guicho271828/trivia

2. https://github.com/cosmos72/stmx

Not only that, but we also know that no one pattern matcher fits all: with or without segment patterns? how to designate pattern variables? should it be extendable? what would we actually be matching? should we actually use a unifier? etc. Pattern matchers are easy to write, anyway.

We also know that they don't always make things clear, so we don't use them everywhere. Programmers using languages that have a pattern matcher "built in" tend to use it all the time, and it's not always a pretty sight. I conjecture that with this addition, Python codebases will become dichotomized: either it will be used everywhere, or it will not be used at all. The latter obviously has a head start :)

Fine for solo work but what happens if your team all come to work in the morning ecstatic about what they just added to the language? Now you have n problems where n is the number of members in your team.
This is what code review is for though, right? I think any reviewer worth their salt is going to be pretty skeptical about a new language feature being the right way to solve a problem.

New syntax is often a bad idea, but sometimes it's a really good idea. It'd be good to allow experimentation in the wild, and potentially upstream these features later once they've been proved out.

This is why I suggested macros as the feature I'd like to see most in the last python developer survey. Most of the time, defining a macro is bad idea and is a code smell, but sometimes they add very useful language features.

I just took a look at this and I am confused as to the actual practical utility of pattern matching in a language that is not strongly-typed to begin with.

My initial take upon stumbling upon the controversy was "why would pattern matching be bad?" because I have only experienced it through the lens of C# 9.0. Upon reviewing PEP 622 and looking at some examples of usage... I am in agreement with the detractors. This just doesn't seem to make sense for python, but I would certainly hate to get in the way of someone's particular use case as long as the new feature doesn't fundamentally cripple the language for others.

IMO, Microsoft nailed it with their pattern matching implementation:

https://docs.microsoft.com/en-us/dotnet/csharp/pattern-match...

Python is strongly-typed. With its built-in type annotation syntax and a checker like mypy or pyright, it is statically-typed, too.
My experience with mypy has been pretty negative.

Not because mypy is bad per se, but it just doesn't flow into a Python workflow naturally. Every time you try to interact with an external module without these annotations you immediately lose all the advantages of statically typed programs.

For me, Python with annotations just doesn't make sense, at that point I would prefer to program in Rust or Java.

A bonus for writing strongly typed code and using a small/safe subset of python is that you can transpile it to languages like rust or kotlin
Type annotation and mypy, while better than nothing, is a hack and doesn't always properly work so in a real codebase you'd be using `# type: ignore` quite often even if all of your downstream code is properly type-annotated and mypy-typechecked.

Most importantly though, it does not make Python statically typed, by no means. It just lets you lint stuff in a semantic fashion (which is helpful, no doubt), improves auto-completion and removes the need of listing type requirements in docstrings.

People complain on external corporate capture e.g. Microsoft’s embrace, extend, extinguish but I see a more common pattern of introducing internal corporate complexity driving socially powered projects to oblivion. Drupal’s extension system a few years back, Firefox addon ecosystem destruction, Python features after 3.8 and many others.

People that contribute are not infinite and those initial more active contributors get demoralized supporting work over a moving foundation.

Why not put up an effort on speeding things up, removing the GIL. Important and stale issues of the language that users really care about. F strings where good tho. That’s it. Async syntax? Less good. This pattern matching thing? Have fun debugging this.

> Firefox addon ecosystem destruction

I _love_ that I can install an extension without restarting, and as much as I sympathize with the pet projects that got dumped, I think it would have been a huge mistake to hold that up just to keep dead extensions in zombie mode rather than grave mode.

>I _love_ that I can install an extension without restarting

Why? How many times do you install extensions that restarting the browser to see them would be a concern?

A lot of us use url/cookie/etc cleaners and restarting the browser forces us to log back in to sites that we'd rather not want to during the current browser session. OR have the browser set to just delete everything at close.
1) if you want to keep the session, why do you use a session cleaner? 2) how hoften do you install new extensions? Is this really a problem?
I leave browsers open for days/weeks. I save anything I think I'll need to bookmarks or pocket. I like having it wiped when I close it. Which does force me to log back in to email/reddit/HN/etc. I have encrypted applescripts for that though so it's not too annoying.
>Firefox addon ecosystem destruction

Firefox suffered greatly in marketshare because they didn't pull the plug soon enough but rather gained a reputation for slowness and bugginess. Doing nothing isn't a viable strategy when you have competitors (ie: Chrome) who are doing something.

> Why not put up an effort on speeding things up, removing the GIL.

You are making it look like there is nobody working on those things but there is actually multiple core devs trying to making Python faster.

> Python features after 3.8 and many others.

Really, after 3.8? I thought 3.5 was a golden era?

> Why not put up an effort on speeding things up, removing the GIL.

That's exactly what requires corporate backing (removing is easy, fixing all the breakage it causes takes a lot of time and effort).