Coverage for /usr/lib/python3/dist-packages/sympy/core/sympify.py: 42%
178 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""sympify -- convert objects SymPy internal format"""
3from __future__ import annotations
4from typing import Any, Callable
6from inspect import getmro
7import string
8from sympy.core.random import choice
10from .parameters import global_parameters
12from sympy.utilities.exceptions import sympy_deprecation_warning
13from sympy.utilities.iterables import iterable
16class SympifyError(ValueError):
17 def __init__(self, expr, base_exc=None):
18 self.expr = expr
19 self.base_exc = base_exc
21 def __str__(self):
22 if self.base_exc is None:
23 return "SympifyError: %r" % (self.expr,)
25 return ("Sympify of expression '%s' failed, because of exception being "
26 "raised:\n%s: %s" % (self.expr, self.base_exc.__class__.__name__,
27 str(self.base_exc)))
30converter: dict[type[Any], Callable[[Any], Basic]] = {}
32#holds the conversions defined in SymPy itself, i.e. non-user defined conversions
33_sympy_converter: dict[type[Any], Callable[[Any], Basic]] = {}
35#alias for clearer use in the library
36_external_converter = converter
38class CantSympify:
39 """
40 Mix in this trait to a class to disallow sympification of its instances.
42 Examples
43 ========
45 >>> from sympy import sympify
46 >>> from sympy.core.sympify import CantSympify
48 >>> class Something(dict):
49 ... pass
50 ...
51 >>> sympify(Something())
52 {}
54 >>> class Something(dict, CantSympify):
55 ... pass
56 ...
57 >>> sympify(Something())
58 Traceback (most recent call last):
59 ...
60 SympifyError: SympifyError: {}
62 """
64 __slots__ = ()
67def _is_numpy_instance(a):
68 """
69 Checks if an object is an instance of a type from the numpy module.
70 """
71 # This check avoids unnecessarily importing NumPy. We check the whole
72 # __mro__ in case any base type is a numpy type.
73 return any(type_.__module__ == 'numpy'
74 for type_ in type(a).__mro__)
77def _convert_numpy_types(a, **sympify_args):
78 """
79 Converts a numpy datatype input to an appropriate SymPy type.
80 """
81 import numpy as np
82 if not isinstance(a, np.floating):
83 if np.iscomplex(a):
84 return _sympy_converter[complex](a.item())
85 else:
86 return sympify(a.item(), **sympify_args)
87 else:
88 try:
89 from .numbers import Float
90 prec = np.finfo(a).nmant + 1
91 # E.g. double precision means prec=53 but nmant=52
92 # Leading bit of mantissa is always 1, so is not stored
93 a = str(list(np.reshape(np.asarray(a),
94 (1, np.size(a)))[0]))[1:-1]
95 return Float(a, precision=prec)
96 except NotImplementedError:
97 raise SympifyError('Translation for numpy float : %s '
98 'is not implemented' % a)
101def sympify(a, locals=None, convert_xor=True, strict=False, rational=False,
102 evaluate=None):
103 """
104 Converts an arbitrary expression to a type that can be used inside SymPy.
106 Explanation
107 ===========
109 It will convert Python ints into instances of :class:`~.Integer`, floats
110 into instances of :class:`~.Float`, etc. It is also able to coerce
111 symbolic expressions which inherit from :class:`~.Basic`. This can be
112 useful in cooperation with SAGE.
114 .. warning::
115 Note that this function uses ``eval``, and thus shouldn't be used on
116 unsanitized input.
118 If the argument is already a type that SymPy understands, it will do
119 nothing but return that value. This can be used at the beginning of a
120 function to ensure you are working with the correct type.
122 Examples
123 ========
125 >>> from sympy import sympify
127 >>> sympify(2).is_integer
128 True
129 >>> sympify(2).is_real
130 True
132 >>> sympify(2.0).is_real
133 True
134 >>> sympify("2.0").is_real
135 True
136 >>> sympify("2e-45").is_real
137 True
139 If the expression could not be converted, a SympifyError is raised.
141 >>> sympify("x***2")
142 Traceback (most recent call last):
143 ...
144 SympifyError: SympifyError: "could not parse 'x***2'"
146 Locals
147 ------
149 The sympification happens with access to everything that is loaded
150 by ``from sympy import *``; anything used in a string that is not
151 defined by that import will be converted to a symbol. In the following,
152 the ``bitcount`` function is treated as a symbol and the ``O`` is
153 interpreted as the :class:`~.Order` object (used with series) and it raises
154 an error when used improperly:
156 >>> s = 'bitcount(42)'
157 >>> sympify(s)
158 bitcount(42)
159 >>> sympify("O(x)")
160 O(x)
161 >>> sympify("O + 1")
162 Traceback (most recent call last):
163 ...
164 TypeError: unbound method...
166 In order to have ``bitcount`` be recognized it can be imported into a
167 namespace dictionary and passed as locals:
169 >>> ns = {}
170 >>> exec('from sympy.core.evalf import bitcount', ns)
171 >>> sympify(s, locals=ns)
172 6
174 In order to have the ``O`` interpreted as a Symbol, identify it as such
175 in the namespace dictionary. This can be done in a variety of ways; all
176 three of the following are possibilities:
178 >>> from sympy import Symbol
179 >>> ns["O"] = Symbol("O") # method 1
180 >>> exec('from sympy.abc import O', ns) # method 2
181 >>> ns.update(dict(O=Symbol("O"))) # method 3
182 >>> sympify("O + 1", locals=ns)
183 O + 1
185 If you want *all* single-letter and Greek-letter variables to be symbols
186 then you can use the clashing-symbols dictionaries that have been defined
187 there as private variables: ``_clash1`` (single-letter variables),
188 ``_clash2`` (the multi-letter Greek names) or ``_clash`` (both single and
189 multi-letter names that are defined in ``abc``).
191 >>> from sympy.abc import _clash1
192 >>> set(_clash1) # if this fails, see issue #23903
193 {'E', 'I', 'N', 'O', 'Q', 'S'}
194 >>> sympify('I & Q', _clash1)
195 I & Q
197 Strict
198 ------
200 If the option ``strict`` is set to ``True``, only the types for which an
201 explicit conversion has been defined are converted. In the other
202 cases, a SympifyError is raised.
204 >>> print(sympify(None))
205 None
206 >>> sympify(None, strict=True)
207 Traceback (most recent call last):
208 ...
209 SympifyError: SympifyError: None
211 .. deprecated:: 1.6
213 ``sympify(obj)`` automatically falls back to ``str(obj)`` when all
214 other conversion methods fail, but this is deprecated. ``strict=True``
215 will disable this deprecated behavior. See
216 :ref:`deprecated-sympify-string-fallback`.
218 Evaluation
219 ----------
221 If the option ``evaluate`` is set to ``False``, then arithmetic and
222 operators will be converted into their SymPy equivalents and the
223 ``evaluate=False`` option will be added. Nested ``Add`` or ``Mul`` will
224 be denested first. This is done via an AST transformation that replaces
225 operators with their SymPy equivalents, so if an operand redefines any
226 of those operations, the redefined operators will not be used. If
227 argument a is not a string, the mathematical expression is evaluated
228 before being passed to sympify, so adding ``evaluate=False`` will still
229 return the evaluated result of expression.
231 >>> sympify('2**2 / 3 + 5')
232 19/3
233 >>> sympify('2**2 / 3 + 5', evaluate=False)
234 2**2/3 + 5
235 >>> sympify('4/2+7', evaluate=True)
236 9
237 >>> sympify('4/2+7', evaluate=False)
238 4/2 + 7
239 >>> sympify(4/2+7, evaluate=False)
240 9.00000000000000
242 Extending
243 ---------
245 To extend ``sympify`` to convert custom objects (not derived from ``Basic``),
246 just define a ``_sympy_`` method to your class. You can do that even to
247 classes that you do not own by subclassing or adding the method at runtime.
249 >>> from sympy import Matrix
250 >>> class MyList1(object):
251 ... def __iter__(self):
252 ... yield 1
253 ... yield 2
254 ... return
255 ... def __getitem__(self, i): return list(self)[i]
256 ... def _sympy_(self): return Matrix(self)
257 >>> sympify(MyList1())
258 Matrix([
259 [1],
260 [2]])
262 If you do not have control over the class definition you could also use the
263 ``converter`` global dictionary. The key is the class and the value is a
264 function that takes a single argument and returns the desired SymPy
265 object, e.g. ``converter[MyList] = lambda x: Matrix(x)``.
267 >>> class MyList2(object): # XXX Do not do this if you control the class!
268 ... def __iter__(self): # Use _sympy_!
269 ... yield 1
270 ... yield 2
271 ... return
272 ... def __getitem__(self, i): return list(self)[i]
273 >>> from sympy.core.sympify import converter
274 >>> converter[MyList2] = lambda x: Matrix(x)
275 >>> sympify(MyList2())
276 Matrix([
277 [1],
278 [2]])
280 Notes
281 =====
283 The keywords ``rational`` and ``convert_xor`` are only used
284 when the input is a string.
286 convert_xor
287 -----------
289 >>> sympify('x^y',convert_xor=True)
290 x**y
291 >>> sympify('x^y',convert_xor=False)
292 x ^ y
294 rational
295 --------
297 >>> sympify('0.1',rational=False)
298 0.1
299 >>> sympify('0.1',rational=True)
300 1/10
302 Sometimes autosimplification during sympification results in expressions
303 that are very different in structure than what was entered. Until such
304 autosimplification is no longer done, the ``kernS`` function might be of
305 some use. In the example below you can see how an expression reduces to
306 $-1$ by autosimplification, but does not do so when ``kernS`` is used.
308 >>> from sympy.core.sympify import kernS
309 >>> from sympy.abc import x
310 >>> -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1
311 -1
312 >>> s = '-2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1'
313 >>> sympify(s)
314 -1
315 >>> kernS(s)
316 -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1
318 Parameters
319 ==========
321 a :
322 - any object defined in SymPy
323 - standard numeric Python types: ``int``, ``long``, ``float``, ``Decimal``
324 - strings (like ``"0.09"``, ``"2e-19"`` or ``'sin(x)'``)
325 - booleans, including ``None`` (will leave ``None`` unchanged)
326 - dicts, lists, sets or tuples containing any of the above
328 convert_xor : bool, optional
329 If true, treats ``^`` as exponentiation.
330 If False, treats ``^`` as XOR itself.
331 Used only when input is a string.
333 locals : any object defined in SymPy, optional
334 In order to have strings be recognized it can be imported
335 into a namespace dictionary and passed as locals.
337 strict : bool, optional
338 If the option strict is set to ``True``, only the types for which
339 an explicit conversion has been defined are converted. In the
340 other cases, a SympifyError is raised.
342 rational : bool, optional
343 If ``True``, converts floats into :class:`~.Rational`.
344 If ``False``, it lets floats remain as it is.
345 Used only when input is a string.
347 evaluate : bool, optional
348 If False, then arithmetic and operators will be converted into
349 their SymPy equivalents. If True the expression will be evaluated
350 and the result will be returned.
352 """
353 # XXX: If a is a Basic subclass rather than instance (e.g. sin rather than
354 # sin(x)) then a.__sympy__ will be the property. Only on the instance will
355 # a.__sympy__ give the *value* of the property (True). Since sympify(sin)
356 # was used for a long time we allow it to pass. However if strict=True as
357 # is the case in internal calls to _sympify then we only allow
358 # is_sympy=True.
359 #
360 # https://github.com/sympy/sympy/issues/20124
361 is_sympy = getattr(a, '__sympy__', None)
362 if is_sympy is True:
363 return a
364 elif is_sympy is not None:
365 if not strict:
366 return a
367 else:
368 raise SympifyError(a)
370 if isinstance(a, CantSympify):
371 raise SympifyError(a)
373 cls = getattr(a, "__class__", None)
375 #Check if there exists a converter for any of the types in the mro
376 for superclass in getmro(cls):
377 #First check for user defined converters
378 conv = _external_converter.get(superclass)
379 if conv is None:
380 #if none exists, check for SymPy defined converters
381 conv = _sympy_converter.get(superclass)
382 if conv is not None:
383 return conv(a)
385 if cls is type(None):
386 if strict:
387 raise SympifyError(a)
388 else:
389 return a
391 if evaluate is None:
392 evaluate = global_parameters.evaluate
394 # Support for basic numpy datatypes
395 if _is_numpy_instance(a):
396 import numpy as np
397 if np.isscalar(a):
398 return _convert_numpy_types(a, locals=locals,
399 convert_xor=convert_xor, strict=strict, rational=rational,
400 evaluate=evaluate)
402 _sympy_ = getattr(a, "_sympy_", None)
403 if _sympy_ is not None:
404 try:
405 return a._sympy_()
406 # XXX: Catches AttributeError: 'SymPyConverter' object has no
407 # attribute 'tuple'
408 # This is probably a bug somewhere but for now we catch it here.
409 except AttributeError:
410 pass
412 if not strict:
413 # Put numpy array conversion _before_ float/int, see
414 # <https://github.com/sympy/sympy/issues/13924>.
415 flat = getattr(a, "flat", None)
416 if flat is not None:
417 shape = getattr(a, "shape", None)
418 if shape is not None:
419 from sympy.tensor.array import Array
420 return Array(a.flat, a.shape) # works with e.g. NumPy arrays
422 if not isinstance(a, str):
423 if _is_numpy_instance(a):
424 import numpy as np
425 assert not isinstance(a, np.number)
426 if isinstance(a, np.ndarray):
427 # Scalar arrays (those with zero dimensions) have sympify
428 # called on the scalar element.
429 if a.ndim == 0:
430 try:
431 return sympify(a.item(),
432 locals=locals,
433 convert_xor=convert_xor,
434 strict=strict,
435 rational=rational,
436 evaluate=evaluate)
437 except SympifyError:
438 pass
439 else:
440 # float and int can coerce size-one numpy arrays to their lone
441 # element. See issue https://github.com/numpy/numpy/issues/10404.
442 for coerce in (float, int):
443 try:
444 return sympify(coerce(a))
445 except (TypeError, ValueError, AttributeError, SympifyError):
446 continue
448 if strict:
449 raise SympifyError(a)
451 if iterable(a):
452 try:
453 return type(a)([sympify(x, locals=locals, convert_xor=convert_xor,
454 rational=rational, evaluate=evaluate) for x in a])
455 except TypeError:
456 # Not all iterables are rebuildable with their type.
457 pass
459 if not isinstance(a, str):
460 try:
461 a = str(a)
462 except Exception as exc:
463 raise SympifyError(a, exc)
464 sympy_deprecation_warning(
465 f"""
466The string fallback in sympify() is deprecated.
468To explicitly convert the string form of an object, use
469sympify(str(obj)). To add define sympify behavior on custom
470objects, use sympy.core.sympify.converter or define obj._sympy_
471(see the sympify() docstring).
473sympify() performed the string fallback resulting in the following string:
475{a!r}
476 """,
477 deprecated_since_version='1.6',
478 active_deprecations_target="deprecated-sympify-string-fallback",
479 )
481 from sympy.parsing.sympy_parser import (parse_expr, TokenError,
482 standard_transformations)
483 from sympy.parsing.sympy_parser import convert_xor as t_convert_xor
484 from sympy.parsing.sympy_parser import rationalize as t_rationalize
486 transformations = standard_transformations
488 if rational:
489 transformations += (t_rationalize,)
490 if convert_xor:
491 transformations += (t_convert_xor,)
493 try:
494 a = a.replace('\n', '')
495 expr = parse_expr(a, local_dict=locals, transformations=transformations, evaluate=evaluate)
496 except (TokenError, SyntaxError) as exc:
497 raise SympifyError('could not parse %r' % a, exc)
499 return expr
502def _sympify(a):
503 """
504 Short version of :func:`~.sympify` for internal usage for ``__add__`` and
505 ``__eq__`` methods where it is ok to allow some things (like Python
506 integers and floats) in the expression. This excludes things (like strings)
507 that are unwise to allow into such an expression.
509 >>> from sympy import Integer
510 >>> Integer(1) == 1
511 True
513 >>> Integer(1) == '1'
514 False
516 >>> from sympy.abc import x
517 >>> x + 1
518 x + 1
520 >>> x + '1'
521 Traceback (most recent call last):
522 ...
523 TypeError: unsupported operand type(s) for +: 'Symbol' and 'str'
525 see: sympify
527 """
528 return sympify(a, strict=True)
531def kernS(s):
532 """Use a hack to try keep autosimplification from distributing a
533 a number into an Add; this modification does not
534 prevent the 2-arg Mul from becoming an Add, however.
536 Examples
537 ========
539 >>> from sympy.core.sympify import kernS
540 >>> from sympy.abc import x, y
542 The 2-arg Mul distributes a number (or minus sign) across the terms
543 of an expression, but kernS will prevent that:
545 >>> 2*(x + y), -(x + 1)
546 (2*x + 2*y, -x - 1)
547 >>> kernS('2*(x + y)')
548 2*(x + y)
549 >>> kernS('-(x + 1)')
550 -(x + 1)
552 If use of the hack fails, the un-hacked string will be passed to sympify...
553 and you get what you get.
555 XXX This hack should not be necessary once issue 4596 has been resolved.
556 """
557 hit = False
558 quoted = '"' in s or "'" in s
559 if '(' in s and not quoted:
560 if s.count('(') != s.count(")"):
561 raise SympifyError('unmatched left parenthesis')
563 # strip all space from s
564 s = ''.join(s.split())
565 olds = s
566 # now use space to represent a symbol that
567 # will
568 # step 1. turn potential 2-arg Muls into 3-arg versions
569 # 1a. *( -> * *(
570 s = s.replace('*(', '* *(')
571 # 1b. close up exponentials
572 s = s.replace('** *', '**')
573 # 2. handle the implied multiplication of a negated
574 # parenthesized expression in two steps
575 # 2a: -(...) --> -( *(...)
576 target = '-( *('
577 s = s.replace('-(', target)
578 # 2b: double the matching closing parenthesis
579 # -( *(...) --> -( *(...))
580 i = nest = 0
581 assert target.endswith('(') # assumption below
582 while True:
583 j = s.find(target, i)
584 if j == -1:
585 break
586 j += len(target) - 1
587 for j in range(j, len(s)):
588 if s[j] == "(":
589 nest += 1
590 elif s[j] == ")":
591 nest -= 1
592 if nest == 0:
593 break
594 s = s[:j] + ")" + s[j:]
595 i = j + 2 # the first char after 2nd )
596 if ' ' in s:
597 # get a unique kern
598 kern = '_'
599 while kern in s:
600 kern += choice(string.ascii_letters + string.digits)
601 s = s.replace(' ', kern)
602 hit = kern in s
603 else:
604 hit = False
606 for i in range(2):
607 try:
608 expr = sympify(s)
609 break
610 except TypeError: # the kern might cause unknown errors...
611 if hit:
612 s = olds # maybe it didn't like the kern; use un-kerned s
613 hit = False
614 continue
615 expr = sympify(s) # let original error raise
617 if not hit:
618 return expr
620 from .symbol import Symbol
621 rep = {Symbol(kern): 1}
622 def _clear(expr):
623 if isinstance(expr, (list, tuple, set)):
624 return type(expr)([_clear(e) for e in expr])
625 if hasattr(expr, 'subs'):
626 return expr.subs(rep, hack2=True)
627 return expr
628 expr = _clear(expr)
629 # hope that kern is not there anymore
630 return expr
633# Avoid circular import
634from .basic import Basic