No/confusing way to use match-case

current scenario -

  1. for certain types, there is no way to match them,
mppngprxy = (type.__dict__)
dct_kys = {'a': 1}.keys()
dct_vls = {'a': 1}.values()
dct_items = {'a': 1}.items()
for i in [mppngprxy, dct_kys, dct_vls, dct_items]:
  match i:
    case type(type.__dict__): print('mapping proxy') # it does not work
    case ???: print('dictionary keys')
    case ???: print('dictionary values')
    case ???: print('dictionary items')
  1. use of match case on types that could be traversed is also a bit confusing, as,
enmrt = enumerate([1, 2, 3])
for i in [enmrt]:
  match i:
    case enumerate(): print('enumerate')

gives, enumerate as output, similar holds true for range also.
but there is no way to match, iter(list([1, 2, 3])) as there is nothing like specifying list_iterator() after case, similar pattern for list_reverseiterator, set_iterator, …

  1. this one is a bit confusing, 1 == True and 0 == False, both give True, but,
match 1:
  case True: print('yes')

does not match, similar for match 0: to case False:.
one more thing is should 1 and 0 match with bool() also, as again 1 == True and 0 == False

  1. use of NotImplemented in case block, currently it acts as a wildcard.
match 1:
  case NotImplemented: print('1')
  case _: print('2')

gives the error,

SyntaxError: name capture 'NotImplemented' makes remaining patterns unreachable

this is also a bit controversial

expected scenario -

  1. if there is a way to get mappingproxy, dict_keys, dict_values, dict_items matched, then inform me, if there is no way, then probably there should be a way
  2. there should be a way to get list_iterator, set_iterator … matched also.
  3. controversial but one possibility is 1 should match with True and bool(), similar for 0 with False and bool()
  4. NotImplemented should not act as a wildcard, either just raise an error if NotImplemented is specified after case, or add a way to get it matched to a function which raises NotImplemented

For mappingproxy and NotImplemented, you can use types.MappingProxyType and types.NotImplementedType.

For dict_keys, dict_values and dict_items, you can’t access their class as a public object anywhere, but you can use collections.abc.KeysView, collections.abc.ValuesView and collections.abc.ItemsView (respectively).

Similarly for list_iterator (etc.), the actual classes aren’t exposed anywhere, since they’re implementation details. It would be a code smell to have a branch of code that depends on a precise kind of Iterator being passed in. Use collections.abc.Iterator instead, so that the pattern-matching succeeds with any kind of Iterator being passed in.

2 Likes

there is one more doubt, how to detect a coroutine function,

import asyncio
async def func():
    pass

print(1) if asyncio.iscoroutinefunction(func) else print(2)

→

1

what would be the equivalent match case for it,

match func:
    case types.CoroutineType:
        print(1)
    case _:
        print(2)

this does not work

You don’t need to use an if expression to distinguish coroutines. You can just print the bool flag:

print(asyncio.iscoroutinefunction(func))

will print True or False, which is more clear than 1 or 2.

For two reasons:

  1. To match a class, or type, you have to use round brackets after the type name.
  2. func is not a coroutine object, it is a coroutine function.

Your case attempts to match func against types.CoroutineType by equality, which fails. But even if you fix that by adding round brackets (parentheses) it still won’t match the CoroutineType because it is not a coroutine object, it is a function object.

Like generator functions, coroutine functions are not a different type of object, they are a function with a flag set. So you can use a guard clause:

match func:
    case types.FunctionType() if asyncio.iscoroutinefunction(func):
        print('coroutine function')
    case _:
        print('something else')

You can also use inspect.iscoroutinefunction instead of asyncio.iscoroutinefunction.

Beware of a subtle, and error-prone, difference when testing for types
in match statements. To match by type, you must follow the type with
round brackets. If you forget, and leave them out:

match something:
    case module.sometype:  # Oops, forgot the brackets!
        pass

you now are testing a constant value pattern which tests whether the subject, something, is equal to module.sometype, which is probably not what you want.

But it gets worse: if the type you are testing for is a builtin class, and you forget the brackets:

match something:
    case int:  # Oops, forgot the brackets!
        pass

this is a capture pattern which always succeeds and binds the subject to the name int.

This aspect of the match syntax is unfortunately a bug magnet. Watch out for it.

1 Like

this appears to be using if-else in match case,

match func:
    case types.FunctionType() if asyncio.iscoroutinefunction(func):
        print('coroutine function')
    case _:
        print('something else')

I would prefer,

print('abc') if asyncio.iscoroutinefunction(func) else print('xyz')

over it
or if there was someway to,

match func:
    case types.CoroutineFunctionType():
              ...

I would prefer it to the previous match case

There is no “else” in the case, so how could it be if-else?

Perhaps you need to read the documentation on match-case in Python, especially the part about guard clauses and the tutorial on match-case.

There is no CoroutineFunctionType because there is no separate type for coroutine functions, they are functions with a flag set.

found out one more way to solve this problem,

async def afunc():
    pass

def func():
    pass

match func.__code__.co_flags & 0x180:
    case 0:
        print('not coroutine')
    case _:
        print('coroutine')

You shouldn’t inspect dunder attributes directly like that when you can use the official public API:

https://docs.python.org/3/library/inspect.html#inspect.iscoroutinefunction

https://docs.python.org/3/library/asyncio-task.html#asyncio.iscoroutinefunction