123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- """Miscellaneous stuff that doesn't really fit anywhere else."""
- from typing import List
- import operator
- import sys
- import os
- import re as _re
- import struct
- from textwrap import fill, dedent
- class Undecidable(ValueError):
- # an error to be raised when a decision cannot be made definitively
- # where a definitive answer is needed
- pass
- def filldedent(s, w=70):
- """
- Strips leading and trailing empty lines from a copy of `s`, then dedents,
- fills and returns it.
- Empty line stripping serves to deal with docstrings like this one that
- start with a newline after the initial triple quote, inserting an empty
- line at the beginning of the string.
- See Also
- ========
- strlines, rawlines
- """
- return '\n' + fill(dedent(str(s)).strip('\n'), width=w)
- def strlines(s, c=64, short=False):
- """Return a cut-and-pastable string that, when printed, is
- equivalent to the input. The lines will be surrounded by
- parentheses and no line will be longer than c (default 64)
- characters. If the line contains newlines characters, the
- `rawlines` result will be returned. If ``short`` is True
- (default is False) then if there is one line it will be
- returned without bounding parentheses.
- Examples
- ========
- >>> from sympy.utilities.misc import strlines
- >>> q = 'this is a long string that should be broken into shorter lines'
- >>> print(strlines(q, 40))
- (
- 'this is a long string that should be b'
- 'roken into shorter lines'
- )
- >>> q == (
- ... 'this is a long string that should be b'
- ... 'roken into shorter lines'
- ... )
- True
- See Also
- ========
- filldedent, rawlines
- """
- if not isinstance(s, str):
- raise ValueError('expecting string input')
- if '\n' in s:
- return rawlines(s)
- q = '"' if repr(s).startswith('"') else "'"
- q = (q,)*2
- if '\\' in s: # use r-string
- m = '(\nr%s%%s%s\n)' % q
- j = '%s\nr%s' % q
- c -= 3
- else:
- m = '(\n%s%%s%s\n)' % q
- j = '%s\n%s' % q
- c -= 2
- out = []
- while s:
- out.append(s[:c])
- s=s[c:]
- if short and len(out) == 1:
- return (m % out[0]).splitlines()[1] # strip bounding (\n...\n)
- return m % j.join(out)
- def rawlines(s):
- """Return a cut-and-pastable string that, when printed, is equivalent
- to the input. Use this when there is more than one line in the
- string. The string returned is formatted so it can be indented
- nicely within tests; in some cases it is wrapped in the dedent
- function which has to be imported from textwrap.
- Examples
- ========
- Note: because there are characters in the examples below that need
- to be escaped because they are themselves within a triple quoted
- docstring, expressions below look more complicated than they would
- be if they were printed in an interpreter window.
- >>> from sympy.utilities.misc import rawlines
- >>> from sympy import TableForm
- >>> s = str(TableForm([[1, 10]], headings=(None, ['a', 'bee'])))
- >>> print(rawlines(s))
- (
- 'a bee\\n'
- '-----\\n'
- '1 10 '
- )
- >>> print(rawlines('''this
- ... that'''))
- dedent('''\\
- this
- that''')
- >>> print(rawlines('''this
- ... that
- ... '''))
- dedent('''\\
- this
- that
- ''')
- >>> s = \"\"\"this
- ... is a triple '''
- ... \"\"\"
- >>> print(rawlines(s))
- dedent(\"\"\"\\
- this
- is a triple '''
- \"\"\")
- >>> print(rawlines('''this
- ... that
- ... '''))
- (
- 'this\\n'
- 'that\\n'
- ' '
- )
- See Also
- ========
- filldedent, strlines
- """
- lines = s.split('\n')
- if len(lines) == 1:
- return repr(lines[0])
- triple = ["'''" in s, '"""' in s]
- if any(li.endswith(' ') for li in lines) or '\\' in s or all(triple):
- rv = []
- # add on the newlines
- trailing = s.endswith('\n')
- last = len(lines) - 1
- for i, li in enumerate(lines):
- if i != last or trailing:
- rv.append(repr(li + '\n'))
- else:
- rv.append(repr(li))
- return '(\n %s\n)' % '\n '.join(rv)
- else:
- rv = '\n '.join(lines)
- if triple[0]:
- return 'dedent("""\\\n %s""")' % rv
- else:
- return "dedent('''\\\n %s''')" % rv
- ARCH = str(struct.calcsize('P') * 8) + "-bit"
- # XXX: PyPy doesn't support hash randomization
- HASH_RANDOMIZATION = getattr(sys.flags, 'hash_randomization', False)
- _debug_tmp = [] # type: List[str]
- _debug_iter = 0
- def debug_decorator(func):
- """If SYMPY_DEBUG is True, it will print a nice execution tree with
- arguments and results of all decorated functions, else do nothing.
- """
- from sympy import SYMPY_DEBUG
- if not SYMPY_DEBUG:
- return func
- def maketree(f, *args, **kw):
- global _debug_tmp
- global _debug_iter
- oldtmp = _debug_tmp
- _debug_tmp = []
- _debug_iter += 1
- def tree(subtrees):
- def indent(s, variant=1):
- x = s.split("\n")
- r = "+-%s\n" % x[0]
- for a in x[1:]:
- if a == "":
- continue
- if variant == 1:
- r += "| %s\n" % a
- else:
- r += " %s\n" % a
- return r
- if len(subtrees) == 0:
- return ""
- f = []
- for a in subtrees[:-1]:
- f.append(indent(a))
- f.append(indent(subtrees[-1], 2))
- return ''.join(f)
- # If there is a bug and the algorithm enters an infinite loop, enable the
- # following lines. It will print the names and parameters of all major functions
- # that are called, *before* they are called
- #from functools import reduce
- #print("%s%s %s%s" % (_debug_iter, reduce(lambda x, y: x + y, \
- # map(lambda x: '-', range(1, 2 + _debug_iter))), f.__name__, args))
- r = f(*args, **kw)
- _debug_iter -= 1
- s = "%s%s = %s\n" % (f.__name__, args, r)
- if _debug_tmp != []:
- s += tree(_debug_tmp)
- _debug_tmp = oldtmp
- _debug_tmp.append(s)
- if _debug_iter == 0:
- print(_debug_tmp[0])
- _debug_tmp = []
- return r
- def decorated(*args, **kwargs):
- return maketree(func, *args, **kwargs)
- return decorated
- def debug(*args):
- """
- Print ``*args`` if SYMPY_DEBUG is True, else do nothing.
- """
- from sympy import SYMPY_DEBUG
- if SYMPY_DEBUG:
- print(*args, file=sys.stderr)
- def find_executable(executable, path=None):
- """Try to find 'executable' in the directories listed in 'path' (a
- string listing directories separated by 'os.pathsep'; defaults to
- os.environ['PATH']). Returns the complete filename or None if not
- found
- """
- from .exceptions import sympy_deprecation_warning
- sympy_deprecation_warning(
- """
- sympy.utilities.misc.find_executable() is deprecated. Use the standard
- library shutil.which() function instead.
- """,
- deprecated_since_version="1.7",
- active_deprecations_target="deprecated-find-executable",
- )
- if path is None:
- path = os.environ['PATH']
- paths = path.split(os.pathsep)
- extlist = ['']
- if os.name == 'os2':
- (base, ext) = os.path.splitext(executable)
- # executable files on OS/2 can have an arbitrary extension, but
- # .exe is automatically appended if no dot is present in the name
- if not ext:
- executable = executable + ".exe"
- elif sys.platform == 'win32':
- pathext = os.environ['PATHEXT'].lower().split(os.pathsep)
- (base, ext) = os.path.splitext(executable)
- if ext.lower() not in pathext:
- extlist = pathext
- for ext in extlist:
- execname = executable + ext
- if os.path.isfile(execname):
- return execname
- else:
- for p in paths:
- f = os.path.join(p, execname)
- if os.path.isfile(f):
- return f
- return None
- def func_name(x, short=False):
- """Return function name of `x` (if defined) else the `type(x)`.
- If short is True and there is a shorter alias for the result,
- return the alias.
- Examples
- ========
- >>> from sympy.utilities.misc import func_name
- >>> from sympy import Matrix
- >>> from sympy.abc import x
- >>> func_name(Matrix.eye(3))
- 'MutableDenseMatrix'
- >>> func_name(x < 1)
- 'StrictLessThan'
- >>> func_name(x < 1, short=True)
- 'Lt'
- """
- alias = {
- 'GreaterThan': 'Ge',
- 'StrictGreaterThan': 'Gt',
- 'LessThan': 'Le',
- 'StrictLessThan': 'Lt',
- 'Equality': 'Eq',
- 'Unequality': 'Ne',
- }
- typ = type(x)
- if str(typ).startswith("<type '"):
- typ = str(typ).split("'")[1].split("'")[0]
- elif str(typ).startswith("<class '"):
- typ = str(typ).split("'")[1].split("'")[0]
- rv = getattr(getattr(x, 'func', x), '__name__', typ)
- if '.' in rv:
- rv = rv.split('.')[-1]
- if short:
- rv = alias.get(rv, rv)
- return rv
- def _replace(reps):
- """Return a function that can make the replacements, given in
- ``reps``, on a string. The replacements should be given as mapping.
- Examples
- ========
- >>> from sympy.utilities.misc import _replace
- >>> f = _replace(dict(foo='bar', d='t'))
- >>> f('food')
- 'bart'
- >>> f = _replace({})
- >>> f('food')
- 'food'
- """
- if not reps:
- return lambda x: x
- D = lambda match: reps[match.group(0)]
- pattern = _re.compile("|".join(
- [_re.escape(k) for k, v in reps.items()]), _re.M)
- return lambda string: pattern.sub(D, string)
- def replace(string, *reps):
- """Return ``string`` with all keys in ``reps`` replaced with
- their corresponding values, longer strings first, irrespective
- of the order they are given. ``reps`` may be passed as tuples
- or a single mapping.
- Examples
- ========
- >>> from sympy.utilities.misc import replace
- >>> replace('foo', {'oo': 'ar', 'f': 'b'})
- 'bar'
- >>> replace("spamham sha", ("spam", "eggs"), ("sha","md5"))
- 'eggsham md5'
- There is no guarantee that a unique answer will be
- obtained if keys in a mapping overlap (i.e. are the same
- length and have some identical sequence at the
- beginning/end):
- >>> reps = [
- ... ('ab', 'x'),
- ... ('bc', 'y')]
- >>> replace('abc', *reps) in ('xc', 'ay')
- True
- References
- ==========
- .. [1] https://stackoverflow.com/questions/6116978/python-replace-multiple-strings
- """
- if len(reps) == 1:
- kv = reps[0]
- if isinstance(kv, dict):
- reps = kv
- else:
- return string.replace(*kv)
- else:
- reps = dict(reps)
- return _replace(reps)(string)
- def translate(s, a, b=None, c=None):
- """Return ``s`` where characters have been replaced or deleted.
- SYNTAX
- ======
- translate(s, None, deletechars):
- all characters in ``deletechars`` are deleted
- translate(s, map [,deletechars]):
- all characters in ``deletechars`` (if provided) are deleted
- then the replacements defined by map are made; if the keys
- of map are strings then the longer ones are handled first.
- Multicharacter deletions should have a value of ''.
- translate(s, oldchars, newchars, deletechars)
- all characters in ``deletechars`` are deleted
- then each character in ``oldchars`` is replaced with the
- corresponding character in ``newchars``
- Examples
- ========
- >>> from sympy.utilities.misc import translate
- >>> abc = 'abc'
- >>> translate(abc, None, 'a')
- 'bc'
- >>> translate(abc, {'a': 'x'}, 'c')
- 'xb'
- >>> translate(abc, {'abc': 'x', 'a': 'y'})
- 'x'
- >>> translate('abcd', 'ac', 'AC', 'd')
- 'AbC'
- There is no guarantee that a unique answer will be
- obtained if keys in a mapping overlap are the same
- length and have some identical sequences at the
- beginning/end:
- >>> translate(abc, {'ab': 'x', 'bc': 'y'}) in ('xc', 'ay')
- True
- """
- mr = {}
- if a is None:
- if c is not None:
- raise ValueError('c should be None when a=None is passed, instead got %s' % c)
- if b is None:
- return s
- c = b
- a = b = ''
- else:
- if isinstance(a, dict):
- short = {}
- for k in list(a.keys()):
- if len(k) == 1 and len(a[k]) == 1:
- short[k] = a.pop(k)
- mr = a
- c = b
- if short:
- a, b = [''.join(i) for i in list(zip(*short.items()))]
- else:
- a = b = ''
- elif len(a) != len(b):
- raise ValueError('oldchars and newchars have different lengths')
- if c:
- val = str.maketrans('', '', c)
- s = s.translate(val)
- s = replace(s, mr)
- n = str.maketrans(a, b)
- return s.translate(n)
- def ordinal(num):
- """Return ordinal number string of num, e.g. 1 becomes 1st.
- """
- # modified from https://codereview.stackexchange.com/questions/41298/producing-ordinal-numbers
- n = as_int(num)
- k = abs(n) % 100
- if 11 <= k <= 13:
- suffix = 'th'
- elif k % 10 == 1:
- suffix = 'st'
- elif k % 10 == 2:
- suffix = 'nd'
- elif k % 10 == 3:
- suffix = 'rd'
- else:
- suffix = 'th'
- return str(n) + suffix
- def as_int(n, strict=True):
- """
- Convert the argument to a builtin integer.
- The return value is guaranteed to be equal to the input. ValueError is
- raised if the input has a non-integral value. When ``strict`` is True, this
- uses `__index__ <https://docs.python.org/3/reference/datamodel.html#object.__index__>`_
- and when it is False it uses ``int``.
- Examples
- ========
- >>> from sympy.utilities.misc import as_int
- >>> from sympy import sqrt, S
- The function is primarily concerned with sanitizing input for
- functions that need to work with builtin integers, so anything that
- is unambiguously an integer should be returned as an int:
- >>> as_int(S(3))
- 3
- Floats, being of limited precision, are not assumed to be exact and
- will raise an error unless the ``strict`` flag is False. This
- precision issue becomes apparent for large floating point numbers:
- >>> big = 1e23
- >>> type(big) is float
- True
- >>> big == int(big)
- True
- >>> as_int(big)
- Traceback (most recent call last):
- ...
- ValueError: ... is not an integer
- >>> as_int(big, strict=False)
- 99999999999999991611392
- Input that might be a complex representation of an integer value is
- also rejected by default:
- >>> one = sqrt(3 + 2*sqrt(2)) - sqrt(2)
- >>> int(one) == 1
- True
- >>> as_int(one)
- Traceback (most recent call last):
- ...
- ValueError: ... is not an integer
- """
- if strict:
- try:
- if isinstance(n, bool):
- raise TypeError
- return operator.index(n)
- except TypeError:
- raise ValueError('%s is not an integer' % (n,))
- else:
- try:
- result = int(n)
- except TypeError:
- raise ValueError('%s is not an integer' % (n,))
- if n != result:
- raise ValueError('%s is not an integer' % (n,))
- return result
|