Feature Suggestion - Assign match case result to a variable

Feature or enhancement

Assign match-case result to a variable

Pitch

Currently, in Python 3.10+, match-case statements allow us to perform pattern matching and execute code blocks based on patterns. However, the result of the match-case statement cannot be directly assigned to a variable. My suggestion is to introduce the ability to assign the match-case result to a variable, making the syntax more concise and flexible.

Current approach:

def is_adult(age) -> bool:
    match age:
        case x if x >= 21:
            return True
        case _:
            return False

Suggested approach:

def is_adult(age) -> bool:
    is_an_adult = match age:
        case x if x >= 21:
            return True
        case _:
            return False

    # doing stuff with is_an_adult

    return is_an_adult

By introducing this enhancement, developers can assign the match-case result to a variable (can_drink in the example) and use it as desired. This will lead to more readable and expressive code, especially when multiple conditions are involved.

Limitations

Single Assignment Limitation: Assigning the match-case result to a variable should only be allowed if the variable is assigned within each case block. Allowing multiple assignments to the same variable from different case blocks might lead to confusion and unexpected behavior.

Scope and Lifetime: The scope and lifetime of the variable assigned within a match-case statement need to be well-defined to avoid potential issues with variable visibility and memory management.

Returning from Match-case Block: If the match-case statement is used inside a function, returning from within a case block might not behave as expected. The return might only exit the current case block, not the entire function, leading to subtle bugs.

Non-trivial Expressions: When the match-case result is assigned to a variable, complex expressions within a case block might lead to less readable code, reducing the clarity of the intent.

Consistency with Existing Syntax: Care should be taken to ensure that the proposed syntax for assigning the match-case result to a variable aligns well with existing Python conventions and is intuitive for developers.

Performance Considerations: If the match-case statement is used in performance-critical sections of code, the introduction of variable assignments might impact performance. Proper benchmarks should be conducted to assess any potential overhead.

By addressing these limitations and considerations, the proposed enhancement can provide a valuable addition to Python’s match-case feature, allowing developers to write more elegant and maintainable code.

1 Like

Your suggested approach doesn’t return anything :wink:

It would be helpful to provide a more expressive example than can_drink = age > 21 [1].

I think all of the limitations you listed are why this is probably not a great idea. In a small number of cases it would be useful[2] but the nature of Python makes it very hard to enforce correct usage and restricting the syntax to a subset of a normal match statement would be confusing.


  1. …and shouldn’t that be >= 21 ? ↩︎

  2. and I definitely like to use this construction in Rust ↩︎

I fixed my post :slight_smile:

I wanted to present the general idea and the potential of it.
I do think that many rust programmers that use this feature will support this request.

this is a new example for this func:

definitions of functions:

def is_equals(x, y):
    return x == y

def add(x, y):
    return x + y

def divide(x, y):
    return x / y

def multiply(x, y):
    return x * y

Current approach:

def match_func(func, x, y):
    match func:
        case "is_equals":
            return is_equals(x, y)
        case "add":
            return add(x, y)
        case "divide":
            return divide(x, y)
        case "multiply":
            return multiply(x, y)

Suggested approach:

def match_func(func, x, y):
    value = match func:
        case "is_equals":
            return is_equals(x, y)
        case "add":
            return add(x, y)
        case "divide":
            return divide(x, y)
        case "multiply":
            return multiply(x, y)
    # do something with the value
    
    return value

I think this made it worse, using return to mean “return from the case statement” is not going to work. I just meant that the original version of your function didn’t return anything.

In practice, if match returned a value it would always return a value, not only when it gets assigned. return can’t be used because it would still have the same meaning as in the current approach: return from the function immediately.

You’re basically suggesting that the match statement is turned into an expression that evaluates to whatever the chosen case is. I think that would make for pretty ugly and confusing code, in Python–in other languages, I think it works fine, but they have other safeguards on what is valid code (like strict typing). I’d rather read this code [1]:

def match_func(func_name):
    match func_name:
        case "is_equals":
            return is_equals
        case "add":
            return add
        case "divide":
            return divide
        case "multiply":
            return multiply
        case _:
            raise SomeKindaError

def do_func(func_name, x, y)
    value = match_func(func_name)(x, y)
    # do something with the value
    return value

  1. okay, I’d actually prefer to not encounter this at all :joy: ↩︎

3 Likes

Is it rust that is inspiring this idea?

In rust this type of thing is natural, but everything in rust is an expression so it just works.
With python being a statement language its not natural.
Trying to turn python into rust will not work.

1 Like

Someone might want to actually return from a match case, so maybe use yield instead?

def match_func(func, x, y):
    value = match func:
        case "is_equals":
            yield is_equals(x, y)
        case "add":
            yield add(x, y)
        case "divide":
            yield divide(x, y)
        case "multiply":
            yield multiply(x, y)
    # do something with the value
    
    return value
1 Like

…but what if I want to yield from a match case?

3 Likes

It’s not just that it would be an outlier in Python, it would be highly backwards incompatible. This is a non-starter.

Consider

def f(x):
    match x:
        case []:
            pass
        case _:
            x.pop()

f([2]) would now return 2 instead of None.

1 Like

Can’t you just perform the assignment within each case?

2 Likes

In principle, this would be quite useful, but this particular proposal potentially introduces backwards incompatibility by breaking return statements nested inside a match statement. Here, in case of a no match match_func no longer returns None and raises a NameError instead:

def match_func(arg):
    match arg:
        case "something":
            some_variable = 4
        case "something else":
            some_variable = 8
        case _:
            return None
    return some_function(some_variable)

Making return behave differently depending on whether match is preceded by assignment probably introduces too much confusion.

1 Like

The Python match syntax is used for Structural Pattern Matching.

To create a Dispatch table, use a dictionary.

call_me = dispatch.get(func)
value = call_me()

It’s shorter, and it’s the appropriate data structure to create a dispatch table.

As I said on the rejected issue, this is not possible in Python because statements in python are not expressions and do not have a value and thus here is nothing to assign. The difference between expressions and statement is quite intentional and fundamental to the design of Python.

6 Likes

This has been already solved, like these examples:

s = input()
match s:
    case a:
        print(a)

s = input()
match s:
    case a:
        print(a)
args = ['gcc', 'hello.c', 'world.c']
# args = ['clean']
# args = ['gcc']

match args:
    case ['gcc']:
        print('gcc: missing source file(s).')
    case ['gcc', file1, *files]:
        print('gcc compile: ' + file1 + ', ' + ', '.join(files))
    case ['clean']:
        print('clean')
    case _:
        print('invalid command.')