Skip to content
This repository was archived by the owner on Sep 3, 2026. It is now read-only.
This repository was archived by the owner on Sep 3, 2026. It is now read-only.

How are annotations that are not types treated? #1451

Closed

Description

@jgarvin

AFAICT pytest doesn't type check this call at all. Is this intended behavior?

#!/usr/bin/env python
# -*- coding: utf-8 -*-

def foo(flag: bool) -> type:
    if flag:
        class Foo:
            x = 1
    else:
        class Foo:
            y = 1
    return Foo

def bar(arg: foo(False)) -> int:
    return arg.y + 1

a = foo(True) # not the same
print(bar(a))

Activity

  1. martindemello commented on Jun 27, 2023

    @martindemello
    Contributor

    the annotations are treated as Any which does disable type checking for them.

  2. jgarvin commented on Jun 27, 2023

    @jgarvin
    Author

    As a user can I count on this being the behavior going forward? I'm hesitant because mypy and pyright both reject it.

  3. martindemello commented on Jun 27, 2023

    @martindemello
    Contributor

    reject it as in simply raise an error saying that foo(False) is not a type? yeah, pytype probably should do that too - don't count on it as a guarantee. (pytype's general philosophy is to be lenient towards things that are technically wrong but used frequently in existing code, and gradually add stricter checks; this lets projects adopt pytype without having to fix hundreds of issues in currently common practices)

  4. jgarvin commented on Jun 28, 2023

    @jgarvin
    Author

    Might help if I explain my use case. I'm frequently writing bindings to another language that also has classes. It's useful to me to be able to express this with the class and annotation syntax. I can write out directly in Python what the class from the other lang looks like:

    @make_binding
    class Example:
        x: lang_int
        y: lang_str

    However occasionally figuring out what the type should be needs to involve some logic and processing of input files. This means I can't always just write a known type directly into the source code. I may want to write something like:

    @make_binding
    class Example:
        x: lang_int
        y: lang_str
        z: determine_z_type()

    This can happen because of things like C enum types where the underlying integer width depends on how many elements there are. Similarly it would be nice to be able to write

    @make_binding
    class Example:
        x: lang_int
        y: lang_str
        z: lang_array[lang_int, 5]

    Official PEP 484 annotations aren't supposed to contain anything other than types, this the need for Literal, but CPython can be made to accept the above either with the annotations future import or having lang_array be a class with a metaclass that defines __getitem__.

    I'd also be happy to use no_type_checking_decorator on make_binding but alas none of the major checkers seem to respect it.

  5. martindemello commented on Jun 28, 2023

    @martindemello
    Contributor

    this sounds like something typing.Annotated would work well for. you could e.g. say z: Annotated[Any, determine_z_type] and your runtime code would be responsible for pulling the value out and using it but the typecheckers would ignore it.

  6. martindemello commented on Jun 28, 2023

    @martindemello
    Contributor
    In [1]: from typing import Annotated, Any
    
    In [2]: def f(x): return int if x else str
    
    In [3]: class A:
       ...:     x: Annotated[Any, f(True)]
       ...: 
    
    In [4]: A.__annotations__
    Out[4]: {'x': typing.Annotated[typing.Any, <class 'int'>]}
    
  7. jgarvin commented on Jun 28, 2023

    @jgarvin
    Author

    That might be the best option, it's just significantly worse ergonomically. :(

  8. eltoder commented on Jul 18, 2023

    @eltoder

    @jgarvin You can cheat using TYPE_CHECKING:

    if TYPE_CHECKING:
        # what you want type checkers to think
        lang_int = int
        lang_str = str
        lang_z = int
    else:
        # what you need for your bindings
        lang_int = ?
        lang_str = ?
        lang_z = determine_z_type()
    
    @make_binding
    class Example:
        x: lang_int
        y: lang_str
        z: lang_z

    You can put the if almost anywhere, but it might be the easiest to have it in one/few places and define aliases that you can use for multiple struct definitions. If you really don't care about type checking at all, you can make all these types alias to Any when TYPE_CHECKING is true.

    If you want more help from type checkers, you can always cheat less. For example, for enums, you can define python Enums or IntEnums to mirror your C enums and use them in the type annotations. Your code will then determine the correct C type to use based on the members of the python enum type. For fixed-size arrays you can use Annotated or a generic class with Literal, depending on what type you want to use in python. But the point is, you can mix and match more precise types with cheating as you like.

  9. jgarvin commented on Jul 18, 2023

    @jgarvin
    Author

    @eltoder that's a good hack, thanks. It's still too onerous for users of my decorator though, they would have to make up an identifier and do that check every time they wanted an expression.

  10. h-joo commented on Aug 20, 2025

    @h-joo
    Contributor

    Hi, thank you for your report. Google is shifting its effort to a different approach for type checking apart from pytype, and we're not planning to put in any effort in the near future in pytype, so we'll close this issue. Please see the announcement here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions