Spaces:
Sleeping
Sleeping
"""Singleton mechanism""" | |
from .core import Registry | |
from .sympify import sympify | |
class SingletonRegistry(Registry): | |
""" | |
The registry for the singleton classes (accessible as ``S``). | |
Explanation | |
=========== | |
This class serves as two separate things. | |
The first thing it is is the ``SingletonRegistry``. Several classes in | |
SymPy appear so often that they are singletonized, that is, using some | |
metaprogramming they are made so that they can only be instantiated once | |
(see the :class:`sympy.core.singleton.Singleton` class for details). For | |
instance, every time you create ``Integer(0)``, this will return the same | |
instance, :class:`sympy.core.numbers.Zero`. All singleton instances are | |
attributes of the ``S`` object, so ``Integer(0)`` can also be accessed as | |
``S.Zero``. | |
Singletonization offers two advantages: it saves memory, and it allows | |
fast comparison. It saves memory because no matter how many times the | |
singletonized objects appear in expressions in memory, they all point to | |
the same single instance in memory. The fast comparison comes from the | |
fact that you can use ``is`` to compare exact instances in Python | |
(usually, you need to use ``==`` to compare things). ``is`` compares | |
objects by memory address, and is very fast. | |
Examples | |
======== | |
>>> from sympy import S, Integer | |
>>> a = Integer(0) | |
>>> a is S.Zero | |
True | |
For the most part, the fact that certain objects are singletonized is an | |
implementation detail that users should not need to worry about. In SymPy | |
library code, ``is`` comparison is often used for performance purposes | |
The primary advantage of ``S`` for end users is the convenient access to | |
certain instances that are otherwise difficult to type, like ``S.Half`` | |
(instead of ``Rational(1, 2)``). | |
When using ``is`` comparison, make sure the argument is sympified. For | |
instance, | |
>>> x = 0 | |
>>> x is S.Zero | |
False | |
This problem is not an issue when using ``==``, which is recommended for | |
most use-cases: | |
>>> 0 == S.Zero | |
True | |
The second thing ``S`` is is a shortcut for | |
:func:`sympy.core.sympify.sympify`. :func:`sympy.core.sympify.sympify` is | |
the function that converts Python objects such as ``int(1)`` into SymPy | |
objects such as ``Integer(1)``. It also converts the string form of an | |
expression into a SymPy expression, like ``sympify("x**2")`` -> | |
``Symbol("x")**2``. ``S(1)`` is the same thing as ``sympify(1)`` | |
(basically, ``S.__call__`` has been defined to call ``sympify``). | |
This is for convenience, since ``S`` is a single letter. It's mostly | |
useful for defining rational numbers. Consider an expression like ``x + | |
1/2``. If you enter this directly in Python, it will evaluate the ``1/2`` | |
and give ``0.5``, because both arguments are ints (see also | |
:ref:`tutorial-gotchas-final-notes`). However, in SymPy, you usually want | |
the quotient of two integers to give an exact rational number. The way | |
Python's evaluation works, at least one side of an operator needs to be a | |
SymPy object for the SymPy evaluation to take over. You could write this | |
as ``x + Rational(1, 2)``, but this is a lot more typing. A shorter | |
version is ``x + S(1)/2``. Since ``S(1)`` returns ``Integer(1)``, the | |
division will return a ``Rational`` type, since it will call | |
``Integer.__truediv__``, which knows how to return a ``Rational``. | |
""" | |
__slots__ = () | |
# Also allow things like S(5) | |
__call__ = staticmethod(sympify) | |
def __init__(self): | |
self._classes_to_install = {} | |
# Dict of classes that have been registered, but that have not have been | |
# installed as an attribute of this SingletonRegistry. | |
# Installation automatically happens at the first attempt to access the | |
# attribute. | |
# The purpose of this is to allow registration during class | |
# initialization during import, but not trigger object creation until | |
# actual use (which should not happen until after all imports are | |
# finished). | |
def register(self, cls): | |
# Make sure a duplicate class overwrites the old one | |
if hasattr(self, cls.__name__): | |
delattr(self, cls.__name__) | |
self._classes_to_install[cls.__name__] = cls | |
def __getattr__(self, name): | |
"""Python calls __getattr__ if no attribute of that name was installed | |
yet. | |
Explanation | |
=========== | |
This __getattr__ checks whether a class with the requested name was | |
already registered but not installed; if no, raises an AttributeError. | |
Otherwise, retrieves the class, calculates its singleton value, installs | |
it as an attribute of the given name, and unregisters the class.""" | |
if name not in self._classes_to_install: | |
raise AttributeError( | |
"Attribute '%s' was not installed on SymPy registry %s" % ( | |
name, self)) | |
class_to_install = self._classes_to_install[name] | |
value_to_install = class_to_install() | |
self.__setattr__(name, value_to_install) | |
del self._classes_to_install[name] | |
return value_to_install | |
def __repr__(self): | |
return "S" | |
S = SingletonRegistry() | |
class Singleton(type): | |
""" | |
Metaclass for singleton classes. | |
Explanation | |
=========== | |
A singleton class has only one instance which is returned every time the | |
class is instantiated. Additionally, this instance can be accessed through | |
the global registry object ``S`` as ``S.<class_name>``. | |
Examples | |
======== | |
>>> from sympy import S, Basic | |
>>> from sympy.core.singleton import Singleton | |
>>> class MySingleton(Basic, metaclass=Singleton): | |
... pass | |
>>> Basic() is Basic() | |
False | |
>>> MySingleton() is MySingleton() | |
True | |
>>> S.MySingleton is MySingleton() | |
True | |
Notes | |
===== | |
Instance creation is delayed until the first time the value is accessed. | |
(SymPy versions before 1.0 would create the instance during class | |
creation time, which would be prone to import cycles.) | |
""" | |
def __init__(cls, *args, **kwargs): | |
cls._instance = obj = Basic.__new__(cls) | |
cls.__new__ = lambda cls: obj | |
cls.__getnewargs__ = lambda obj: () | |
cls.__getstate__ = lambda obj: None | |
S.register(cls) | |
# Delayed to avoid cyclic import | |
from .basic import Basic | |