1

When I define a class attribute named type, a type[Foo] annotation inside the same class causes mypy to report that the type name is a variable and therefore “not valid as a type”.

class Foo:
    type: str
    bar: type[Foo]

mypy produces:

main.py:3: error: Variable "__main__.Foo.type" is not valid as a type  [valid-type]

I expected type[Foo] to refer to the built-in type, but mypy treats type as the class attribute I just declared.

I found those workarounds:

  • Rename the attribute (e.g., type_ or kind).
  • Use the old typing.Type instead of new builtin type

I found in PEP8 a convention of naming

single_trailing_underscore_: used by convention to avoid conflicts with Python keyword

but type is not a keyword.

Is this is aligned with language semantics, or just a mypy limitation?

3

1 Answer 1

0

This is the combination of two problems:

  1. Variables can't be in static annotations.
  2. Python won't stop you from redeclaring built-in names (which you should never do).

In this case, you've declared the name type to be a variable of type str - and, in the process, overridden its built-in definition as a base for type[] annotations. As such, you won't be able to use type in annotations anymore (at least within the scope of Foo).

This isn't specific to type; you can run into similar issues if you manually reassign other built-in names.

list = object()
l: list[int] # type checker error

int = object()
i: int # type checker error

object = object()
o: object # type checker error

My preferred solution would be to give your variable a totally new name. kind is a good example. Adding on a trailing underscore is fine too, but it's less readable IMO.

Some minutia:

  • type actually is a keyword (it's a "soft keyword", which means you're allowed to use it as a name), but that doesn't matter for your question since it's only a soft keyword in the context of type statements, not class objects (which is what you're currently using it for).
  • Don't use typing.Type. It's deprecated.
Sign up to request clarification or add additional context in comments.

6 Comments

Sometimes you have no way to rename the field (e.g. a model for a third-party API response). You can use typing.Type in those cases (but keep in mind it may be removed in some later python version) or declare your own global alias Type: TypeAlias = type[T] at the module scope and using Type where type is shadowed.
type: str in this case does not override the builtin type, since it doesn't create any local name. mypy just seems to equate this usage with an actual name creation, like type: str = 'foo'.
@deceze It's not overridden in the strict sense of having a new value, but it's overridden in the semantic sense of now being a variable and not a type constant.
Wrong. type: str does not change anything about the name type floating around. After this, type still refers to __builtins__.type, just as it did before this line.
@deceze Again - yes. The name still refers to the same thing at runtime. But to a type checker, it is now a string variable. Is the word “override” confusing? Is there a better word I should use?
Well, the type checker should type check according to what's actually happening in Python. If the actual Python code is type safe, the type checker shouldn't complain about it. It could complain about the possibly misleading style as a linter rule if it wanted to. So it's mostly a shortcoming in the type checker, which looks at the situation too simplistically.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.