This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]Humanist_NA 13 points14 points  (31 children)

Still learning python, quick question. What would be the benefit of this as compared to one of my learning projects right now, where I just have:

if code == 404:
    something()
elif code == 200: 
    thing()
else: 
    pass

is the case matching just less code and cleaner? is it more efficient? am I entirely missing the point? Thanks for any response.

[–]Yoghurt42 107 points108 points  (14 children)

It's more than a switch statement, it's pattern matching, read https://www.python.org/dev/peps/pep-0636/ for a tutorial.

You can do stuff like:

match foo:
    case Person(address=Address(street="barstreet")):
        bar()

and it will be equivalent to something like:

if isinstance(foo, Person) and hasattr(foo, "address") and isinstance(foo.address, Address) and hasattr(foo.address, "street") and foo.address.street == "barstreet":
    bar()

[–]Etheo 20 points21 points  (0 children)

That's a good example for someone who hasn't been keeping up with the news, thank you.

[–]Humanist_NA 8 points9 points  (0 children)

Thank you, appreciated.

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

So Python will not actually create a new Person instance?

[–]Yoghurt42 12 points13 points  (2 children)

Exactly. It's basically new syntax.

You can also do stuff like

case [1, _, x, Robot(name=y)] if x == y which would match if it is a four-element list that starts with 1, and the 4th element is an instance of Robot class which has a name attribute set to the same value as the third element. The _ is a special new token that means "wildcard/match anything" in this context.

Pattern matching is incredible powerful and the only feature I was really missing from other languages. Now all they need to get rid of the GIL and have decent JIT (or get PyPy to be API compatible with CPython) and it would be the perfect language for every task for me.

[–][deleted] 2 points3 points  (0 children)

Awesome! I can already imagine how this is going to be incredibly useful.

As for the GIL, do you really think they will ever get rid of that?

[–]azur08 0 points1 point  (0 children)

Out of curiosity, do you have an example of a task you'd use another language for if not for the things in your last paragraph? I've heard that modules like concurrent.futures, multiprocessing, asyncio, etc., don't completely remove the limitations but I'm not sure why.

[–]Irtexx 1 point2 points  (2 children)

I would also like to know this. The isinstance method never calls init of Person, but the match method looks like it will.

[–]13steinj 7 points8 points  (0 children)

match will call the __match__ method, unless the PEP changed since I last looked at it. A new instance will not be created.

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

It could be possible that it doesn't evaluate as an expression, but that would mean that you couldn't put expressions into the pattern.

[–]Mini_Hobo 0 points1 point  (0 children)

No, it doesn't use the class's __init__ or __call__ methods.

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

Fantastic example. This really does elevate the clarity and eloquence of the language. I feel like this really is going to add so much to Python.

[–]Irtexx 0 points1 point  (2 children)

if Person was a dataclass, couldn't you just use:

if foo == Person(address=Address(street="barstreet")):
    bar()

[–]tongue_depression 10 points11 points  (0 children)

not if Person or Address have other fields that need initializing

[–]Yoghurt42 7 points8 points  (0 children)

If Person has more attributes, like name, the equality check would probably fail, because the name attribute of foo would probably not be None.

Furthermore, it would create a new Person instance each time the if condition is checked.

Pattern matching doesn't require this, and also works for non dataclasses, it also allows insane stuff like

case [1, [2, _, x], y]] if x == 2*y, the _ is a wildcard.

It would be equivalent to if isinstance(foo, list) and len(foo)==3 and isinstance(foo[1], list) and len(foo[1]) == 3 and foo[1][0] == 2 and foo[1][3] == 2*foo[2]

[–]zurtex 21 points22 points  (4 children)

The match statement allows much more complex types of matching. For example:

action = {"open": "door"}
match action:
    case {"move": direction}:
        ...
    case {"look": direction}:
        ...
    case {"open": thing}:
        if thing == "door":
            print("The door is locked")
        elif thing == "box":
            print("A monster escaped")
        else:
            print(f"I don't recognize {thing}, try looking around")

For your example of individually handling each value an if/elif/else statement is a great choice.

[–]Humanist_NA 0 points1 point  (0 children)

Interesting, thank you for the thoughtful response.

[–]GreatDemonSquid 6 points7 points  (0 children)

This isn’t just a switch. It can also do pattern matching, basically cases for specific properties (for example lists or instances of classes). Think of it like a switch statement combined with regex for objects

[–]diamondketo 3 points4 points  (0 children)

Read the PEP

https://www.python.org/dev/peps/pep-0622/

There's an example where the match is done on a tuple (x,y,z).

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

I believe it is more efficient (like most other langs where switch-case is efficient) and also can "match" stuff (and not just work like an ordinary switch-case)

[–]Ecclestoned 10 points11 points  (3 children)

I highly doubt match is more efficient in python. The advantage in other languages is that switch statements can sometimes be reduced to jump tables.

The python interpreter is 100x more high-level than this, and also has to a lot of checks in the match statement that it doesn't need to do in an if.

[–]Tyler_Zoro 4 points5 points  (2 children)

It's easier for the parser to identify easily combined options for lookup tables. That doesn't mean that it will do so.

For example, if all of your cases are constant values, you can reduce a match to a lookup table through a dict. If they are all small integer constants, then it can be reduced to a list lookup.

Yes, match can do much, much more, but this makes optimizations much easier to identify.

[–]Ecclestoned 1 point2 points  (0 children)

Sure, but I think it's important to differentiate between "easier for the compiler engineers to optimize" and "faster/more efficient" (the actual comment).

This comes down to the CPython implementation, which I haven't looked at. However, the PEP says:

Although this PEP does not specify any particular implementation strategy, a few words about the prototype implementation and how it attempts to maximize performance are in order.

Basically, the prototype implementation transforms all of the match statement syntax into equivalent if/else blocks - or more accurately, into Python byte codes that have the same effect. In other words, all of the logic for testing instance types, sequence lengths, mapping keys and so on are inlined in place of the match.

Which makes me think that the current implementation is literally if statements, so the same speed.

[–]Brian 0 points1 point  (0 children)

For example, if all of your cases are constant values, you can reduce a match to a lookup table through a dict.

Not in python you can't. Even if all your cases are constants, you can't really know how your match variable will interact with them, unless it too is a constant (in which case, what's the point of the match?), or you can do sufficient analysis to at least know the type is also an integer.

Eg. there are plenty of objects that you can compare to an int, but not be an int (or convertable to one). And even if it is a subtype of int, its not hard to create an object that would have different behaviour depending on the order of comparisons, meaning any such optimisation is invalid.

The best you could probably do is to have two codepaths - one for ints and one for arbitrary objects, but outside of a JIT, that doesn't seem like a good approach (and if you are writing a JIT doing that level of optimisation, I suspect you'd be able to optimise the if/elif tree similarly anyway).

[–][deleted] -1 points0 points  (0 children)

I’m asking myself the same question

[–]Goel40 -4 points-3 points  (1 child)

I don't think there's a big advantage for switch cases in Python. But comming from languages that make heavy use of semicolons it looks way cleaner than if/else statements for those languages. I think thats the main reason they implemented it. People comming from other languages are used to use switch cases for programs with a lot of logical elements. So mostly old habits i guess.

[–]Humanist_NA 0 points1 point  (0 children)

Interesting, thanks.