Hi Oscar, Thank you for the thorough reply. I will digest it and try to put a beginner touch on it. Would you be willing to review it and, if it is acceptable, I would be happy for it to be used in the documentation. I don't know the development hierarchy so feel free to point me to other people, in other directions, or suggest other things (including to stand down on behalf of SymPy 🙂).
Conrad ________________________________ From: [email protected] <[email protected]> on behalf of Oscar Benjamin <[email protected]> Sent: Saturday, December 27, 2025 8:03 AM To: [email protected] <[email protected]> Subject: Re: [sympy] subs v. replace v. xreplace - help needed on a minimal viable example of their difference Hi Conrad, Maybe the docs for this can be improved. In fact there should probably be a separate docs page for exactly this that perhaps also covers some other common methods such as rewrite. The xreplace function is the simplest. The signature is expr2 = expr1.xreplace({old1: new1, old2: new2, ...}) This will recursively walk down through the expression tree replacing exact subexpressions so that if old1 is somewhere in expr1 then expr2 will have new1 in that place instead: In [9]: expr1 = x**2 + cos(x) In [10]: print(expr1) x**2 + cos(x) In [11]: print(expr1.xreplace({cos(x): sin(x)})) x**2 + sin(x) For all of xreplace, replace and subs the expression tree is evaluated as it is rebuilt so often inserting new1 causes some part of the expression to transform e.g. here cos(pi/4) evaluates to sqrt(2)/2 and (pi/4)**2 evaluates to pi**2/16: In [12]: print(expr1.xreplace({x: pi/4})) pi**2/16 + sqrt(2)/2 The replace function is like xreplace but gives more flexibility for selecting which expressions to replace and what to replace them by. There are several different options for what the arguments to replace can be but they all allow for more complex matching than xreplace which only matches exact subexpressions. This version uses Wild symbols for pattern matching: In [19]: expr1 = sin(x) + sin(y) In [20]: print(expr1) sin(x) + sin(y) In [21]: a = Wild('a') In [22]: print(expr1.replace(sin(a), cos(a))) cos(x) + cos(y) It is also possible to pass functions as argument to replace so that the full Turing complete Python language can be used to select subexpressions and compute their replacements: In [24]: print(expr1.replace(lambda e: e.func == cos, lambda e: sin(e.args[0]))) sin(x) + sin(y) Both xreplace and replace are purely structural operations on expression trees. They do not attempt to interpret anything in any mathematical way and will just replace the exact subexpressions as instructed. The subs method is supposed to correspond to performing a mathematically correct substitution meaning that it will interpret a match differently: In [31]: print(expr1) x**4 + x**3 + x**2 + x In [32]: print(expr1.subs(x**2, y)) x**3 + x + y**2 + y Here subs decided that even powers of x could be replaced even if e.g. x**4 was not the exact replacement pattern but it decided against replacing odd powers since it does not know whether x is sqrt(y) or -sqrt(y). Usually when subs is used you do not have this and the replacement patterns are all just symbol to expression like expr.subs(x, some_expr). In that case the primary distinction between xreplace and subs is really that subs distinguishes between free and bound symbols whereas xreplace does not: In [33]: expr = Integral(x*y, (x, 0, 1)) In [34]: print(expr) Integral(x*y, (x, 0, 1)) In [35]: print(expr.xreplace({x:z, y:t})) Integral(t*z, (z, 0, 1)) In [36]: print(expr.subs({x:z, y:t})) Integral(t*x, (x, 0, 1)) In [37]: expr.free_symbols Out[37]: {y} These are part of a more general feature that different types of expression can override the behaviour of subs in a way that does not happen for xreplace and replace. The _eval_subs method is used for this and you can see that there are many of these methods defined in the codebase for different kinds of expression (the ones above were for Pow in power.py and for Integral in expr_with_limits.py): $ git grep 'def _eval_subs' sympy/algebras/quaternion.py: def _eval_subs(self, old: Expr, new: Expr) -> Quaternion: # type: ignore sympy/concrete/expr_with_limits.py: def _eval_subs(self, old, new): sympy/core/add.py: def _eval_subs(self, old, new): sympy/core/basic.py: def _eval_subs(self, old: Basic, new: Basic) -> Basic | None: sympy/core/function.py: def _eval_subs(self, old, new): sympy/core/function.py: def _eval_subs(self, old, new): sympy/core/function.py: def _eval_subs(self, old, new): sympy/core/mul.py: def _eval_subs(self, old, new): sympy/core/numbers.py: def _eval_subs(self, old, new): sympy/core/numbers.py: def _eval_subs(self, old, new): sympy/core/numbers.py: def _eval_subs(self, old, new): sympy/core/power.py: def _eval_subs(self, old, new): sympy/core/symbol.py: def _eval_subs(self, old, new): sympy/functions/elementary/exponential.py: def _eval_subs(self, old, new): sympy/functions/elementary/piecewise.py: def _eval_subs(self, old, new): sympy/functions/elementary/trigonometric.py: def _eval_subs(self, old, new): sympy/geometry/curve.py: def _eval_subs(self, old, new): sympy/geometry/entity.py: def _eval_subs(self, old, new): sympy/logic/boolalg.py: def _eval_subs(self, old, new): sympy/logic/boolalg.py: def _eval_subs(self, old, new): sympy/logic/boolalg.py: def _eval_subs(self, old, new): sympy/physics/control/lti.py: def _eval_subs(self, old, new): sympy/physics/units/quantities.py: def _eval_subs(self, old, new): sympy/polys/polytools.py: def _eval_subs(f, old, new): sympy/polys/rootoftools.py: def _eval_subs(self, old, new): sympy/series/formal.py: def _eval_subs(self, old, new): sympy/series/fourier.py: def _eval_subs(self, old, new): sympy/series/order.py: def _eval_subs(self, old, new): sympy/sets/conditionset.py: def _eval_subs(self, old, new): sympy/tensor/tensor.py: def _eval_subs(self, old, new): The xreplace function is the fastest and simplest. The replace function is the most flexible. The subs function is the slowest and is the only one that applies any semantic meaning to the substitution. -- Oscar On Thu, 25 Dec 2025 at 15:57, 'Conrad Schiff' via sympy <[email protected]> wrote: > > All, > > I am trying to understand the actual differences between the 'subs', > 'replace', and 'xreplace'. I've reviewed the documentation (some of it seems > stale) and various discussions in this group. Perhaps I've missed something > but I've not found a side-by-side minimal viable example of that shows when > to pick amongst these. > > Does such a thing exist? If it doesn't is there any interest in making > such a thing. I would be happy to take the first draft of documenting this > if (and only if) I can get help from the rest of the group. > > Thank you, > Conrad Schiff, PhD > Professor of Physics > Capitol Technology University > > -- > You received this message because you are subscribed to the Google Groups > "sympy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To view this discussion visit > https://groups.google.com/d/msgid/sympy/CH3PR12MB94327C143742026A16782408CFB2A%40CH3PR12MB9432.namprd12.prod.outlook.com. -- You received this message because you are subscribed to the Google Groups "sympy" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/sympy/CAHVvXxS8qOU4Hc-nAX4niFhTaGJaQ%2Bt7MQwMGfG0oPn38LSUbw%40mail.gmail.com. -- You received this message because you are subscribed to the Google Groups "sympy" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/sympy/CH3PR12MB9432C4D8378063949646B961CFB1A%40CH3PR12MB9432.namprd12.prod.outlook.com.
