8

I'm trying to use SPM to determine if a certain type is an int or an str.

The following code:

from typing import Type

def main(type_to_match: Type):
    match type_to_match:
        case str():
            print("This is a String")
        case int():
            print("This is an Int")
        case _:
            print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")

if __name__ == "__main__":
    test_type = str
    main(test_type)

returns https://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg

Most of the documentation I found talks about how to test if a certain variable is an instance of a type. But not how to test if a type is of a certain type.

Any ideas on how to make it work?

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Curtwagner1984
  • 1,908
  • 4
  • 30
  • 48
  • 1
    Comparing types directly is usually more restrictive than you want. I would consider `if issubclass(type_to_match, str):`, etc, to allow for subclass matching. – chepner Jan 26 '22 at 13:43

3 Answers3

12

If you just pass a type directly, it will consider it to be a "name capture" rather than a "value capture." You can coerce it to use a value capture by importing the builtins module, and using a dotted notation to check for the type.

import builtins
from typing import Type


def main(type_: Type):
    match (type_):
        case builtins.str:  # it works with the dotted notation
            print(f"{type_} is a String")
        case builtins.int:
            print(f"{type_} is an Int")
        case _:
            print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")

if __name__ == "__main__":
    main(type("hello"))  # <class 'str'> is a String
    main(str)  # <class 'str'> is a String
    main(type(42))  # <class 'int'> is an Int
    main(int)  # <class 'int'> is an Int
  • 1
    there is absolutely no need to import builtins, or use `builtins.str` or `builtins.int`. Just use `str` and `int` in the case patterns. (yes, one can possibly override these names in the global namespace of the current module, but then, one should know what he is doing) – jsbueno Jan 26 '22 at 13:25
  • 8
    @jsbueno That wouldn't work. See https://stackoverflow.com/questions/66159432/how-to-use-values-stored-in-variables-as-case-patterns. Using a bare name means the name for the capture. If you want to use a name as a constant, it has to be a dotted notation... – Tomerikoo Jan 26 '22 at 13:33
  • @jsbueno Compare the defintions of [capture patterns](https://docs.python.org/3.10/reference/compound_stmts.html?highlight=case#capture-patterns) and [value patterns](https://docs.python.org/3.10/reference/compound_stmts.html?highlight=case#value-patterns). – chepner Jan 26 '22 at 13:38
  • 2
    @jsbueno `case str` gives `SyntaxError: name capture 'str' makes remaining patterns unreachable`. If you have an actual working example, I would gladly see it. – Patryk Bratkowski Jan 26 '22 at 13:41
  • @jsbueno, Like it was already stated, `case str` gives a syntax error. – Curtwagner1984 Jan 26 '22 at 13:45
  • You could, however, use an Enum or a class to encompass the "supported" types instead of importing something... – Tomerikoo Jan 26 '22 at 13:54
  • @Tomerikoo Wouldn't you need to import `Enum` for that? – Curtwagner1984 Jan 26 '22 at 14:02
  • Haha yeah I thought about that right after posting the comment.... Just a class with some class-level variables will have the same effect I guess... Just a matter of taste, works the same really (as your solution I mean)... – Tomerikoo Jan 26 '22 at 14:04
  • 1
    I stand corrected. Maybe I'd just use a guard with `issubclass` (if issubclass is the desired behavior) - `case type() if issubclass(type_, str)` - but then, indeed, for the exact class, `builtins.int` is a good solution. – jsbueno Jan 26 '22 at 14:06
  • @jsbueno Do you know how to match an `enum`? For example, if I have a type `MyEnum`, and `MyOtherEnum` and I want to match either of them as a general enum? I tried doing `Enum()` but it didn't work. – Curtwagner1984 Jan 26 '22 at 14:09
  • @Curtwagner1984 `issubclass(MyEnum, Enum)`? – Tomerikoo Jan 26 '22 at 14:09
  • Enums might be tricky due to deep metaclass magic used in their construction. I will make some experiments here. – jsbueno Jan 26 '22 at 14:11
  • @Tomerikoo Thanks! `case type_ if issubclass(type_,Enum)` works – Curtwagner1984 Jan 26 '22 at 14:18
  • yest, that from @Tomerikoo for _any- enum type. If you want to test for enum members of more than one type, use the `|` operator: `case Color() | Animal():`. If you want to test for a some specific enum classes, use the guard syntax and you might do: `case type() if type(type_) in (Color, Animal):` – jsbueno Jan 26 '22 at 14:21
5

As its name suggests, structural pattern matching is more suited for matching patterns, not values (like a classic switch/case in other languages). For example, it makes it very easy to check different possible structures of a list or a dict, but for values there is not much advantage over a simple if/else structure:

if type_to_match == str:
    print("This is a String")
elif type_to_match == int:
    print("This is an Int")
else:
    print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")

But if you really want to use SPM, you could use the guard feature along with issublcass to check if a type is or is a child of any other:

match type_to_match:
    case s if issubclass(type_to_match, str):
        print(f"{s} - This is a String")
    case n if issubclass(type_to_match, int):
        print(f"{n} - This is an Int")
    case _:
        print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
2

One option is to grab the type name and match on that instead of on the type directly:

from typing import Type

def main(type_to_match: Type):
    match type_to_match.__name__:
        case 'str':
            print("This is a String")
        case 'int':
            print("This is an Int")
        case _:
            print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")

if __name__ == "__main__":
    test_type = str
    main(test_type)
Joshua Little
  • 308
  • 2
  • 6