Summary
The module uses global mutable state for tallying (_tally) and error handling (_fail), which prevents clean reuse as a library and makes the code non-reentrant/non-threadsafe.
Details
In repren.py:
-
_tally (line 579): A module-level _Tally() instance that gets mutated by multi_replace() and transform_file(). This means calling these functions from library code accumulates state across calls with no way to reset it.
-
_fail (line 527): A module-level function pointer that defaults to _fail_with_exception but gets reassigned to _fail_with_exit inside _run_cli(). This means library callers who import after main() has run get different behavior.
-
_tally mutation inside multi_replace() (lines 669-673): The core replacement function has a side effect of mutating global state, mixing pure computation with I/O bookkeeping.
Suggestion
Pass a Tally (or context) object through the call chain instead of relying on module-level globals. This would make the functions pure, reentrant, and safe for concurrent use. The Rust port (repren-rs) already uses this pattern — each function returns counts as part of its return value.
Context
Found during the repren-rs Rust port review, where this pattern was identified as a porting friction point.
Summary
The module uses global mutable state for tallying (
_tally) and error handling (_fail), which prevents clean reuse as a library and makes the code non-reentrant/non-threadsafe.Details
In
repren.py:_tally(line 579): A module-level_Tally()instance that gets mutated bymulti_replace()andtransform_file(). This means calling these functions from library code accumulates state across calls with no way to reset it._fail(line 527): A module-level function pointer that defaults to_fail_with_exceptionbut gets reassigned to_fail_with_exitinside_run_cli(). This means library callers who import aftermain()has run get different behavior._tallymutation insidemulti_replace()(lines 669-673): The core replacement function has a side effect of mutating global state, mixing pure computation with I/O bookkeeping.Suggestion
Pass a
Tally(or context) object through the call chain instead of relying on module-level globals. This would make the functions pure, reentrant, and safe for concurrent use. The Rust port (repren-rs) already uses this pattern — each function returns counts as part of its return value.Context
Found during the
repren-rsRust port review, where this pattern was identified as a porting friction point.