-
Notifications
You must be signed in to change notification settings - Fork 0
Idioms and Antipatterns
Here is a list of idioms and antipatterns in SymPy. You should use the former, and avoid the latter.
Please edit this page with any idioms or antipatterns that you know of. Don't worry about organization too much.
For most antipatterns, try to add an example of how it should be done instead.
Rebuild an expression using expr.func(*expr.args)
.
When creating custom subclasses of Basic
or Expr
, make sure that it
satisfies expr == expr.func(*expr.args)
. Many very basic functions in SymPy
rely on this idiom to modify an expression and rebuild it, such as subs
.
TODO: How to properly subclass Basic and Expr (and when to).
When using Wild
, be sure to use the exclude
keyword to make the pattern
deterministic.
Don't
x, y = Symbols("x y")
a, b = Wild('a'), Wild('b')
(2*x + 3*y).match(a*x + b*y)
Do
x, y = Symbols("x y")
a, b = Wild('a', exclude=[x, y]), Wild('b', exclude=[x, y])
(2*x + 3*y).match(a*x + b*y)
Reason
Without the exclude pattern, you may get matches that are technically correct,
but not what you wanted. This is especially likely if the expression you are
matching doesn't actually match your pattern. For example, using the above
without exclude
:
>>> (2 + 3*y).match(a*x + b*y)
{a: 2/x, b: 3}
This is technically correct, because (2/x)*x + 3*y == 2 + 3*y
, but you
probably wanted it to not match at all. The issue is that you really didn't
want a
and b
to include x
and y
, and the exclude
parameter lets you
specify exactly this. With the exclude
parameter, the above match gives
None
, meaning it did not match.
Don't use strings as input to functions. Rather, create the objects symbolically using Symbols and the appropriate SymPy functions, and manipulate them.
Don't
>>> simplify("(x**2 + x)/x")
x + 1
Do
>>> x = Symbol('x')
>>> simplify((x**2 + x)/x)
x + 1
Reason
Support for string input is in many ways accidental. It only happens because
functions call sympify()
on their input to ensure that it is a SymPy object,
and sympify()
translates strings. Support for this may go away in a future
version.
There are many disadvantages to using strings:
-
They are not explicit. They make code much harder to read.
-
sympify()
automatically turns all undefined names into Symbols of Functions, so if you have a typo, the string will still parse correctly, but the output will not be what you expect. For example
>>> expand_trig("sine(x + y)")
sine(x + y)
>>> expand_trig(sine(x + y))
Traceback (most recent call last):
File "<ipython-input-41-a8617eceeca5>", line 1, in <module>
expand_trig(sine(x + y))
NameError: name 'sine' is not defined
>>> expand_trig(sin(x + y))
sin(x)*cos(y) + sin(y)*cos(x)
In the first example, sine
, a typo for sin
is parsed into
Function("sine")
, and it appears that expand_trig
cannot handle it. In the
second case, we immediately get an error from the undefined name sine
, and
fixing our typo, we see that expand_trig
can indeed do what we want.
- See also the section on
S('x')
in the "Creating Symbols" section below. Symbol names can contain any character, including things that aren't valid Python. But using strings as input will pass the string tosympify
, even the argument to the function requires a Symbol. For example,
>>> x = Symbol('2x')
>>> solve(x - 1, x)
[1]
>>> solve(x - 1, '2x')
Traceback (most recent call last):
File "<ipython-input-12-71e92ee34319>", line 1, in <module>
solve(Symbol('2d') - 1, '2d ')
File "/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/solvers/solvers.py", line 672, in solve
f, symbols = (_sympified_list(w) for w in [f, symbols])
File "/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/solvers/solvers.py", line 672, in <genexpr>
f, symbols = (_sympified_list(w) for w in [f, symbols])
File "/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/solvers/solvers.py", line 663, in _sympified_list
return map(sympify, w if iterable(w) else [w])
File "/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/core/sympify.py", line 297, in sympify
raise SympifyError('could not parse %r' % a, exc)
SympifyError: Sympify of expression 'could not parse u'2d'' failed, because of exception being raised:
SyntaxError: invalid syntax (<string>, line 1)
-
If you use strings, syntax errors won't be caught until the line is run. If you build up the expressions, syntax errors will be caught when Python compiles the script before any of it runs.
-
In code editors that do syntax highlighting, strings will be highlighted all one color, whereas Python expressions will be highlighted according to their actual content.
-
As mentioned, they are not officially supported or tested, and so may go away at any time.
There are several ways to create Symbols:
-
x = Symbol('x')
. x, y, z = map(Symbol, 'xyz')
var('x y z')
from sympy.abc import x, y, z
x = S('x') # or sympify('x')
- Using
isympy -a
x, y, z = symbols('x y z')
There are also some variations on the above. For example, 7 can also be
spelled symbols('x,y,z')
or symbols('x:z')
.
The recommended way to create Symbols is to use symbols
, i.e.,
number 7. Some of the other methods are useful when working interactively, but
should be avoided when writing more permanent code. Here is a discussion on
each of the above:
-
x = Symbol('x')
This way is actually the next best way, and if you are only creating a single Symbol, it is fine.
Symbol
is the class of Symbol, so creating a Symbol this way calls the constructor directly. The primary disadvantage of this method is that it does not generalize easily to creating multiple Symbols. The one situation where this method is convenient is if you wish to create a Symbol with a character in the name that will be parsed bysymbols
, such as a space,,
, or:
. (To do this withsymbols
requires escaping the special characters.) -
x, y, z = map(Symbol, 'xyz')
There is no reason to do this. Just use
symbols
instead. It is easier to read, and has nice syntax for things like numbered symbols (see below). Also,map
works differently thansymbols
(if you put in space, you will get the wrong thing), and it is harder to create multicharacter Symbols with this method. -
var('x y z')
var
uses the exact same syntax assymbols
. The difference is thatvar
automatically injects Symbols into the namespace. Thus, it is sufficient to type justvar('x y z')
instead ofx, y, z = var('x y z')
. There are a few reasons this should be avoided in permanent places like scripts, libraries, and notebooks. First, it is harder to read, both by humans and by libraries that parse source code. Second, it usesinspect.currentframe
to inject the symbols, which is not guaranteed to always work. For example, it may not behave as expected in certain nested scopes, or in alternate Python implementations from CPython. -
from sympy.abc import x, y, z
This method is convenient, and should work fine. The biggest disadvantage to using
sympy.abc
is that it does not generalize to Symbol names that are not single letters. On the other hand,symbols
, which is about as much typing, does. Also, it is easy to dofrom sympy.abc import *
, which will clobber several builtin uppercase names, likeI
orQ
. -
x = S('x')
sympify
will create Symbols, but this is just a side effect of its string parsing abilities (see also the previous section). The biggest issue here is that if you mistype something (or heaven forbid you use pass user-input through this), it will create that whole expression instead of the Symbol. -
isympy -a
This is the easiest way to work interactively and not have to worry about defining Symbol names. With the
-a
option,isympy
will automatically parse input for undefined names and convert them to Symbols. This requires IPython to work. The disadvantage here is that if you later wish to move something you wrote to a script, you will need to define all the Symbols. Another disadvantage is that if you mistype a function name, it will create a Symbol for it, instead of failing with NameError as usual. There are some other subtleties with this method. See the section on-a
inisympy --help
for more info. -
x, y, z = symbols('x y z')
This is the best way to create Symbols. It works for single symbols (
x = symbols('x')
), but generalizes easily to multiple symbols. It has convenient syntax for creating multiple Symbols at once. For example,symbols('x0:5')
, or justsymbols('x:5')
will createx0, x1, x2, x3, x4
.symbols('a:z')
will create a Symbol for every lowercase letter in the English alphabet.symbols
also generalizes to otherSymbol
-like classes, likeWild
orDummy
(or actually, any class that takes a string argument). Just usex, y, z = symbols('x y z', cls=Dummy)
. See the docstring ofsymbols
for more information on what this function can do.
In summary
Don't
Don't use map(Symbols, 'xyz')
or sympify
(2 or 5) to create Symbols.
Do
Do use symbols
(number 7). If you are working strictly interactively, use var
or isympy -a
to avoid the hassle of creating symbols. Symbol('x')
and
from sympy.abc import x
are OK, but are not as general as symbols
.