Match statement redefinining names is a very subtle source of bugs

Long time Python developer here, learning about match statement recently.

I tried this naive code, and was stumped when I later figured out I should use int() instead, but it didn’t work! I was in an interactive environment and I had unwittingly shadowed to builtin int to be None.

a = (None, 1)
match a:
  case (int, _):
    print(a[0])
  case (_, int):
    print(a[1])
  case _:
    print(-1)

This was probably discussed to death somewhere, but I don’t know how to look for it.
I feel this behavior is really pernicious and I don’t see the point of it? Why should a structural pattern have side effects like this? The as argument provides something in that direction, but it’s explicit.

1 Like

This is just like assigning a value to int any other time…you’re gonna have a bad time. I don’t think it would make sense to disallow this in match statement but not elsewhere.

That said, sometimes this is a linting rule and so you’d get a warning in an IDE or CI (if you run it).

2 Likes

Where is the assignment happening?

Ugh I see it now. I forget you can use names to specify the size of the tuple (and name those variables). It’s a bit confusing what is a “symbol name” and what is a structural pattern, but that’s on me I suppose.

If I tweak the code to this it works as expected:

a = (None, 1)
match a:
    case (int(), _):
        print(a[0])
    case (_, int()):
        print(a[1])
    case _:
        print(-1)

I don’t understand why it’s doing an assignment and overriding int so I’m really curious as to what’s going on.

It’s because you can use it like this

a = (None, 1)
match a:
  case (x,):
    print(x)
case (x, y):
  print(y)
case _:
   print(-1)
2 Likes

That’s just how pattern matching works, which isn’t obvious if you haven’t read the documentation closely. You are assuming that case (int, _) is something like if len(a) == 2 and a[0] == int. It’s really more like if len(a) == 2 and (int := a[0]).