sympy
SymPy is a computer algebra system written in pure Python. Instead of computing with numbers, it computes with expressions: you declare symbols, build expressions out of them, and ask for the derivative, the integral, the limit, the factored form, or the exact solution. Answers come back as expressions, not floats, so 1/3 stays one third and sqrt(2) stays sqrt(2) until you ask for digits. It covers calculus, equation solving, linear algebra with exact entries, differential equations, series expansion, and number theory, and it can print any expression as LaTeX or turn it into a fast numeric function for NumPy with lambdify. The only dependency is mpmath, which handles arbitrary-precision floats.
The default computer algebra system for Python, and the only one that installs with a single pip command and no compiler. Use it to derive and verify expressions, then lambdify them into NumPy for anything that runs in a loop, and keep a timeout around calls to solve and integrate on inputs you do not control.
Use it if
- You need a derivative, integral, limit, or series expansion done symbolically and correctly rather than approximated numerically
- You want to derive a formula once and then evaluate it fast on arrays: lambdify compiles a SymPy expression into a plain Python function backed by NumPy
- You need exact arithmetic, where Rational(1, 3) times 3 is exactly 1 and a matrix inverse comes back with fractions instead of floating point noise
- You are generating LaTeX for a paper, or C, Fortran, or Python source, from expressions you built or solved programmatically
- It is pure Python with one dependency, so it installs anywhere, including places where compiling a scientific stack is painful
- You are doing numerics. Every SymPy object is a Python object graph, so element-wise math on symbolic expressions is orders of magnitude slower than NumPy; build the expression symbolically and lambdify it before any loop
- solve() is convenient and inconsistent: it returns a list, a list of tuples, a dict, or an empty list depending on the input, so downstream code has to handle all of them. solveset() is the cleaner replacement but does not cover every case solve does
- Hard integrals, nonlinear systems, and some limits either return unevaluated, take an unpredictable amount of time, or return something correct but unusable. There is no timeout, so a request that does not terminate hangs your process
- The tracker holds 4724 open issues and 1186 open PRs, and 1.14.0 was released in April 2025, so a bug you hit may be known, fixed on master, and still not in a release
- sympify() and parse_expr() evaluate strings, so building expressions from user input is code execution unless you lock down the parser yourself
- For serious computer algebra at scale, a specialized system is much faster: python-flint wraps Flint and Arb, and Sage or a commercial CAS handle problems SymPy will not finish
- If all you actually want is arbitrary-precision arithmetic, use mpmath or the standard library decimal module directly and skip the algebra layer
Setup reality
pip install sympy pulls in mpmath and nothing else, and there is nothing to configure. The learning curve is entirely conceptual and it bites early. Python evaluates before SymPy sees anything, so 1/3 inside an expression is already 0.333 and x + 1/3 needs Rational(1, 3) or S(1)/3 to stay exact. Equality is structural, not mathematical: expr1 == expr2 compares trees, and the way to ask whether two expressions are equal is simplify(expr1 - expr2) == 0. Equations are built with Eq(lhs, rhs) because x == y is a Python bool. Assumptions change results, so sqrt(x**2) stays as written until you declare x positive, at which point it becomes x, and forgetting this makes simplify look broken. Finally, symbols are matched by name, so two separately created Symbol('x') objects are the same symbol, which is convenient until you rely on it accidentally.
Patterns
Create symbols and give them assumptionsdeclare-symbols
from sympy import symbols, sqrt
x, y = symbols('x y')
a = symbols('a', positive=True)
n = symbols('n', integer=True)
print(sqrt(x**2)) # sqrt(x**2), unchanged
print(sqrt(a**2)) # aAssumptions are not decoration; they decide which simplifications are valid. Without positive=True, SymPy cannot know sqrt(x**2) is not abs(x), so it leaves the expression alone and simplify looks like it failed.
Rearrange an expressionsimplify-and-factor
from sympy import symbols, simplify, expand, factor, sin, cos
x = symbols('x')
print(simplify(sin(x)**2 + cos(x)**2)) # 1
print(expand((x + 1)**3)) # x**3 + 3*x**2 + 3*x + 1
print(factor(x**3 - 1)) # (x - 1)*(x**2 + x + 1)simplify is a heuristic search over many transformations and can be slow with no guarantee of the form you wanted. When you know the shape you need, call the specific function (expand, factor, cancel, trigsimp, radsimp) instead.
Take derivativesdifferentiate
from sympy import symbols, diff, sin, exp, Function
x, y = symbols('x y')
print(diff(sin(x) * exp(x), x)) # exp(x)*sin(x) + exp(x)*cos(x)
print(diff(x**4, x, 2)) # 12*x**2, second derivative
print(diff(x**2 * y**3, x, y)) # 6*x*y**2, mixed partial
f = Function('f')
print(diff(f(x)**2, x)) # 2*f(x)*Derivative(f(x), x)An undefined Function stays symbolic and its derivative comes back as Derivative(...), which is exactly what you want when setting up a differential equation. expr.diff(x) is the same call as a method.
Compute indefinite and definite integralsintegrate
from sympy import symbols, integrate, exp, sin, oo, pi
x = symbols('x')
print(integrate(exp(x) * sin(x), x))
# exp(x)*sin(x)/2 - exp(x)*cos(x)/2
print(integrate(exp(-x**2), (x, -oo, oo))) # sqrt(pi)No constant of integration is added to indefinite results. If SymPy cannot find a closed form it returns an unevaluated Integral rather than raising, and hard integrands can run for a long time with no way to interrupt short of a signal.
Solve an equation for a variablesolve-equations
from sympy import symbols, Eq, solve, solveset, nsolve, S, cos
x = symbols('x')
print(solve(x**2 - 4, x)) # [-2, 2]
print(solveset(Eq(x**2, 4), x, domain=S.Reals)) # {-2, 2}
print(solve([Eq(x + symbols('y'), 5),
Eq(x - symbols('y'), 1)], [x, symbols('y')])) # {x: 3, y: 2}
print(nsolve(cos(x) - x, x, 1)) # 0.739085133215161An expression with no Eq is assumed equal to zero. solve returns a list for one unknown and a dict for a system, so branch on the type; solveset always returns a set object and needs a domain to stay real. nsolve is the numeric fallback and needs a starting guess.
Work with exact matriceslinear-algebra
from sympy import Matrix, symbols, linsolve
M = Matrix([[1, 2], [3, 4]])
print(M.det()) # -2
print(M.inv()) # Matrix([[-2, 1], [3/2, -1/2]])
print(M.eigenvals()) # {5/2 - sqrt(33)/2: 1, 5/2 + sqrt(33)/2: 1}
x, y, z = symbols('x y z')
print(linsolve([x + y + z - 6, 2*x + y - 3, x - y + z - 2], (x, y, z)))
# {(1/2, 2, 7/2)}Entries stay exact, so the inverse has fractions and eigenvalues have radicals instead of floats. This is also why a symbolic matrix inverse blows up quickly with size; past roughly 10x10 with symbols, use numpy or scipy.
Take limits and expand as a serieslimits-and-series
from sympy import symbols, limit, sin, cos, oo
x = symbols('x')
print(limit(sin(x) / x, x, 0)) # 1
print(limit(1 / x, x, 0, '+')) # oo
print(limit(1 / x, x, 0, '-')) # -oo
print(cos(x).series(x, 0, 6))
# 1 - x**2/2 + x**4/24 + O(x**6)The default limit is from the right, so a two-sided limit that does not exist can still return a value; pass '+' or '-' explicitly when the sides differ. Call removeO() on a series to get a plain polynomial you can evaluate.
Keep numbers exact, then ask for digitsexact-numbers
from sympy import Rational, S, pi, N, sqrt
print(Rational(1, 3) + Rational(1, 6)) # 1/2
print(S(1) / 3) # 1/3
print(1 / 3) # 0.3333333333333333, plain Python
print(N(pi, 30)) # 3.14159265358979323846264338328
print(sqrt(2).evalf(50))Python computes 1/3 before SymPy ever sees it, which is the single most common source of unexpected floats. Rational(1, 3), S(1)/3, or sympify('1/3') all keep it exact.
Substitute values into an expressionsubstitute-values
from sympy import symbols, sin, pi
x, y = symbols('x y')
expr = x**2 + sin(y)
print(expr.subs(x, 3)) # sin(y) + 9
print(expr.subs({x: 3, y: pi / 2})) # 10
print(expr.subs([(x, y), (y, 2)])) # careful: sequentialsubs with a list applies substitutions one after another, so the result of the first can be rewritten by the second. Pass a dict for simultaneous substitution, or subs(..., simultaneous=True).
Turn an expression into a fast numeric functionlambdify-for-numpy
import numpy as np
from sympy import symbols, sin, lambdify
x, y = symbols('x y')
expr = x**2 + sin(y)
f = lambdify((x, y), expr, 'numpy')
print(f(np.array([1.0, 2.0]), np.array([0.0, 0.0]))) # [1. 4.]This is the bridge out of symbolic land and the fix for slow SymPy code: derive once, lambdify, then loop. lambdify builds the function with exec, so never pass it an expression parsed from untrusted input.
Solve an ODEsolve-differential-equation
from sympy import symbols, Function, Eq, dsolve, sin
x = symbols('x')
f = Function('f')
print(dsolve(Eq(f(x).diff(x, 2) + f(x), 0), f(x)))
# Eq(f(x), C1*sin(x) + C2*cos(x))
print(dsolve(Eq(f(x).diff(x) + f(x), sin(x)), f(x),
ics={f(0): 1}))Constants come back as C1, C2 and so on unless you pass ics with initial conditions. dsolve picks a solving method by classifying the equation; classify_ode(eq, f(x)) shows you which ones it considered when the answer looks odd.
Print an expression as LaTeXexport-latex
from sympy import symbols, Integral, cos, pi, latex, init_printing
x = symbols('x')
print(latex(Integral(cos(x)**2, (x, 0, pi))))
# \int\limits_{0}^{\pi} \cos^{2}{\left(x \right)}\, dx
init_printing() # pretty output in a Jupyter notebook or terminalCapital-letter constructors such as Integral, Derivative, and Sum build the unevaluated object, which is what you want for display; call .doit() to actually compute it. init_printing() only affects display, never the values.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| python-flint | PyPI | You need exact polynomial, matrix, or number-theory work that is fast, and can accept a compiled dependency and a narrower feature set. |
| mpmath | PyPI | You only need arbitrary-precision floating point, special functions, and numeric root finding, without symbolic manipulation. |
| numpy | PyPI | The problem is numeric all the way down and you never needed a closed-form answer. |
| sagemath-standard | PyPI | You want a full mathematics system that wraps many specialized libraries and outperforms SymPy on serious algebra. |