admin管理员组

文章数量:1401176

I'm using Python 3.12 and I would like to return polygon.exterior.coords as a type of list[tuple[float, float]] so that I can correctly enforce typing later in the program. I'm wondering if there is a more elegant solution:

from shapely import Polygon
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
data = polygon.exterior.coords
# This raises a run time error: IndexError: tuple index out of range
data[0][2]

I would like to do:

data: list[tuple[float, float]] = list(polygon.exterior.coords)
# This now correctly shows a type hint error: "Index 2 is out of range for type tuple[float, float]"
data[0][2]

However the line data: list[tuple[float, float]] = list(polygon.exterior.coords) then shows a type hint error of:

Type "list[tuple[float, ...]]" is not assignable to declared type "list[tuple[float, float]]"
    "list[tuple[float, ...]]" is not assignable to "list[tuple[float, float]]"
    Type parameter "_T@list" is invariant, but "tuple[float, ...]" is not the same as "tuple[float, float]"
    Consider switching from "list" to "Sequence" which is covariant

I can solve this by using this code:

data = type_safe_list(polygon.exterior.coords)

def type_safe_list(geometry: Polygon) -> list[tuple[float, float]]:
    coords: list[tuple[float, float]] = []
    for point in clipped_geometry.exterior.coords:
        x, y = point
        coords.append((x, y))
    return coords

But perhaps there's a more elegant solution that doesn't incur a run time penalty? (not that performance is currently an issue for this part of the code). Also I'm actually trying to handle other types like MultiPolygon so the actual implementation I currently have is:

def type_safe_list(geometry: Polygon | MultiPolygon) -> list[tuple[float, float]] | list[list[tuple[float, float]]]:
    if isinstance(geometry, Polygon):
        coords: list[tuple[float, float]] = []
        for point in geometry.exterior.coords:
            x, y = point
            coords.append((x, y))
        return coords
    elif isinstance(geometry, MultiPolygon):
        multi_coords: list[list[tuple[float, float]]] = []
        for poly in geometry.geoms:
            coords: list[tuple[float, float]] = []
            for point in poly.exterior.coords:
                x, y = point
                coords.append((x, y))
            multi_coords.append(coords)
        return multi_coords
    else:
        raise NotImplementedError(f"Unhandled type: {type(geometry)}")
    return []

I'm using Python 3.12 and I would like to return polygon.exterior.coords as a type of list[tuple[float, float]] so that I can correctly enforce typing later in the program. I'm wondering if there is a more elegant solution:

from shapely import Polygon
polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
data = polygon.exterior.coords
# This raises a run time error: IndexError: tuple index out of range
data[0][2]

I would like to do:

data: list[tuple[float, float]] = list(polygon.exterior.coords)
# This now correctly shows a type hint error: "Index 2 is out of range for type tuple[float, float]"
data[0][2]

However the line data: list[tuple[float, float]] = list(polygon.exterior.coords) then shows a type hint error of:

Type "list[tuple[float, ...]]" is not assignable to declared type "list[tuple[float, float]]"
    "list[tuple[float, ...]]" is not assignable to "list[tuple[float, float]]"
    Type parameter "_T@list" is invariant, but "tuple[float, ...]" is not the same as "tuple[float, float]"
    Consider switching from "list" to "Sequence" which is covariant

I can solve this by using this code:

data = type_safe_list(polygon.exterior.coords)

def type_safe_list(geometry: Polygon) -> list[tuple[float, float]]:
    coords: list[tuple[float, float]] = []
    for point in clipped_geometry.exterior.coords:
        x, y = point
        coords.append((x, y))
    return coords

But perhaps there's a more elegant solution that doesn't incur a run time penalty? (not that performance is currently an issue for this part of the code). Also I'm actually trying to handle other types like MultiPolygon so the actual implementation I currently have is:

def type_safe_list(geometry: Polygon | MultiPolygon) -> list[tuple[float, float]] | list[list[tuple[float, float]]]:
    if isinstance(geometry, Polygon):
        coords: list[tuple[float, float]] = []
        for point in geometry.exterior.coords:
            x, y = point
            coords.append((x, y))
        return coords
    elif isinstance(geometry, MultiPolygon):
        multi_coords: list[list[tuple[float, float]]] = []
        for poly in geometry.geoms:
            coords: list[tuple[float, float]] = []
            for point in poly.exterior.coords:
                x, y = point
                coords.append((x, y))
            multi_coords.append(coords)
        return multi_coords
    else:
        raise NotImplementedError(f"Unhandled type: {type(geometry)}")
    return []
Share Improve this question edited Mar 25 at 14:11 InSync 11.1k4 gold badges18 silver badges56 bronze badges asked Mar 25 at 14:07 AJPAJP 28.6k26 gold badges94 silver badges144 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 3

The type of geometry.exterior.coords is CoordinateSequence, whose __iter__ only ever returns an interator of tuple[float, ...]:

class CoordinateSequence:
    ...
    def __iter__(self) -> Iterator[tuple[float, ...]]: ...

There is no way to change this other than editing the stubs of shapely itself.

Thus, cast() is probably your best bet:

def type_safe_list(geometry: Polygon) -> list[tuple[float, float]]:
    return cast(list[tuple[float, float]], list(geometry.exterior.coords))

A Shapely polygon's vertices are allowed to have z coordinates. If the vertices have z coordinates, then the elements of polygon.exterior.coords will be 3-element tuples.

This is why Shapely's type hints are the way they are, and why mypy rejects the assignment you want to perform: that assignment is not actually type-safe.

If you're sure you don't have z coordinates in your data, and you don't want to spend time checking, you can just typing.cast it:

from typing import cast

data: list[tuple[float, float]] = cast(list[tuple[float, float]],
                                       list(polygon.exterior.coords))

本文标签: Elegantly handling python type hint for Shapely polygonexteriorcoordsStack Overflow