123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- """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
|