python / python/mypy

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

Offen
#14,967 1 Kommentar 2 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

bug topic-mypyc
Vorherrschende Sprache
Python
Sterne
20.6k
Forks
3.3k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

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

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Reproduziere den Bericht mit mypyc und mypy gegen polars/utils/_construction.py und konzentriere dich auf die Einstiegspunkte numpy_to_pydf und iterable_to_pydf. Verfolge, wie mypyc mit den annotierten optionalen lokalen Variablen umgeht, und vergleiche seine Diagnosen mit mypy; abgeschlossen ist die Aufgabe, wenn die gemeldeten False-Positive-Fehler nicht mehr auftreten und die gültige Typprüfung intakt bleibt.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
compilers, devtools
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
30/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.