@@ -372,6 +372,126 @@ Well, nothing is **really** immutable in python, but you were warned.
372
372
We also provide :class: `returns.primitives.types.Immutable ` mixin
373
373
that users can use to quickly make their classes immutable.
374
374
375
+ Creating Modified Copies of Containers
376
+ --------------------------------------
377
+
378
+ While containers are immutable, sometimes you need to create a modified copy
379
+ of a container with different inner values. Since Python 3.13, ``returns ``
380
+ containers support the ``copy.replace() `` function via the ``__replace__ ``
381
+ magic method.
382
+
383
+ .. code :: python
384
+
385
+ >> > from returns.result import Success, Failure
386
+ >> > import copy, sys
387
+ >> >
388
+ >> > # Only run this example on Python 3.13+
389
+ >> > if sys.version_info >= (3 , 13 ):
390
+ ... # Replace the inner value of a Success container
391
+ ... original = Success(1 )
392
+ ... modified = copy.replace(original, _inner_value = 2 )
393
+ ... assert modified == Success(2 )
394
+ ... assert original is not modified # Creates a new instance
395
+ ...
396
+ ... # Works with Failure too
397
+ ... error = Failure(" original error" )
398
+ ... new_error = copy.replace(error, _inner_value = " new error message" )
399
+ ... assert new_error == Failure(" new error message" )
400
+ ...
401
+ ... # No changes returns the original object (due to immutability)
402
+ ... assert copy.replace(original) is original
403
+ ... else :
404
+ ... # For Python versions before 3.13, the tests would be skipped
405
+ ... pass
406
+
407
+ .. note ::
408
+ The parameter name ``_inner_value `` is used because it directly maps to the
409
+ internal attribute of the same name in ``BaseContainer ``. In the ``__replace__ ``
410
+ implementation, this parameter name is specifically recognized to create a new
411
+ container instance with a modified inner value.
412
+
413
+ .. warning ::
414
+ While ``copy.replace() `` works at runtime, it has limitations with static
415
+ type checking. If you replace an inner value with a value of a different
416
+ type, type checkers won't automatically infer the new type:
417
+
418
+ .. code :: python
419
+
420
+ # Example that would work in Python 3.13+:
421
+ # >>> num_container = Success(123)
422
+ # >>> str_container = copy.replace(num_container, _inner_value="string")
423
+ # >>> # Type checkers may still think this is Success[int] not Success[str]
424
+ >> > # The above is skipped in doctest as copy.replace requires Python 3.13+
425
+
426
+ Using ``copy.replace() `` with Custom Containers
427
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
428
+
429
+ If you create your own container by extending ``BaseContainer ``, it will automatically
430
+ inherit the ``__replace__ `` implementation for free. This means your custom containers
431
+ will work with ``copy.replace() `` just like the built-in ones.
432
+
433
+ .. code :: python
434
+
435
+ >> > from returns.primitives.container import BaseContainer
436
+ >> > from typing import TypeVar, Generic
437
+ >> > import copy, sys # Requires Python 3.13+ for copy.replace
438
+
439
+ >> > T = TypeVar(' T' )
440
+ >> > class MyBox (BaseContainer , Generic[T]):
441
+ ... """ A custom container that wraps a value."""
442
+ ... def __init__ (self , inner_value : T) -> None :
443
+ ... super ().__init__ (inner_value)
444
+ ...
445
+ ... def __eq__ (self , other : object ) -> bool :
446
+ ... if not isinstance (other, MyBox):
447
+ ... return False
448
+ ... return self ._inner_value == other._inner_value
449
+ ...
450
+ ... def __repr__ (self ) -> str :
451
+ ... return f " MyBox( { self ._inner_value!r } ) "
452
+
453
+ >> > # Create a basic container
454
+ >> > box = MyBox(" hello" )
455
+ >> >
456
+ >> > # Test works with copy.replace only on Python 3.13+
457
+ >> > if sys.version_info >= (3 , 13 ):
458
+ ... new_box = copy.replace(box, _inner_value = " world" )
459
+ ... assert new_box == MyBox(" world" )
460
+ ... assert box is not new_box
461
+ ... else :
462
+ ... # For Python versions before 3.13
463
+ ... pass
464
+
465
+ By inheriting from ``BaseContainer ``, your custom container will automatically support:
466
+
467
+ 1. The basic container operations like ``__eq__ ``, ``__hash__ ``, ``__repr__ ``
468
+ 2. Pickling via ``__getstate__ `` and ``__setstate__ ``
469
+ 3. The ``copy.replace() `` functionality via ``__replace__ ``
470
+ 4. Immutability via the ``Immutable `` mixin
471
+
472
+ Before Python 3.13, you can use container-specific methods to create modified copies:
473
+
474
+ .. code :: python
475
+
476
+ >> > from returns.result import Success, Failure, Result
477
+ >> > from typing import Any
478
+
479
+ >> > # For Success containers, we can use .map to transform the inner value
480
+ >> > original = Success(1 )
481
+ >> > modified = original.map(lambda _ : 2 )
482
+ >> > assert modified == Success(2 )
483
+
484
+ >> > # For Failure containers, we can use .alt to transform the inner value
485
+ >> > error = Failure(" error" )
486
+ >> > new_error = error.alt(lambda _ : " new error" )
487
+ >> > assert new_error == Failure(" new error" )
488
+
489
+ >> > # For general containers without knowing success/failure state:
490
+ >> > def replace_inner_value (container : Result[Any, Any], new_value : Any) -> Result[Any, Any]:
491
+ ... """ Create a new container with the same state but different inner value."""
492
+ ... if container.is_success():
493
+ ... return Success(new_value)
494
+ ... return Failure(new_value)
375
495
376
496
.. _type-safety :
377
497
0 commit comments