Unexpected syntax issue: list[1,2,3]

I have just been bitten by what boils down to

x = list[1,2,3]

being syntactically correct. Some time ago this would be a syntax error. Now it’s not and I think this makes python language worse.

It would help if typing annotations outside function signatures were invalid or required quotes, because they are strings anyway.

>>> repr(list[1, 2, 3])
'list[1, 2, 3]'

Just sharing my experience.

1 Like

This is a consequence of PEP585 – Type Hinting Generics In Standard Collections, new in Python3.9.

The fact that it works with literals as parameters seems surprising, since List[1,2,3] gives a TypeError. I wonder if that’s intentional. The PEP doesn’t mention this behavior.

No they are not strings, calling repr gives you a string, and you would get a string for any object.

And this was not a SyntaxError in the past but a TypeError.

Is this a problem because you thought you created a list but actually ended up creating a GenericAlias? Then you would have realized your mistake pretty soon, as soon as you try to use the value anywhere later in your code, so it is not a very big deal. There are lots of other cases where you cannot rely on the interpreter to catch your mistake at the point where it was made and you have to find out later, for example the trailing comma making a tuple.

On the other hand, you get benefits of static type checking which does help you find the errors early without even running your code. If you had used a static type checker it would have caught this mistake for you.

3 Likes

You are right that I didn’t check what type of error would an older Python give me and you are right that my inspection of the resulting type was wrong.

Nevertheless the discussed issue may be a big deal in a larger codebase and the error may appear quite far from the wrong line of code. Static checker may sometimes help in such case, you are right again.

But It took me quite long to discover, because list[] and list() are visually similar, and the error was an if condition with comparison that never evaluated True. In my case static checker would not help at all.

Odd, because on my system, this:

x = list[1, 2, 3]

… throws this error:

builtins.TypeError: 'type' object is not subscriptable

… at runtime.

You’re using Python3.8 or older.

2 Likes

So, is the issue that there’s no error raised with P3.8+ at runtime?

Yes, or more specifically whether that should be considered an issue at all.

IMHO it should not be considered an issue because it is a valid way how to specify a type variable. Example of how it could be used at run time:

>>> from typeguard import check_type
>>> x = list[int]
>>> check_type('test_list', [1, 2, 3], x)
>>> check_type('test_list', ['a', 'b'], x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/vbrozik/tmp/python/typeguard/.venv/lib/python3.10/site-packages/typeguard/__init__.py", line 757, in check_type
    checker_func(argname, value, expected_type, memo)
  File "/home/vbrozik/tmp/python/typeguard/.venv/lib/python3.10/site-packages/typeguard/__init__.py", line 458, in check_list
    check_type('{}[{}]'.format(argname, i), v, value_type, memo)
  File "/home/vbrozik/tmp/python/typeguard/.venv/lib/python3.10/site-packages/typeguard/__init__.py", line 785, in check_type
    raise TypeError(
TypeError: type of test_list[0] must be int; got str instead

typeguard.check_type() raises TypeError when the type does not match.

Note: list[1, 2, 3] is not a valid type specification.

But it is valid syntax. Perhaps the question should be, in what circumstance (if any) is list[1,2,3] useful? If the answer is “never”, shouldn’t the interpreter complain about it?

Okay, I don’t know about your actual code, but for the this simple example that you have, mypy does helpfully tell you that you made a mistake

main.py:1: error: "list" expects 1 type argument, but 3 given  [type-arg]
main.py:1: error: Invalid type: try using Literal[1] instead?  [valid-type]
main.py:1: error: Invalid type: try using Literal[2] instead?  [valid-type]
main.py:1: error: Invalid type: try using Literal[3] instead?  [valid-type]
Found 4 errors in 1 file (checked 1 source file)

you can try it at mypy Playground

The answer is probably “never” but making the interpreter complain about it has two problems.

  1. Runtime checks are slow, better to let a type checker find out about it before runtime and have no effect on runtime performance.
  2. Too many runtime checks cause issues with innovation in the typing syntax, if some invalid type became valid later with a new PEP, the change cannot be easily back-ported to older versions by typing_extensions. I’m quoting @Jelle here, he had similar thing to say about another runtime check bpo-46644: No longer accept arbitrary callables as type arguments in generics by serhiy-storchaka · Pull Request #31159 · python/cpython · GitHub.

That said, if too many people are tripped by this behavior it may make sense to add a runtime check for this. But I feel this causes issues rarely and it is easy to catch later in your code because GenericAlias will behave very differently from the object you expected to create, and once you learn about this behavior you won’t repeat the mistake.

2 Likes

Perhaps it should be a SyntaxError if there is more than one argument?

That would prevent the likes of list[int, float], and wouldn’t prevent list[0].

I thought the proper way to do that was list[int | float]

2 Likes

Ah, yes, you are right. That’s not generalizable though, since for example dict takes two arguments. I imagine checking all such possibilities at runtime would be expensive.