Coverage for /usr/lib/python3/dist-packages/sympy/core/singleton.py: 97%
30 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"""Singleton mechanism"""
4from .core import Registry
5from .sympify import sympify
8class SingletonRegistry(Registry):
9 """
10 The registry for the singleton classes (accessible as ``S``).
12 Explanation
13 ===========
15 This class serves as two separate things.
17 The first thing it is is the ``SingletonRegistry``. Several classes in
18 SymPy appear so often that they are singletonized, that is, using some
19 metaprogramming they are made so that they can only be instantiated once
20 (see the :class:`sympy.core.singleton.Singleton` class for details). For
21 instance, every time you create ``Integer(0)``, this will return the same
22 instance, :class:`sympy.core.numbers.Zero`. All singleton instances are
23 attributes of the ``S`` object, so ``Integer(0)`` can also be accessed as
24 ``S.Zero``.
26 Singletonization offers two advantages: it saves memory, and it allows
27 fast comparison. It saves memory because no matter how many times the
28 singletonized objects appear in expressions in memory, they all point to
29 the same single instance in memory. The fast comparison comes from the
30 fact that you can use ``is`` to compare exact instances in Python
31 (usually, you need to use ``==`` to compare things). ``is`` compares
32 objects by memory address, and is very fast.
34 Examples
35 ========
37 >>> from sympy import S, Integer
38 >>> a = Integer(0)
39 >>> a is S.Zero
40 True
42 For the most part, the fact that certain objects are singletonized is an
43 implementation detail that users should not need to worry about. In SymPy
44 library code, ``is`` comparison is often used for performance purposes
45 The primary advantage of ``S`` for end users is the convenient access to
46 certain instances that are otherwise difficult to type, like ``S.Half``
47 (instead of ``Rational(1, 2)``).
49 When using ``is`` comparison, make sure the argument is sympified. For
50 instance,
52 >>> x = 0
53 >>> x is S.Zero
54 False
56 This problem is not an issue when using ``==``, which is recommended for
57 most use-cases:
59 >>> 0 == S.Zero
60 True
62 The second thing ``S`` is is a shortcut for
63 :func:`sympy.core.sympify.sympify`. :func:`sympy.core.sympify.sympify` is
64 the function that converts Python objects such as ``int(1)`` into SymPy
65 objects such as ``Integer(1)``. It also converts the string form of an
66 expression into a SymPy expression, like ``sympify("x**2")`` ->
67 ``Symbol("x")**2``. ``S(1)`` is the same thing as ``sympify(1)``
68 (basically, ``S.__call__`` has been defined to call ``sympify``).
70 This is for convenience, since ``S`` is a single letter. It's mostly
71 useful for defining rational numbers. Consider an expression like ``x +
72 1/2``. If you enter this directly in Python, it will evaluate the ``1/2``
73 and give ``0.5``, because both arguments are ints (see also
74 :ref:`tutorial-gotchas-final-notes`). However, in SymPy, you usually want
75 the quotient of two integers to give an exact rational number. The way
76 Python's evaluation works, at least one side of an operator needs to be a
77 SymPy object for the SymPy evaluation to take over. You could write this
78 as ``x + Rational(1, 2)``, but this is a lot more typing. A shorter
79 version is ``x + S(1)/2``. Since ``S(1)`` returns ``Integer(1)``, the
80 division will return a ``Rational`` type, since it will call
81 ``Integer.__truediv__``, which knows how to return a ``Rational``.
83 """
84 __slots__ = ()
86 # Also allow things like S(5)
87 __call__ = staticmethod(sympify)
89 def __init__(self):
90 self._classes_to_install = {}
91 # Dict of classes that have been registered, but that have not have been
92 # installed as an attribute of this SingletonRegistry.
93 # Installation automatically happens at the first attempt to access the
94 # attribute.
95 # The purpose of this is to allow registration during class
96 # initialization during import, but not trigger object creation until
97 # actual use (which should not happen until after all imports are
98 # finished).
100 def register(self, cls):
101 # Make sure a duplicate class overwrites the old one
102 if hasattr(self, cls.__name__):
103 delattr(self, cls.__name__)
104 self._classes_to_install[cls.__name__] = cls
106 def __getattr__(self, name):
107 """Python calls __getattr__ if no attribute of that name was installed
108 yet.
110 Explanation
111 ===========
113 This __getattr__ checks whether a class with the requested name was
114 already registered but not installed; if no, raises an AttributeError.
115 Otherwise, retrieves the class, calculates its singleton value, installs
116 it as an attribute of the given name, and unregisters the class."""
117 if name not in self._classes_to_install:
118 raise AttributeError(
119 "Attribute '%s' was not installed on SymPy registry %s" % (
120 name, self))
121 class_to_install = self._classes_to_install[name]
122 value_to_install = class_to_install()
123 self.__setattr__(name, value_to_install)
124 del self._classes_to_install[name]
125 return value_to_install
127 def __repr__(self):
128 return "S"
130S = SingletonRegistry()
133class Singleton(type):
134 """
135 Metaclass for singleton classes.
137 Explanation
138 ===========
140 A singleton class has only one instance which is returned every time the
141 class is instantiated. Additionally, this instance can be accessed through
142 the global registry object ``S`` as ``S.<class_name>``.
144 Examples
145 ========
147 >>> from sympy import S, Basic
148 >>> from sympy.core.singleton import Singleton
149 >>> class MySingleton(Basic, metaclass=Singleton):
150 ... pass
151 >>> Basic() is Basic()
152 False
153 >>> MySingleton() is MySingleton()
154 True
155 >>> S.MySingleton is MySingleton()
156 True
158 Notes
159 =====
161 Instance creation is delayed until the first time the value is accessed.
162 (SymPy versions before 1.0 would create the instance during class
163 creation time, which would be prone to import cycles.)
164 """
165 def __init__(cls, *args, **kwargs):
166 cls._instance = obj = Basic.__new__(cls)
167 cls.__new__ = lambda cls: obj
168 cls.__getnewargs__ = lambda obj: ()
169 cls.__getstate__ = lambda obj: None
170 S.register(cls)
173# Delayed to avoid cyclic import
174from .basic import Basic