python / python/mypy

Error: "Local variable "x" has inferred type None; add an annotation", while annotation seems to be provided.

オープン
#14,967 コメント 1 件 リアクション 2 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

bug topic-mypyc
主要言語
Python
スター
20.6k
フォーク
3.3k
PR マージ指標
PR 指標を取得中

説明

Bug Report

(A clear and concise description of what the bug is.)
mypyc complains about error: Local variable "x" has inferred type None; add an annotation, while it seems to me, that the annotation is added. mypy doesn't complain about it at all.

To Reproduce

git clone ---depth 1 https://github.com/pola-rs/polars

cd polars/py-polars

$ mypyc polars/utils/_construction.py
polars/utils/_construction.py:1050: error: Local variable "orient" has inferred type None; add an annotation
polars/utils/_construction.py:1248: error: Local variable "schema" has inferred type None; add an annotation

$ mypy polars/utils/_construction.py
Success: no issues found in 1 source file
$ tail -n +1046 polars/utils/_construction.py | head
def numpy_to_pydf(
    data: np.ndarray[Any, Any],
    schema: SchemaDefinition | None = None,
    schema_overrides: SchemaDict | None = None,
    orient: Orientation | None = None,
    nan_to_null: bool = False,
) -> PyDataFrame:
    """Construct a PyDataFrame from a numpy ndarray."""
    shape = data.shape


$ tail -n +1246 polars/utils/_construction.py | head
def iterable_to_pydf(
    data: Iterable[Any],
    schema: SchemaDefinition | None = None,
    schema_overrides: SchemaDict | None = None,
    orient: Orientation | None = None,
    chunk_size: int | None = None,
    infer_schema_length: int | None = N_INFER_DEFAULT,
) -> PyDataFrame:
    """Construct a PyDataFrame from an iterable/generator."""
    original_schema = schema

The polars/utils/_construction.py:1050: error: Local variable "orient" has inferred type None; add an annotation can be "fixed" by not accessing orient in the else ValueError branch.

def numpy_to_pydf(
    data: np.ndarray[Any, Any],
    schema: SchemaDefinition | None = None,
    schema_overrides: SchemaDict | None = None,
    orient: Orientation | None = None,
    nan_to_null: bool = False,
) -> PyDataFrame:
    """Construct a PyDataFrame from a numpy ndarray."""
    shape = data.shape

    # Unpack columns
    if shape == (0,):
        n_columns = 0

    elif len(shape) == 1:
        n_columns = 1

    elif len(shape) == 2:
        # default convention
        # first axis is rows, second axis is columns
        if orient is None and schema is None:
            n_columns = shape[1]
            orient = "row"

        # Infer orientation if columns argument is given
        elif orient is None and schema is not None:
            if len(schema) == shape[0]:
                orient = "col"
                n_columns = shape[0]
            else:
                orient = "row"
                n_columns = shape[1]

        elif orient == "row":
            n_columns = shape[1]
        elif orient == "col":
            n_columns = shape[0]
        else:
            raise ValueError(
                # Only commenting out `{orient}`, seems to work.
                # f"orient must be one of {{'col', 'row', None}}, got {orient} instead."
                f"orient must be one of {{'col', 'row', None}},"  # got {orient} instead."
            )
    else:
        raise ValueError(
            "Cannot create DataFrame from numpy array with more than two dimensions."
        )

    if schema is not None and len(schema) != n_columns:
        raise ValueError("Dimensions of columns arg must match data dimensions.")

    column_names, schema_overrides = _unpack_schema(
        schema, schema_overrides=schema_overrides, n_expected=n_columns
    )

    # Convert data to series
    if shape == (0,):
        data_series = []

    elif len(shape) == 1:
        data_series = [
            pli.Series(
                name=column_names[0],
                values=data,
                dtype=schema_overrides.get(column_names[0]),
                nan_to_null=nan_to_null,
            )._s
        ]
    else:
        if orient == "row":
            data_series = [
                pli.Series(
                    name=column_names[i],
                    values=data[:, i],
                    dtype=schema_overrides.get(column_names[i]),
                    nan_to_null=nan_to_null,
                )._s
                for i in range(n_columns)
            ]
        else:
            data_series = [
                pli.Series(
                    name=column_names[i],
                    values=data[i],
                    dtype=schema_overrides.get(column_names[i]),
                    nan_to_null=nan_to_null,
                )._s
                for i in range(n_columns)
            ]

    data_series = _handle_columns_arg(data_series, columns=column_names)
    return PyDataFrame(data_series)

Other things didn't seem to work:

❯ diff -u polars/utils/_construction.py polars/utils/_construction.py2
--- polars/utils/_construction.py	2023-03-27 15:17:50.072747854 +0200
+++ polars/utils/_construction.py2	2023-03-27 16:24:24.728991744 +0200
@@ -1076,14 +1076,16 @@
                 orient = "row"
                 n_columns = shape[1]
 
-        elif orient == "row":
+        elif orient is not None and orient == "row":
             n_columns = shape[1]
-        elif orient == "col":
+        elif orient is not None and orient == "col":
             n_columns = shape[0]
         else:
+            #assert orient is not None
             raise ValueError(
 #                f"orient must be one of {{'col', 'row', None}}, got {orient} instead."
-                 f"orient must be one of {{'col', 'row', None}},"  # got {orient} instead."
+                f"orient must be one of {{'col', 'row', None}}, got {orient if orient is not None else 'None'} instead."
             )
     else:
         raise ValueError(
@@ -1111,6 +1113,7 @@
             )._s
         ]
     else:
+        assert orient is not None
         if orient == "row":
             data_series = [
                 pli.Series(

For the other function, I have no clue how to "solve" polars/utils/_construction.py:1248: error: Local variable "schema" has inferred type None; add an annotation:

def iterable_to_pydf(
    data: Iterable[Any],
    schema: SchemaDefinition | None = None,
    schema_overrides: SchemaDict | None = None,
    orient: Orientation | None = None,
    chunk_size: int | None = None,
    infer_schema_length: int | None = N_INFER_DEFAULT,
) -> PyDataFrame:
    """Construct a PyDataFrame from an iterable/generator."""
    original_schema = schema
    column_names: list[str] = []
    dtypes_by_idx: dict[int, PolarsDataType] = {}
    if schema is not None:
        column_names, schema_overrides = _unpack_schema(
            schema, schema_overrides=schema_overrides
        )
    elif schema_overrides:
        _, schema_overrides = _unpack_schema(schema, schema_overrides=schema_overrides)

    if not isinstance(data, Generator):
        data = iter(data)

    if orient == "col":
        if column_names and schema_overrides:
            dtypes_by_idx = {
                idx: schema_overrides.get(col, Unknown)
                for idx, col in enumerate(column_names)
            }

        return pli.DataFrame(
            {
                (column_names[idx] if column_names else f"column_{idx}"): pli.Series(
                    coldata, dtype=dtypes_by_idx.get(idx)
                )
                for idx, coldata in enumerate(data)
            }
        )._df

    def to_frame_chunk(values: list[Any], schema: SchemaDefinition | None) -> DataFrame:
        return pli.DataFrame(
            data=values,
            schema=schema,
            orient="row",
            infer_schema_length=infer_schema_length,
        )

    n_chunks = 0
    n_chunk_elems = 1_000_000

    if chunk_size:
        adaptive_chunk_size = chunk_size
    elif column_names:
        adaptive_chunk_size = n_chunk_elems // len(column_names)
    else:
        adaptive_chunk_size = None

    df: DataFrame = None  # type: ignore[assignment]
    chunk_size = max(
        (infer_schema_length or 0),
        (adaptive_chunk_size or 1000),
    )
    while True:
        values = list(islice(data, chunk_size))
        if not values:
            break
        frame_chunk = to_frame_chunk(values, original_schema)
        if df is None:
            df = frame_chunk
            if not original_schema:
                original_schema = list(df.schema.items())
            if chunk_size != adaptive_chunk_size:
                chunk_size = adaptive_chunk_size = n_chunk_elems // len(df.columns)
        else:
            df.vstack(frame_chunk, in_place=True)
            n_chunks += 1

    if df is None:
        df = to_frame_chunk([], original_schema)

    return (df.rechunk() if n_chunks > 0 else df)._df

Expected Behavior

Actual Behavior

$ mypyc polars/utils/_construction.py
polars/utils/_construction.py:1050: error: Local variable "orient" has inferred type None; add an annotation
polars/utils/_construction.py:1248: error: Local variable "schema" has inferred type None; add an annotation

Your Environment

  • Mypy version used: 1.1.1 and 1.3.0+dev.2e75cbaa8e5c0fb8aa3548d5c7c8ccfd00131057
  • Mypy command-line flags:
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.8.10

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

polars/utils/_construction.py に対して mypyc と mypy を使って報告を再現し、numpy_to_pydf と iterable_to_pydf のエントリポイントに焦点を当てます。mypy 要素が注釈付きのオプショナルなローカル変数をどのように処理するかを追跡し、その診断結果を mypy と比較します。報告された誤検出エラーが発生しなくなり、正しい型チェックが維持されていれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
compilers, devtools
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
30/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。