Skip to content

Inconsistent Behavior of match-case Statement with Dict Literals {} and dict() Constructor #106133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
abyesilyurt opened this issue Jun 27, 2023 · 2 comments
Labels
type-bug An unexpected behavior, bug, or error

Comments

@abyesilyurt
Copy link

Bug report

The behavior of the match-case statement seems to be inconsistent when using dictionary literals {} and dict() constructor in the case clause.

def match_test(data_dict):
    match data_dict:
        case {"name": name, "entries": [{"id": id}]}:
            print("1st match stmt., 1st case")
        case dict(name=name, entries=[dict(id=id)]):
            print("1st match stmt., 2nd case")

    match data_dict:
        case dict(name=name, entries=[dict(id=id)]):
            print("2nd match stmt., 1st case")
        case {"name": name, "entries": [{"id": id}]}:
            print("2nd match stmt., 2nd case")

data_dict_1 = {"name": "test", "entries": [{"id": "test"}]}
data_dict_2 = dict(name="test", entries=[dict(id="test")])

match_test(data_dict_1)
match_test(data_dict_2)

Output

1st match stmt., 1st case
2nd match stmt., 2nd case
1st match stmt., 1st case
2nd match stmt., 2nd case

This suggests that the match-case statement first matches with the pattern that is defined using the same syntax ({} or dict()) as the input dictionary, regardless of the pattern order in the case clauses.

Expected Output

I would expect that the first statement would be matched in this case, regardless of how the dict is initialized.

1st match stmt., 1st case
2nd match stmt., 1nd case
1st match stmt., 1st case
2nd match stmt., 1nd case

Your environment

Python 3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:41:52) [Clang 15.0.7 ] on darwin

@abyesilyurt abyesilyurt added the type-bug An unexpected behavior, bug, or error label Jun 27, 2023
@sobolevn
Copy link
Member

sobolevn commented Jun 27, 2023

According to https://peps.python.org/pep-0634 and https://peps.python.org/pep-0636 these two cases are not identical. Let me explain.

First of all, calling dict(some='a') in pattern matching case is not the same as calling it anywhere else. In PM case it will actually use ClassPattern logic that includes "matching" the given pattern to the given class structure:

>>> class A: 
...    def __init__(self, arg: int) -> None: 
...      self.arg = arg
... 
>>> match A(1):
...    case A(arg=x):
...        print(x)
... 
1

Because this is how ClassPattern works: https://peps.python.org/pep-0634/#class-patterns

    If only keyword patterns are present, they are processed as follows, one by one:
        The keyword is looked up as an attribute on the subject.
            If this raises an exception other than AttributeError, the exception bubbles up.
            If this raises AttributeError the class pattern fails.
            Otherwise, the subpattern associated with the keyword is matched against the attribute value. If this fails, the class pattern fails. If it succeeds, the match proceeds to the next keyword.
        If all keyword patterns succeed, the class pattern as a whole succeeds.
    If any positional patterns are present, they are converted to keyword patterns (see below) and treated as additional keyword patterns, preceding the syntactic keyword patterns (if any).

This does not work:

>>> class Kw:
...    def __init__(self, **kw):
...      self.kw = kw
... 
>>> match Kw(a=1):
...    case Kw(a=x):
...       print(x)
... 

But this class will:

>>> class KwGood:
...    def __init__(self, **kw):
...       for k, v in kw.items():
...          setattr(self, k, v)
... 
>>> match KwGood(a=1):
...     case KwGood(a=x):
...         print(x)
... 
1

But, dict does not have the same behavior:

>>> dict(a=1).a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'a'

This is why ClassPattern (dict(a=1)) is not the same as MappingPattern ({'a': 1}).

This is clearly not a bug :)

@abyesilyurt
Copy link
Author

@sobolevn, I appreciate your thorough response. It has significantly helped my understanding of pattern matching. I'll close the issue.

@erlend-aasland erlend-aasland closed this as not planned Won't fix, can't repro, duplicate, stale Jun 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants