ronantakizawa/codereview-bench huggingface dataset:
Repository: numpy/numpy
File: numpy/ma/core.pyi (Python)

Code before:
    ndmin=...,
): ...
def is_masked(x): ...

class _extrema_operation(_MaskedUFunc):
    compare: Any
    fill_value_func: Any
    def __init__(self, ufunc, compare, fill_value): ...
    # NOTE: in practice `b` has a default value, but users should
    # explicitly provide a value here as the default is deprecated
    def __call__(self, a, b): ...
    def reduce(self, target, axis=...): ...
    def outer(self, a, b): ...

@overload
def min(
    obj: _ArrayLike[_SCT],
    axis: None = ...,
    out: None = ...,
    fill_value: _ScalarLike_co | None = ...,
    keepdims: Literal[False] = ...,
) -> _SCT: ...

@overload
def min(
    obj: _ArrayLike[Any],
    axis: _ShapeLike | None = ...,
    out: None = ...,
    fill_value: _ScalarLike_co | None = ...,
    keepdims: bool = ...,
) -> Any: ...

@overload
def min(
    obj: _ArrayLike[Any],
    axis: _ShapeLike | None = ...,
    out: _ArrayType = ...,
    fill_value: _ScalarLike_co | None = ...,
    keepdims: bool = ...,
) -> _ArrayType: ...

def max(obj, axis=..., out=..., fill_value=..., keepdims=...): ...
def ptp(obj, axis=..., out=..., fill_value=..., keepdims=...): ...

class _frommethod:
    __name__: Any
    __doc__: Any
    reversed: Any
    def __init__(self, methodname, reversed=...): ...
    def getdoc(self): ...
    def __call__(self, a, *args, **params): ...

Review comment (suggestion):
`_ArrayLike[Any]` doesn't accept (nested) sequences of "bare" builtin scalars, e.g. `[3, 1, 4]`, and you could instead do something like

```suggestion
    obj: ArrayLike,
```

or alternatively one of the `_ArrayLike{}_co` ones, in order to avoid any dtypes that `min` is allergic to, if any <sub>pun intended</sub>.

Code after:
    def __setattr__(self, attr, value): ...

masked: MaskedConstant
masked_singleton: MaskedConstant
masked_array = MaskedArray

def array(
    data,
    dtype=...,
    copy=...,
    order=...,
    mask=...,
    fill_value=...,
    keep_mask=...,
    hard_mask=...,
    shrink=...,
    subok=...,
    ndmin=...,
): ...
def is_masked(x): ...

class _extrema_operation(_MaskedUFunc):
    compare: Any
    fill_value_func: Any
    def __init__(self, ufunc, compare, fill_value): ...
    # NOTE: in practice `b` has a default value, but users should
    # explicitly provide a value here as the default is deprecated
    def __call__(self, a, b): ...
    def reduce(self, target, axis=...): ...
    def outer(self, a, b): ...

@overload
def min(
    obj: _ArrayLike[_SCT],
    axis: None = None,
    out: None = None,
    fill_value: _ScalarLike_co | None = None,
    keepdims: Literal[False] | _NoValueType = ...,
) -> _SCT: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    out: None = None,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...
) -> Any: ...
@overload
def min(
    obj: ArrayLike,
    axis: None,
Repository: numpy/numpy
File: numpy/typing/tests/data/reveal/ma.pyi (Python)

Code before:


_ScalarType_co = TypeVar("_ScalarType_co", bound=generic, covariant=True)
MaskedNDArray: TypeAlias = np.ma.MaskedArray[_Shape, dtype[_ScalarType_co]]

class MaskedNDArraySubclass(MaskedNDArray[np.complex128]): ...

MAR_b: MaskedNDArray[np.bool]
MAR_f4: MaskedNDArray[np.float32]
MAR_i8: MaskedNDArray[np.int64]
MAR_subclass: MaskedNDArraySubclass
MAR_1d: np.ma.MaskedArray[tuple[int], np.dtype[Any]]

b: np.bool
f4: np.float32
i8: np.int64
f: float

assert_type(MAR_1d.shape, tuple[int])

assert_type(MAR_f4.dtype, np.dtype[np.float32])

assert_type(int(MAR_i8), int)
assert_type(float(MAR_f4), float)

assert_type(np.ma.min(MAR_b), np.bool)
assert_type(np.ma.min(MAR_f4), np.float32)
assert_type(np.ma.min(MAR_b, axis=0), Any)
assert_type(np.ma.min(MAR_f4, axis=0), Any)
assert_type(np.ma.min(MAR_b, keepdims=True), Any)
assert_type(np.ma.min(MAR_f4, keepdims=True), Any)
assert_type(np.ma.min(MAR_f4, out=MAR_subclass), MaskedNDArraySubclass)


Review comment (suggestion):
`keepdims=True` shouldn't be too difficult to overload as well, in case you feel like it (and it's also fine if you don't)

Code after:


_ScalarType_co = TypeVar("_ScalarType_co", bound=generic, covariant=True)
MaskedNDArray: TypeAlias = np.ma.MaskedArray[_Shape, dtype[_ScalarType_co]]

class MaskedNDArraySubclass(MaskedNDArray[np.complex128]): ...

MAR_b: MaskedNDArray[np.bool]
MAR_f4: MaskedNDArray[np.float32]
MAR_i8: MaskedNDArray[np.int64]
MAR_subclass: MaskedNDArraySubclass
MAR_1d: np.ma.MaskedArray[tuple[int], np.dtype[Any]]

assert_type(MAR_1d.shape, tuple[int])

assert_type(MAR_f4.dtype, np.dtype[np.float32])

assert_type(int(MAR_i8), int)
assert_type(float(MAR_f4), float)

assert_type(np.ma.min(MAR_b), np.bool)
assert_type(np.ma.min(MAR_f4), np.float32)
assert_type(np.ma.min(MAR_b, axis=0), Any)
assert_type(np.ma.min(MAR_f4, axis=0), Any)
assert_type(np.ma.min(MAR_b, keepdims=True), Any)
assert_type(np.ma.min(MAR_f4, keepdims=True), Any)
assert_type(np.ma.min(MAR_f4, out=MAR_subclass), MaskedNDArraySubclass)
assert_type(np.ma.min(MAR_f4, None, MAR_subclass), MaskedNDArraySubclass)
Repository: numpy/numpy
File: numpy/ma/core.pyi (Python)

Code before:
    def __call__(self, a, b): ...
    def reduce(self, target, axis=...): ...
    def outer(self, a, b): ...

@overload
def min(
    obj: _ArrayLike[_SCT],
    axis: None = None,
    out: None = None,
    fill_value: _ScalarLike_co | None = None,
    keepdims: Literal[False] | _NoValueType = ...,
) -> _SCT: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    out: None = None,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...
) -> Any: ...
@overload
def min(
    obj: ArrayLike,
    axis: None,
    out: _ArrayType,
    fill_value: _ScalarLike_co | None = ...,
    keepdims: bool | _NoValueType = ...,
) -> _ArrayType: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    *,
    out: _ArrayType,
    fill_value: _ScalarLike_co | None = ...,
    keepdims: bool | _NoValueType = ...,
) -> _ArrayType: ...

def max(obj, axis=..., out=..., fill_value=..., keepdims=...): ...
def ptp(obj, axis=..., out=..., fill_value=..., keepdims=...): ...

class _frommethod:
    __name__: Any
    __doc__: Any
    reversed: Any
    def __init__(self, methodname, reversed=...): ...
    def getdoc(self): ...
    def __call__(self, a, *args, **params): ...

all: _frommethod
anomalies: _frommethod

Review comment (suggestion):
```suggestion
    fill_value: _ScalarLike_co | None = None,
```

Code after:
    def __call__(self, a, b): ...
    def reduce(self, target, axis=...): ...
    def outer(self, a, b): ...

@overload
def min(
    obj: _ArrayLike[_SCT],
    axis: None = None,
    out: None = None,
    fill_value: _ScalarLike_co | None = None,
    keepdims: Literal[False] | _NoValueType = ...,
) -> _SCT: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    out: None = None,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...
) -> Any: ...
@overload
def min(
    obj: ArrayLike,
    axis: None,
    out: _ArrayType,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...,
) -> _ArrayType: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    *,
    out: _ArrayType,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...,
) -> _ArrayType: ...

def max(obj, axis=..., out=..., fill_value=..., keepdims=...): ...
def ptp(obj, axis=..., out=..., fill_value=..., keepdims=...): ...

class _frommethod:
    __name__: Any
    __doc__: Any
    reversed: Any
    def __init__(self, methodname, reversed=...): ...
    def getdoc(self): ...
    def __call__(self, a, *args, **params): ...

all: _frommethod
anomalies: _frommethod
Repository: numpy/numpy
File: numpy/ma/core.pyi (Python)

Code before:
    fill_value: _ScalarLike_co | None = None,
    keepdims: Literal[False] | _NoValueType = ...,
) -> _SCT: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    out: None = None,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...
) -> Any: ...
@overload
def min(
    obj: ArrayLike,
    axis: None,
    out: _ArrayType,
    fill_value: _ScalarLike_co | None = ...,
    keepdims: bool | _NoValueType = ...,
) -> _ArrayType: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    *,
    out: _ArrayType,
    fill_value: _ScalarLike_co | None = ...,
    keepdims: bool | _NoValueType = ...,
) -> _ArrayType: ...

def max(obj, axis=..., out=..., fill_value=..., keepdims=...): ...
def ptp(obj, axis=..., out=..., fill_value=..., keepdims=...): ...

class _frommethod:
    __name__: Any
    __doc__: Any
    reversed: Any
    def __init__(self, methodname, reversed=...): ...
    def getdoc(self): ...
    def __call__(self, a, *args, **params): ...

all: _frommethod
anomalies: _frommethod
anom: _frommethod
any: _frommethod
compress: _frommethod
cumprod: _frommethod
cumsum: _frommethod
copy: _frommethod
diagonal: _frommethod
harden_mask: _frommethod
ids: _frommethod

Review comment (suggestion):
```suggestion
    fill_value: _ScalarLike_co | None = None,
```

Code after:
    fill_value: _ScalarLike_co | None = None,
    keepdims: Literal[False] | _NoValueType = ...,
) -> _SCT: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    out: None = None,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...
) -> Any: ...
@overload
def min(
    obj: ArrayLike,
    axis: None,
    out: _ArrayType,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...,
) -> _ArrayType: ...
@overload
def min(
    obj: ArrayLike,
    axis: _ShapeLike | None = None,
    *,
    out: _ArrayType,
    fill_value: _ScalarLike_co | None = None,
    keepdims: bool | _NoValueType = ...,
) -> _ArrayType: ...

def max(obj, axis=..., out=..., fill_value=..., keepdims=...): ...
def ptp(obj, axis=..., out=..., fill_value=..., keepdims=...): ...

class _frommethod:
    __name__: Any
    __doc__: Any
    reversed: Any
    def __init__(self, methodname, reversed=...): ...
    def getdoc(self): ...
    def __call__(self, a, *args, **params): ...

all: _frommethod
anomalies: _frommethod
anom: _frommethod
any: _frommethod
compress: _frommethod
cumprod: _frommethod
cumsum: _frommethod
copy: _frommethod
diagonal: _frommethod
harden_mask: _frommethod
ids: _frommethod
Repository: numpy/numpy
File: numpy/ma/core.pyi (Python)

Code before:
    "soften_mask",
    "sometrue",
    "sort",
    "sqrt",
    "squeeze",
    "std",
    "subtract",
    "sum",
    "swapaxes",
    "take",
    "tan",
    "tanh",
    "trace",
    "transpose",
    "true_divide",
    "var",
    "where",
    "zeros",
    "zeros_like",
]

_ShapeType = TypeVar("_ShapeType", bound=tuple[int, ...])
_ShapeType_co = TypeVar("_ShapeType_co", bound=tuple[int, ...], covariant=True)
_DType = TypeVar("_DType", bound=dtype[Any])
_DType_co = TypeVar("_DType_co", bound=dtype[Any], covariant=True)
_MaskedArrayType = TypeVar("_MaskedArrayType", bound=MaskedArray[Any, Any])
_SCT = TypeVar("_SCT", bound=generic)
# A subset of `MaskedArray` that can be parametrized w.r.t. `np.generic`
_MaskedArray: TypeAlias = MaskedArray[Any, dtype[_SCT]]

MaskType = bool
nomask: bool

class MaskedArrayFutureWarning(FutureWarning): ...
class MAError(Exception): ...
class MaskError(MAError): ...

def default_fill_value(obj): ...
def minimum_fill_value(obj): ...
def maximum_fill_value(obj): ...
def set_fill_value(a, fill_value): ...
def common_fill_value(a, b): ...
def filled(a, fill_value=...): ...
def getdata(a, subok=...): ...
get_data = getdata

def fix_invalid(a, mask=..., copy=..., fill_value=...): ...

class _MaskedUFunc:
    f: Any
    __doc__: Any

Review comment (suggestion):
I believe this is only used for `out` parameter annotations. But this change made me realize that the `out` parameters in `np.ma` also accept `np.ndarray` instances:

```pycon
>>> import numpy as np
>>> np.ma.min([1,2,3], out=np.empty(()))
array(1.)
>>> type(_)
<class 'numpy.ndarray'>
```

So it would be more correct to restore the name, and instead widen the `bound` to `np.ndarray[Any, Any]`. 

Code after:
    "soften_mask",
    "sometrue",
    "sort",
    "sqrt",
    "squeeze",
    "std",
    "subtract",
    "sum",
    "swapaxes",
    "take",
    "tan",
    "tanh",
    "trace",
    "transpose",
    "true_divide",
    "var",
    "where",
    "zeros",
    "zeros_like",
]

_ShapeType = TypeVar("_ShapeType", bound=tuple[int, ...])
_ShapeType_co = TypeVar("_ShapeType_co", bound=tuple[int, ...], covariant=True)
_DType = TypeVar("_DType", bound=dtype[Any])
_DType_co = TypeVar("_DType_co", bound=dtype[Any], covariant=True)
_ArrayType = TypeVar("_ArrayType", bound=ndarray[Any, Any])
_SCT = TypeVar("_SCT", bound=generic)
# A subset of `MaskedArray` that can be parametrized w.r.t. `np.generic`
_MaskedArray: TypeAlias = MaskedArray[Any, dtype[_SCT]]

MaskType = bool
nomask: bool

class MaskedArrayFutureWarning(FutureWarning): ...
class MAError(Exception): ...
class MaskError(MAError): ...

def default_fill_value(obj): ...
def minimum_fill_value(obj): ...
def maximum_fill_value(obj): ...
def set_fill_value(a, fill_value): ...
def common_fill_value(a, b): ...
def filled(a, fill_value=...): ...
def getdata(a, subok=...): ...
get_data = getdata

def fix_invalid(a, mask=..., copy=..., fill_value=...): ...

class _MaskedUFunc:
    f: Any
    __doc__: Any
Repository: numpy/numpy
File: numpy/_core/src/common/simd/simd.inc.hpp (C++)

Code before:
template <typename TLane>
HWY_API Vec<TLane>
LoadU(const TLane *ptr)
{
    return hn::LoadU(_Tag<TLane>(), ptr);
}

/// Unaligned store of a vector to memory.
template <typename TLane>
HWY_API void
StoreU(const Vec<TLane> &a, TLane *ptr)
{
    hn::StoreU(a, _Tag<TLane>(), ptr);
}

/// Returns the number of vector lanes based on the lane type.
template <typename TLane>
HWY_API constexpr size_t
Lanes(TLane tag = 0)
{
    return hn::Lanes(_Tag<TLane>());
}

/// Returns an uninitialized N-lane vector.
template <typename TLane>
HWY_API Vec<TLane>
Undefined(TLane tag = 0)
{
    return hn::Undefined(_Tag<TLane>());
}

/// Returns N-lane vector with all lanes equal to zero.
template <typename TLane>
HWY_API Vec<TLane>
Zero(TLane tag = 0)
{
    return hn::Zero(_Tag<TLane>());
}

/// Returns N-lane vector with all lanes equal to the given value of type `TLane`.
template <typename TLane>
HWY_API Vec<TLane>
Set(TLane val)
{
    return hn::Set(_Tag<TLane>(), val);
}

/// Converts a mask to a vector based on the specified lane type.
template <typename TLane, typename TMask>
HWY_API Vec<TLane>
VecFromMask(const TMask &m)

Review comment (suggestion):
`hn::Lanes` is not always constexpr, see [this](https://github.com/numpy/numpy/actions/runs/14486834354/job/40634365835?pr=28718).

Code after:
template <typename TLane>
using Mask = hn::Mask<_Tag<TLane>>;

/// Unaligned load of a vector from memory.
template <typename TLane>
HWY_API Vec<TLane>
LoadU(const TLane *ptr)
{
    return hn::LoadU(_Tag<TLane>(), ptr);
}

/// Unaligned store of a vector to memory.
template <typename TLane>
HWY_API void
StoreU(const Vec<TLane> &a, TLane *ptr)
{
    hn::StoreU(a, _Tag<TLane>(), ptr);
}

/// Returns the number of vector lanes based on the lane type.
template <typename TLane>
HWY_API HWY_LANES_CONSTEXPR size_t
Lanes(TLane tag = 0)
{
    return hn::Lanes(_Tag<TLane>());
}

/// Returns an uninitialized N-lane vector.
template <typename TLane>
HWY_API Vec<TLane>
Undefined(TLane tag = 0)
{
    return hn::Undefined(_Tag<TLane>());
}

/// Returns N-lane vector with all lanes equal to zero.
template <typename TLane>
HWY_API Vec<TLane>
Zero(TLane tag = 0)
{
    return hn::Zero(_Tag<TLane>());
}

/// Returns N-lane vector with all lanes equal to the given value of type `TLane`.
template <typename TLane>
HWY_API Vec<TLane>
Set(TLane val)
{
    return hn::Set(_Tag<TLane>(), val);
}
Repository: numpy/numpy
File: numpy/_core/src/common/simd/simd.inc.hpp (C++)

Code before:
HWY_API Vec<TLaneTo>
BitCast(const TVec &v)
{
    return hn::BitCast(_Tag<TLaneTo>(), v);
}

// Import common Highway intrinsics
using hn::Abs;
using hn::Add;
using hn::And;
using hn::AndNot;
using hn::Div;
using hn::Eq;
using hn::Ge;
using hn::Gt;
using hn::Le;
using hn::Lt;
using hn::Max;
using hn::Min;
using hn::Mul;
using hn::Or;
using hn::Sqrt;
using hn::Sub;
using hn::Xor;

} // namespace anonymous

#endif  // NPY_SIMDX


Review comment (suggestion):
```suggestion
#endif  // NPY_SIMDX

} // namespace anonymous
```

Code after:
HWY_API Vec<TLaneTo>
BitCast(const TVec &v)
{
    return hn::BitCast(_Tag<TLaneTo>(), v);
}

// Import common Highway intrinsics
using hn::Abs;
using hn::Add;
using hn::And;
using hn::AndNot;
using hn::Div;
using hn::Eq;
using hn::Ge;
using hn::Gt;
using hn::Le;
using hn::Lt;
using hn::Max;
using hn::Min;
using hn::Mul;
using hn::Or;
using hn::Sqrt;
using hn::Sub;
using hn::Xor;

#endif  // NPY_HWY

}  // namespace
Repository: numpy/numpy
File: benchmarks/benchmarks/bench_ufunc.py (Python)

Code before:
    def setup(self):
        self.x = np.asarray(1.0)
        self.y = np.asarray(1.0 + 1j)
        self.z = complex(1.0, 1.0)

    def time_add_scalar(self):
        (self.x + self.x)

    def time_add_scalar_conv(self):
        (self.x + 1.0)

    def time_add_scalar_conv_complex(self):
        (self.y + self.z)


class ArgPack:
    __slots__ = ['args', 'kwargs']

    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def __repr__(self):
        return '({})'.format(', '.join(
            [repr(a) for a in self.args] +
            [f'{k}={repr(v)}' for k, v in self.kwargs.items()]
        ))


class ArgParsing(Benchmark):
    # In order to benchmark the speed of argument parsing, all but the
    # out arguments are chosen such that they have no effect on the
    # calculation.  In particular, subok=True and where=True are
    # defaults, and the dtype is the correct one (the latter will
    # still have some effect on the search for the correct inner loop).
    x = np.array(1.)
    y = np.array(2.)
    out = np.array(3.)
    param_names = ['arg_kwarg']
    params = [[
        ArgPack(x, y),
        ArgPack(x, y, out),
        ArgPack(x, y, out=out),
        ArgPack(x, y, out=(out,)),
        ArgPack(x, y, out=out, subok=True, where=True),
        ArgPack(x, y, subok=True),
        ArgPack(x, y, subok=True, where=True),
        ArgPack(x, y, out, subok=True, where=True)
    ]]

    def time_add_arg_parsing(self, arg_pack):

Review comment (suggestion):
```suggestion
            [f'{k}={v!r}' for k, v in self.kwargs.items()]
```

Code after:
    def setup(self):
        self.x = np.asarray(1.0)
        self.y = np.asarray(1.0 + 1j)
        self.z = complex(1.0, 1.0)

    def time_add_scalar(self):
        (self.x + self.x)

    def time_add_scalar_conv(self):
        (self.x + 1.0)

    def time_add_scalar_conv_complex(self):
        (self.y + self.z)


class ArgPack:
    __slots__ = ['args', 'kwargs']

    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def __repr__(self):
        return '({})'.format(', '.join(
            [repr(a) for a in self.args] +
            [f'{k}={v!r}' for k, v in self.kwargs.items()]
        ))


class ArgParsing(Benchmark):
    # In order to benchmark the speed of argument parsing, all but the
    # out arguments are chosen such that they have no effect on the
    # calculation.  In particular, subok=True and where=True are
    # defaults, and the dtype is the correct one (the latter will
    # still have some effect on the search for the correct inner loop).
    x = np.array(1.)
    y = np.array(2.)
    out = np.array(3.)
    param_names = ['arg_kwarg']
    params = [[
        ArgPack(x, y),
        ArgPack(x, y, out),
        ArgPack(x, y, out=out),
        ArgPack(x, y, out=(out,)),
        ArgPack(x, y, out=out, subok=True, where=True),
        ArgPack(x, y, subok=True),
        ArgPack(x, y, subok=True, where=True),
        ArgPack(x, y, out, subok=True, where=True)
    ]]

    def time_add_arg_parsing(self, arg_pack):
Repository: numpy/numpy
File: numpy/_utils/_pep440.py (Python)

Code before:
        return self._compare(other, lambda s, o: s >= o)

    def __gt__(self, other):
        return self._compare(other, lambda s, o: s > o)

    def __ne__(self, other):
        return self._compare(other, lambda s, o: s != o)

    def _compare(self, other, method):
        if not isinstance(other, _BaseVersion):
            return NotImplemented

        return method(self._key, other._key)


class LegacyVersion(_BaseVersion):

    def __init__(self, version):
        self._version = str(version)
        self._key = _legacy_cmpkey(self._version)

    def __str__(self):
        return self._version

    def __repr__(self):
        return f"<LegacyVersion({repr(str(self))})>"

    @property
    def public(self):
        return self._version

    @property
    def base_version(self):
        return self._version

    @property
    def local(self):
        return None

    @property
    def is_prerelease(self):
        return False

    @property
    def is_postrelease(self):
        return False


_legacy_version_component_re = re.compile(
    r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
)

Review comment (suggestion):
```suggestion
        return f"<LegacyVersion({str(self)!r})>"
```

Code after:
        return self._compare(other, lambda s, o: s >= o)

    def __gt__(self, other):
        return self._compare(other, lambda s, o: s > o)

    def __ne__(self, other):
        return self._compare(other, lambda s, o: s != o)

    def _compare(self, other, method):
        if not isinstance(other, _BaseVersion):
            return NotImplemented

        return method(self._key, other._key)


class LegacyVersion(_BaseVersion):

    def __init__(self, version):
        self._version = str(version)
        self._key = _legacy_cmpkey(self._version)

    def __str__(self):
        return self._version

    def __repr__(self):
        return f"<LegacyVersion({str(self)!r})>"

    @property
    def public(self):
        return self._version

    @property
    def base_version(self):
        return self._version

    @property
    def local(self):
        return None

    @property
    def is_prerelease(self):
        return False

    @property
    def is_postrelease(self):
        return False


_legacy_version_component_re = re.compile(
    r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
)