4

If I want to match against a list containing 2 elements (1 str, 1 bool) I can do the following:

match some_lst:
    case [str(), bool()]:  # -> Valid
        do_something()

How can I apply the same logic to dictionaries without using guards? For example, this doesn't work:

match some_dict:
    case {str(): bool()}:  # -> This is invalid
        do_something()

Working example with guard:

match some_dict:
    case dict() if all(isinstance(k, str) and isinstance(v, bool) for k, v in some_dict.items()):
        do_something()  # -> This works
ILS
  • 1,224
  • 1
  • 8
  • 14
Deneb
  • 981
  • 2
  • 9
  • 25
  • It seems that there is no sufficient reason to refuse the guard. I simply checked the bytecode and the document. What you want may not exist at present. – Mechanic Pig Oct 01 '22 at 15:25
  • @MechanicPig I checked the documentation as well, but since it seemed rather sparse regarding the subject I opened the question here. Thank you. – Deneb Oct 02 '22 at 19:30
  • If you `apply the same logic to dictionaries`, you need to list all item patterns. But according to the `guard` example, you may want to list only one item pattern because they are the same. – ILS Oct 03 '22 at 14:02
  • Looking in [the grammar](https://peps.python.org/pep-0634/#mapping-patterns) it doesn't seem to be possible: the "key" part of the `key_value_pattern` says `(literal_pattern | value_pattern)` – wim Oct 07 '22 at 05:25

3 Answers3

0

In python, you can't easily enforce keys type and values type for your dict.

This means python has no means to verify all the keys and all the values from your statement:

match some_dict:
case {str(): bool()}:  # -> This is invalid
    do_something()

You could consider doing some kind of switch case with TypedDict, however, you can't use anystring keys with this type, you are force to define each of your keys which can often be unwanted.

Nevertheless here are 2 ways you could attack your problem:

PYTHON 3.8 with jsonschema

from contextlib import suppress
from jsonschema import ValidationError, validate

# Watch out, use json types here
# We use anystring regex for property
anystring_bool_dict = {"type": "object", "patternProperties": {"^.*$": {"type": "boolean"}}}
anystring_int_dict = {"type": "object", "patternProperties": {"^.*$": {"type": "integer"}}}

def switch_on_dict_types(case: dict):
    with suppress(ValidationError):
        if not validate(instance=case, schema=anystring_bool_dict):
            print("FIRST_CASE")
    with suppress(ValidationError):
        if not validate(instance=case, schema=anystring_int_dict):
            print("SECOND_CASE")


switch_on_dict_types({"lalal": True})
switch_on_dict_types({"lalal": 1})

# On Execution:
#    FIRST_CASE
#    SECOND_CASE

Feel free to improve indentation level and complexity from this solution if you manage to do so.

PYTHON 3.10 iterate dict keys, values and using type to string

This ain't perfect, I am just moving your loop but at least you don't run the loop for each case:

def switch_on_dict_types(dict):
    for k, v in dict.items():
        match str(type(k)) + str(type(v)):
            case "<class 'str'><class 'bool'>":
                print("string bool case")
            case "<class 'str'><class 'int'>":
                print("string int case")


switch_on_dict_types({"lalal": True})
switch_on_dict_types({"lalal": 2})

# On Execution:
#   string bool case
#   string int case
Greg7000
  • 297
  • 3
  • 15
  • While your code may reaches the overall goal, it doesn't uses structural pattern matching and is by no means a canonical answer. – Thingamabobs Oct 06 '22 at 16:41
  • For some reason my introduction was all gone... Editing ... I still won't fully comply with "t doesn't uses structural pattern matching and is by no means a canonical answer" but it will add come details... – Greg7000 Oct 06 '22 at 17:16
0

A mapping pattern does not support arbitrary types, it expects a simple literal or a qualified name. However, a workaround that uses the structural pattern matching is to simply split your dictionary into a list of keys and a list of values. From here it just depends on your need if you want to check for specific identifier or rather have chunks and match key,value pairs. An working example can be found below.

class A:
    pass

class B:
    pass

def dict_typematch(subject):
    keys = list(subject)
    vals = list(subject.values())
    #chunks = [(keys[idx],vals[idx]) for idx in range(len(keys))]
    match keys:
        case str(), str():
            print('keys', 'str', 'str')
            print(vals)
            match vals:
                case str(), str():
                    print('val', 'str', 'str')
                case str(),type_ if isinstance(type_,B):
                    print('val', 'str', 'instance B')
                case a, _type if _type is A:
                    print('val', 'str', 'type A')

        case _type, str() if _type is ins_a:
            print('keys', 'str', 'ins_a')
            match vals:
                case str(), int():
                    print('vals', 'str', 'int')

        case tuple(), str():
            print('keys', 'tuple', 'str')
                    
            
        case object(), str():
            print('obj', 'str')

subject1 = {
    'a' :'hi',
    'b' :'bye'}
subject2 = { 
    'b':'hi',
    'a':B()}
subject3 = { 
    'b':'hi',
    'a':A}
dict_typematch(subject1)
dict_typematch(subject2)
dict_typematch(subject3)

ins_a = A()
subject4 = {
    ins_a:'hi',
    'a':5}
subject5 = {
    (1,A()):'hi',
    'a':B()}
dict_typematch(subject4)
dict_typematch(subject5)
Thingamabobs
  • 7,274
  • 5
  • 21
  • 54
-1

Maybe I don't get the task right but I checked and it works fine with dictionaries.

I used Python 3.10.5.

match {"a": True}:
    case {"a": True}:
        print("a: True")
    case {"a": False}:
        print("a: False")
    case {"b": True}:
        print("b: True")
    case {"b": False}:
        print("b: False")
        
a: True

match {"a": False}:
    case {"a": True}:
        print("a: True")
    case {"a": False}:
        print("a: False")
    case {"b": True}:
        print("b: True")
    case {"b": False}:
        print("b: False")
        
a: False

match {"b": True}:
    case {"a": True}:
        print("a: True")
    case {"a": False}:
        print("a: False")
    case {"b": True}:
        print("b: True")
    case {"b": False}:
        print("b: False")
        
b: True

match {"b": False}:
    case {"a": True}:
        print("a: True")
    case {"a": False}:
        print("a: False")
    case {"b": True}:
        print("b: True")
    case {"b": False}:
        print("b: False")
        
b: False
Raibek
  • 558
  • 3
  • 6