singleton.py 6.9 KB

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