最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Why can't Pylance fully infer the return type of this function? - Stack Overflow

programmeradmin0浏览0评论

I am using Python 3.11.4, and Pydantic 2.10.0. The following is a toy example of a real-world problem.

I have defined two Pydantic Basemodels, and created a list containing instances of both, as follows.

import pydantic


class Foo(pydantic.BaseModel):
    a: int
    b: int


class Bar(pydantic.BaseModel):
    c: int
    d: int

non_homogeneous_container = [
    Foo(a=1, b=5),
    Foo(a=7, b=8),
    Bar(c=5, d=3),
    Bar(c=15, d=12),
]

I want to write a function that takes in such a list, as well as a target_type, and returns a list containing only those objects from the original list that conform to the specified target_type. I have written such a function, as below, and provided (what I believe to be) the appropriate type annotation for my type checker (Pylance, in VSCode).

from typing import Any, Type, TypeVar

T = TypeVar("T", bound=pydantic.BaseModel)


def extract_objects_of_specified_type(
    mixed_up_list: list[Any],
    target_type: Type[T],
) -> list[T]:
    extracted_list: list[T] = []
    for each_object in mixed_up_list:
        if isinstance(each_object, target_type):
            extracted_list.append(each_object)
    return extracted_list

Invoking my function with my mixed up list of objects, I obtain the expected list containing only objects of the target_type.

list_of_only_foos = extract_objects_of_specified_type(
    mixed_up_list=non_homogeneous_container, target_type=Foo
)

print (list_of_only_foos)
# Result: [Foo(a=1, b=5), Foo(a=7, b=8)]

However, I have a yellow underline on the line where I invoke my function; Pylance says the argument type is partially unknown. The bit that's underlined and the associated Pylance report are as below:

# list_of_only_foos: list[Foo] = extract_objects_of_specified_type(
#     mixed_up_list=non_homogeneous_container, target_type=Foo
# )                 ^^^^^^^^^^^^^^^^^^^^^^^^^
# Linter report: 
# Argument type is partially unknown
#   Argument corresponds to parameter "mixed_up_list" in function "extract_objects_of_specified_type"
#   Argument type is "list[Unknown]"PylancereportUnknownArgumentType

The linter report goes away when I annotate my input list of objects as below:

non_homogeneous_container: list[Foo | Bar] = [
    Foo(a=1, b=5),
    Foo(a=7, b=8),
    Bar(c=5, d=3),
    Bar(c=15, d=12),
]

or

non_homogeneous_container: list[pydantic.BaseModel] = [
    Foo(a=1, b=5),
    Foo(a=7, b=8),
    Bar(c=5, d=3),
    Bar(c=15, d=12),
]

I find this unsatisfactory because it obliges me to know in advance what objects are expected to be in the list I feed to my function.

I have tried to get around this issue by wrapping my function in a class that employs a Generic (below), but this made no difference.

class ExtractSpecificType(Generic[T]):
    @staticmethod
    def extract_objects_of_specified_type(
        mixed_up_list: list[Any],
        target_type: Type[T],
    ) -> list[T]:
        extracted_list: list[T] = []
        for each_object in mixed_up_list:
            if isinstance(each_object, target_type):
                extracted_list.append(each_object)
        return extracted_list

My function as originally constructed does what I want it to do - my question is about WHY the type checker is unhappy. Is my type annotation imprecise in some way? If so, what should I do differently?

Thank you.

EDIT

user2357112's comment and InSync's answer indicate that that this behaviour has something to do with the prevailing type-checker settings about when and where to warn about missing/ambiguous type annotations. For context, I have reproduced my VSCode type-checking settings below.

  "python.analysis.typeCheckingMode": "standard",
  "python.analysis.diagnosticSeverityOverrides": {
    "reportGeneralTypeIssues": true,
    "reportUnknownArgumentType": "warning",
    "reportUnknownParameterType": "warning",
    "reportMissingTypeArgument ": "warning",
    "reportMissingParameterType": "warning",
    "reportReturnType": "error",
    "reportUnusedImport": "warning",
    "reportUnnecessaryTypeIgnoreComment": "error"
  },

I am using Python 3.11.4, and Pydantic 2.10.0. The following is a toy example of a real-world problem.

I have defined two Pydantic Basemodels, and created a list containing instances of both, as follows.

import pydantic


class Foo(pydantic.BaseModel):
    a: int
    b: int


class Bar(pydantic.BaseModel):
    c: int
    d: int

non_homogeneous_container = [
    Foo(a=1, b=5),
    Foo(a=7, b=8),
    Bar(c=5, d=3),
    Bar(c=15, d=12),
]

I want to write a function that takes in such a list, as well as a target_type, and returns a list containing only those objects from the original list that conform to the specified target_type. I have written such a function, as below, and provided (what I believe to be) the appropriate type annotation for my type checker (Pylance, in VSCode).

from typing import Any, Type, TypeVar

T = TypeVar("T", bound=pydantic.BaseModel)


def extract_objects_of_specified_type(
    mixed_up_list: list[Any],
    target_type: Type[T],
) -> list[T]:
    extracted_list: list[T] = []
    for each_object in mixed_up_list:
        if isinstance(each_object, target_type):
            extracted_list.append(each_object)
    return extracted_list

Invoking my function with my mixed up list of objects, I obtain the expected list containing only objects of the target_type.

list_of_only_foos = extract_objects_of_specified_type(
    mixed_up_list=non_homogeneous_container, target_type=Foo
)

print (list_of_only_foos)
# Result: [Foo(a=1, b=5), Foo(a=7, b=8)]

However, I have a yellow underline on the line where I invoke my function; Pylance says the argument type is partially unknown. The bit that's underlined and the associated Pylance report are as below:

# list_of_only_foos: list[Foo] = extract_objects_of_specified_type(
#     mixed_up_list=non_homogeneous_container, target_type=Foo
# )                 ^^^^^^^^^^^^^^^^^^^^^^^^^
# Linter report: 
# Argument type is partially unknown
#   Argument corresponds to parameter "mixed_up_list" in function "extract_objects_of_specified_type"
#   Argument type is "list[Unknown]"PylancereportUnknownArgumentType

The linter report goes away when I annotate my input list of objects as below:

non_homogeneous_container: list[Foo | Bar] = [
    Foo(a=1, b=5),
    Foo(a=7, b=8),
    Bar(c=5, d=3),
    Bar(c=15, d=12),
]

or

non_homogeneous_container: list[pydantic.BaseModel] = [
    Foo(a=1, b=5),
    Foo(a=7, b=8),
    Bar(c=5, d=3),
    Bar(c=15, d=12),
]

I find this unsatisfactory because it obliges me to know in advance what objects are expected to be in the list I feed to my function.

I have tried to get around this issue by wrapping my function in a class that employs a Generic (below), but this made no difference.

class ExtractSpecificType(Generic[T]):
    @staticmethod
    def extract_objects_of_specified_type(
        mixed_up_list: list[Any],
        target_type: Type[T],
    ) -> list[T]:
        extracted_list: list[T] = []
        for each_object in mixed_up_list:
            if isinstance(each_object, target_type):
                extracted_list.append(each_object)
        return extracted_list

My function as originally constructed does what I want it to do - my question is about WHY the type checker is unhappy. Is my type annotation imprecise in some way? If so, what should I do differently?

Thank you.

EDIT

user2357112's comment and InSync's answer indicate that that this behaviour has something to do with the prevailing type-checker settings about when and where to warn about missing/ambiguous type annotations. For context, I have reproduced my VSCode type-checking settings below.

  "python.analysis.typeCheckingMode": "standard",
  "python.analysis.diagnosticSeverityOverrides": {
    "reportGeneralTypeIssues": true,
    "reportUnknownArgumentType": "warning",
    "reportUnknownParameterType": "warning",
    "reportMissingTypeArgument ": "warning",
    "reportMissingParameterType": "warning",
    "reportReturnType": "error",
    "reportUnusedImport": "warning",
    "reportUnnecessaryTypeIgnoreComment": "error"
  },
Share Improve this question edited Feb 6 at 2:49 Vin asked Feb 6 at 1:41 VinVin 1,0371 gold badge8 silver badges16 bronze badges 2
  • 1 It doesn't look like this has anything to do with the function you wrote - it looks like Pylance is complaining about non_homogeneous_container, but it's decided to do that when you use it as a function argument instead of when you initialize it, for some reason. – user2357112 Commented Feb 6 at 2:10
  • Thank you - you're right, when I annotate non_homogeneous_container as list[Any] (which is perfectly accurate), the warning goes away - and the type checker correctly infers the type of list_of_only_foos as list[Foo]. It's strange that the type checker chose to speak up where and when it did. – Vin Commented Feb 6 at 2:19
Add a comment  | 

1 Answer 1

Reset to default 3

You have reportUnknownArgumentType enabled, but not strictListInference, which controls how Pyright/Pylance infers list types.

When inferring the type of a list, use strict type assumptions. For example, the expression [1, 'a', 3.4] could be inferred to be of type list[Any] or list[int | str | float]. If this setting is true, it will use the latter (stricter) type.

A small example demonstrating how it works:

class A: ...
class B: ...

(playground)

# strictListInference = false (default)
l = [A(), A(), B(), B()]
reveal_type(l)  # list[Unknown]

(playground)

# strictListInference = true
l = [A(), A(), B(), B()]
reveal_type(l)  # list[A | B]

This setting can be specified using either pyrightconfig.json or pyproject.toml:

// pyrightconfig.json
{
  "strictListInference": true,
}
# pyproject.toml
[tool.pyright]
strictListInference = true
发布评论

评论列表(0)

  1. 暂无评论