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

1"""Singleton mechanism""" 

2 

3 

4from .core import Registry 

5from .sympify import sympify 

6 

7 

8class SingletonRegistry(Registry): 

9 """ 

10 The registry for the singleton classes (accessible as ``S``). 

11 

12 Explanation 

13 =========== 

14 

15 This class serves as two separate things. 

16 

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``. 

25 

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. 

33 

34 Examples 

35 ======== 

36 

37 >>> from sympy import S, Integer 

38 >>> a = Integer(0) 

39 >>> a is S.Zero 

40 True 

41 

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)``). 

48 

49 When using ``is`` comparison, make sure the argument is sympified. For 

50 instance, 

51 

52 >>> x = 0 

53 >>> x is S.Zero 

54 False 

55 

56 This problem is not an issue when using ``==``, which is recommended for 

57 most use-cases: 

58 

59 >>> 0 == S.Zero 

60 True 

61 

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``). 

69 

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``. 

82 

83 """ 

84 __slots__ = () 

85 

86 # Also allow things like S(5) 

87 __call__ = staticmethod(sympify) 

88 

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). 

99 

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 

105 

106 def __getattr__(self, name): 

107 """Python calls __getattr__ if no attribute of that name was installed 

108 yet. 

109 

110 Explanation 

111 =========== 

112 

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 

126 

127 def __repr__(self): 

128 return "S" 

129 

130S = SingletonRegistry() 

131 

132 

133class Singleton(type): 

134 """ 

135 Metaclass for singleton classes. 

136 

137 Explanation 

138 =========== 

139 

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>``. 

143 

144 Examples 

145 ======== 

146 

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 

157 

158 Notes 

159 ===== 

160 

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) 

171 

172 

173# Delayed to avoid cyclic import 

174from .basic import Basic