From fda8fa6b03311c063820fcacc0e87ca7de21acfd Mon Sep 17 00:00:00 2001 From: 40% Date: Thu, 15 Oct 2026 21:58:50 +0800 Subject: [PATCH 1/2] Make expression fields read-only Declare Expr.terms, GenExpr.children, SumExpr/ProdExpr.constant, PowExpr.expo and Constant.number as \cdef readonly\, so assigning to them from Python raises AttributeError. The fields remain writable from Cython, which is all the internal code needs. No type annotations are added on purpose: typing the fields (e.g. \cdef readonly double number\) would change runtime behaviour, as values would be converted on assignment (a Decimal would become a float at construction) and repr() would change accordingly. Only the Cython declaration changes; runtime behaviour, the type stubs and the mypy baseline are unaffected. --- CHANGELOG.md | 1 + src/pyscipopt/expr.pxi | 10 +++++----- src/pyscipopt/scip.pxd | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbec1d025..a243f784b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Fixed incorrect `getObjVal()` result when called before `getBestSol()` (e.g., in event handlers). ### Changed - Replaced `format()` with f-strings in examples +- Made expression fields read-only to prevent accidental runtime modification ### Removed ## 6.3.0 - 2026.10.11 diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index 73743a969..3886df3d4 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -573,7 +573,7 @@ Operator = Op() cdef class GenExpr(ExprLike): cdef public _op - cdef public children + cdef readonly children def __init__(self): # do we need it ''' ''' @@ -749,7 +749,7 @@ cdef class GenExpr(ExprLike): # Sum Expressions cdef class SumExpr(GenExpr): - cdef public constant + cdef readonly constant def __init__(self): self.constant = 0.0 @@ -778,7 +778,7 @@ cdef class SumExpr(GenExpr): # Prod Expressions cdef class ProdExpr(GenExpr): - cdef public constant + cdef readonly constant def __init__(self): self.constant = 1.0 @@ -830,7 +830,7 @@ cdef class VarExpr(GenExpr): # Pow Expressions cdef class PowExpr(GenExpr): - cdef public expo + cdef readonly expo def __init__(self): self.expo = 1.0 @@ -929,7 +929,7 @@ cdef class CosExpr(UnaryExpr): cdef class Constant(GenExpr): - cdef public number + cdef readonly number def __init__(self, number: Union[int, float]): self.number = number diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index fe5f3ac6b..6fbeaf08a 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -2192,7 +2192,7 @@ cdef class ExprLike: cdef ExprLike copy(self, bint copy=*) cdef class Expr(ExprLike): - cdef public terms + cdef readonly terms cdef class Event: cdef SCIP_EVENT* event From eb9b94793b9421601b923dc436adc1d57fc20579 Mon Sep 17 00:00:00 2001 From: 40% Date: Sun, 18 Oct 2026 20:09:27 +0800 Subject: [PATCH 2/2] Correct expression property stubs Declare expression attributes as read-only properties with their concrete return types instead of incomplete writable fields. --- src/pyscipopt/scip.pyi | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index a29999dc1..dbc20b1e1 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -251,7 +251,8 @@ class Conshdlr: @disjoint_base class Constant(GenExpr): - number: SupportsFloat + @property + def number(self) -> SupportsFloat: ... def __init__(self, number: SupportsFloat) -> None: ... def __neg__(self, /) -> Constant: ... def __pow__( # type: ignore[override] @@ -379,7 +380,8 @@ class ExprLike: @disjoint_base class Expr(ExprLike): - terms: Incomplete + @property + def terms(self) -> dict[Term, float]: ... def __init__(self, terms: Incomplete = None) -> None: ... def degree(self) -> int: ... def normalize(self) -> None: ... @@ -443,7 +445,8 @@ class ExprCons: @disjoint_base class GenExpr(ExprLike): _op: str | None - children: Incomplete + @property + def children(self) -> list[GenExpr]: ... def __init__(self) -> None: ... def degree(self) -> float: ... @deprecated("Deprecated since 6.3.0; use type(expr) instead.") @@ -2144,7 +2147,8 @@ class PY_SCIP_STATUS: @disjoint_base class PowExpr(GenExpr): - expo: SupportsFloat + @property + def expo(self) -> SupportsFloat: ... def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ... @@ -2175,7 +2179,8 @@ class Pricer: @disjoint_base class ProdExpr(GenExpr): - constant: SupportsFloat + @property + def constant(self) -> SupportsFloat: ... def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ... @@ -2374,7 +2379,8 @@ class Statistics: @disjoint_base class SumExpr(GenExpr): - constant: SupportsFloat + @property + def constant(self) -> SupportsFloat: ... def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ...