Providing a shorthand match case statement

Since the Structural Pattern Matching released I’ve been using it a lot,
one thing that I realized is that sometimes - I just wan’t to check if a single object “matches” a patten, something like this:

if response.status != 200 and response.get('data') and response['data'].get('error') == 'BadFormat':
    raise Exception()

changed to:

if response.status != 200:
    match response:
        case {'data': {'error': 'BadFormat'}}:
            raise Exception()

I think it would be really nice to provide a match keyword - kind of like the is keyword that will return a boolean - so this would be possible:

if response.status != 200 and response match {'error': 'BadFormat'}:
    raise Exception()
2 Likes

I wouldn’t push for such a change.
This is nothing more than syntactic sugar that can make it difficult to understand code.

Although the plus is that it will speed up writing programs.

How often would you use it?
More often, one match would probably not be enough (I think), for example:

if response.code != 200:
        match response:
            case {'data': {'error': 'BadFormat'}}:
                raise FormatError()
            case {'data': {'error': 'AuthFailed'}}:
                raise AuthError()

Or something like this:

if response.code != 200:
    match response:
            case {'data': {'error': error_message}}:
                raise Exception(error_message)

As are decorators, and look how useful they’ve become. I don’t have an opinion on the proposed change, but just because it’s only syntactic sugar shouldn’t disqualify it.

3 Likes

Rust has this in the form of if let:

https://doc.rust-lang.org/book/ch06-03-if-let.html

I have found it extremely useful in my Rust adventures.

4 Likes

There was previously a thread that proposed using the walrus operator for if let: Mailman 3 Extrapolating PEP 634: The walrus was waiting for patmat all along - Python-ideas - python.org

The proposed syntax was

if [x, y, *z] := my_list:
    ...

meaning

match my_list:
    case [x, y, *z]:
        ...
4 Likes

The OP’s example would also normally, IMO, be written in a shorter form even without an “inline” match keyword, where the if response.status != 200 is made part of the “case” line as a guard rather than nesting the match block.

1 Like

I agree, decorators are a good example of syntactic sugar.

I was gonna propose the same thing. Eventually, many Python statements are turned into expressions for inline use:

  • deflambda:
  • for → List comprehension
  • if... if x else ...
  • a = ba := b

I think having an inline match would be a very natural evolution. Something less generic was proposed by the original PEP but ultimately deferred for future discussion: PEP 622 – Structural Pattern Matching | peps.python.org

While inspecting some code-bases that may benefit the most from the proposed syntax, it was found that single clause matches would be used relatively often, mostly for various special-casing. In other languages this is supported in the form of one-off matches.

The pep proposed the syntax:

  • if match value as pattern

However, I think

  • if value match pattern

feel more natural and is what I would have used intuitively (and it’s also the syntax proposed by original post author).

So the following would be equivalent:

if isinstance(x, tuple):

if x match tuple():

Even for simple case like this, I think the match syntax is more readable, and unlock more powerful use-cases.

I regularly find myself wishing for a match statement that could be used inside list comprehensions.

6 Likes

Structural Pattern Matching As Standalone Boolean Expression

I don’t have a strong opinion on “syntax” at the moment, I mainly want to illustrate the benefits of this “construct” itself.

Syntax comments:

  • One thing to think about would be whether to support comma-separated patterns.
    Though it seems it’s not supported in match-case either (no example in pep-0636 tutorial).

  • Any of these will work for me:

    • match(<thing>, <pattern>)
    • match <thing>, <pattern>
    • <thing> match <pattern>
  • Following will likely not:

    • thing := pattern Conflicts with walrus operator
    • match <thing> as <pattern> Too many is, as etc would become hard to remember down the line

Usefulness of this construct:

With this proposal:

best of both worlds:

  • clear selection with structural pattern match, and
  • flexible logic with guard clauses using orphan if-else blocks
def cli(args: list[str]) -> None:

	if args match [("-h" | "--help"), *x]:
		print(attempt_help.format(command=Path(sys.argv[0]).stem), end="")
		return

	response: str = input(attempt_prompt).lower()
	print()

	if response != 'y':
		return

	match (args):
		case []:
			attempt(load.default_path())

		case [("-f" | "--file"), file]:
			attempt(file)

		case _:
			...

Current alternate 2: if-else & invert-select guard clause

benefit: clear logic flow. invert-select guard clause keep the main body separated.
problem: selection itself is messed up: IndexError when args is empty, requires needless LBYL or EAFP

def cli(args: list[str]) -> None:
	if args[0] in ["-h", "--help"]:
		print(attempt_help.format(command=Path(sys.argv[0]).stem), end="")
		return

	response: str = input(attempt_prompt).lower()
	print()

	if response != 'y':
		return

	match (args):
		case []:
			attempt(load.default_path())

		case [("-f" | "--file"), file]:
			attempt(file)

		case _:
			...

Current alternate 1: match-case structural matching statement

benefit: clear selection: very easy structural pattern matching
problem: logic is messed up: either violates DRY or becomes needlessly nested (here, a combo of both)

def cli(args: list[str]) -> None:
	response: str
	match (args):
		case []:
			response = input(attempt_prompt).lower()
			print()
			if response == 'y':
				attempt(load.default_path())

		case [("-f" | "--file"), file]:
			response = input(attempt_prompt).lower()
			if response == 'y':
				attempt(file)
			else:
				print()

		case [("-h" | "--help"), *x]:
			print(attempt_help.format(command=Path(sys.argv[0]).stem), end="")

		case _:
			...

P.S.

the “Your topic is similar to…” is much much much more useful than tedious search.
can that panel be brought up again once it’s dismissed?
as it covers over the preview, but i think preview and duplicate search are separate functionalities

Previous writeup before finding this one

I have skimmed various articles online, and it seems that:

  • structural pattern matching is available only via match-case which is a statement, and can’t be used as expression. (tutorial here: pep-0636)
  • re is limited to text pattern matching, not on structural matching of general python objects like match-case. (tutorial here: howto/regex)

am i right? or does there exist a way to use match-case like capability in an expression too?

this might seem similar to, but is not same as:

2 Likes