Lazy imports - Utilities

Skip to content
Back to top
View this page
Edit this page

# Lazy imports

This module allows one to lazily import objects into a namespace, where the actual import is delayed until the object is actually called or inspected. This is useful for modules that are expensive to import or may cause circular references, though there is some overhead in its use.

EXAMPLES:

Sage

    sage: lazy_import('sage.rings.integer_ring', 'ZZ')
    sage: type(ZZ)
    <class 'sage.misc.lazy_import.LazyImport'>
    sage: ZZ(4.0)
    4

Python

    >>> from sage.all import *
    >>> lazy_import('sage.rings.integer_ring', 'ZZ')
    >>> type(ZZ)
    <class 'sage.misc.lazy_import.LazyImport'>
    >>> ZZ(RealNumber('4.0'))
    4

By default, a warning is issued if a lazy import module is resolved during Sage’s startup. In case a lazy import’s sole purpose is to break a circular reference and it is known to be resolved at startup time, one can use the `at_startup` option:

Sage

    sage: lazy_import('sage.rings.integer_ring', 'ZZ', at_startup=True)

[Python]

    >>> from sage.all import *
    >>> lazy_import('sage.rings.integer_ring', 'ZZ', at_startup=True)

This option can also be used as an intermediate step toward not importing by default a module that is used in several places, some of which can already afford to lazy import the module but not all.

A lazy import that is marked as “at_startup” will print a message if it is actually resolved after the startup, so that the developer knows that (s)he can remove the flag:

Sage

    sage: ZZ
    doctest:warning...
    UserWarning: Option ``at_startup=True`` for lazy import ZZ not needed anymore
    Integer Ring

Python

    >>> from sage.all import *
    >>> ZZ
    doctest:warning...
    UserWarning: Option ``at_startup=True`` for lazy import ZZ not needed anymore
    Integer Ring

Warning

After the first usage, the imported object is directly injected into the namespace; however, before that, the `LazyImport` object is not exactly equivalent to the actual imported object. For example:

Sage

    sage: from sage.misc.lazy_import import LazyImport
    sage: my_qqbar = LazyImport('sage.rings.qqbar', 'QQbar')
    sage: my_qqbar(5) == QQbar(5)  # good
    True
    sage: isinstance(QQbar, Parent)
    True
    sage: isinstance(my_qqbar, Parent)  # fails!
    False

Python

    >>> from sage.all import *
    >>> from sage.misc.lazy_import import LazyImport
    >>> my_qqbar = LazyImport('sage.rings.qqbar', 'QQbar')
    >>> my_qqbar(Integer(5)) == QQbar(Integer(5))  # good
    True
    >>> isinstance(QQbar, Parent)
    True
    >>> isinstance(my_qqbar, Parent)  # fails!
    False

To avoid this issue, you may execute the import inside the function instead.

See also

`lazy_import()`, `LazyImport`

AUTHOR:

> - Robert Bradshaw

class sage.misc.lazy_import.LazyImport

  Bases: `object`

  EXAMPLES:

  Sage

      sage: from sage.misc.lazy_import import LazyImport
      sage: my_integer = LazyImport('sage.rings.integer', 'Integer')
      sage: my_integer(4)
      4
      sage: my_integer('101', base=2)
      5
      sage: my_integer(3/2)
      Traceback (most recent call last):
      ...
      TypeError: no conversion of this rational to integer

  Python

      >>> from sage.all import *
      >>> from sage.misc.lazy_import import LazyImport
      >>> my_integer = LazyImport('sage.rings.integer', 'Integer')
      >>> my_integer(Integer(4))
      4
      >>> my_integer('101', base=Integer(2))
      5
      >>> my_integer(Integer(3)/Integer(2))
      Traceback (most recent call last):
      ...
      TypeError: no conversion of this rational to integer

sage.misc.lazy_import.attributes(a)

  Return the private attributes of a `LazyImport` object in a dictionary.

  This is for debugging and doctesting purposes only.

  EXAMPLES:

  Sage

      sage: from sage.misc.lazy_import import attributes
      sage: lazy_import("sage.structure.unique_representation", "foo")
      sage: attributes(foo)['_namespace'] is globals()
      True
      sage: D = attributes(foo)
      sage: del D['_namespace']
      sage: D
      {'_as_name': 'foo',
       '_at_startup': False,
       '_deprecation': None,
       '_module': 'sage.structure.unique_representation',
       '_name': 'foo',
       '_object': None}

  Python

      >>> from sage.all import *
      >>> from sage.misc.lazy_import import attributes
      >>> lazy_import("sage.structure.unique_representation", "foo")
      >>> attributes(foo)['_namespace'] is globals()
      True
      >>> D = attributes(foo)
      >>> del D['_namespace']
      >>> D
      {'_as_name': 'foo',
       '_at_startup': False,
       '_deprecation': None,
       '_module': 'sage.structure.unique_representation',
       '_name': 'foo',
       '_object': None}

sage.misc.lazy_import.clean_namespace(namespace=None)

  Adjust `LazyImport` bindings in given namespace to refer to this actual namespace.

  When `LazyImport` objects are imported into other namespaces via normal `import` instructions, the data stored on a `LazyImport` object that helps it to adjust the binding in the namespace to the actual imported object upon access is not adjusted. This routine fixes that.

  INPUT:

  - `namespace` – the namespace where importing the names; by default, import the names to current namespace

  EXAMPLES:

  Sage

      sage: # needs sage.symbolic
      sage: from sage.misc.lazy_import import attributes, clean_namespace
      sage: from sage.misc.functional import CDF as C
      sage: attributes(C)['_as_name']
      'CDF'
      sage: attributes(C)['_namespace'] is sage.misc.functional.__dict__
      True
      sage: clean_namespace(globals())
      sage: attributes(C)['_as_name']
      'C'
      sage: attributes(C)['_namespace'] is globals()
      True

  Python

      >>> from sage.all import *
      >>> # needs sage.symbolic
      >>> from sage.misc.lazy_import import attributes, clean_namespace
      >>> from sage.misc.functional import CDF as C
      >>> attributes(C)['_as_name']
      'CDF'
      >>> attributes(C)['_namespace'] is sage.misc.functional.__dict__
      True
      >>> clean_namespace(globals())
      >>> attributes(C)['_as_name']
      'C'
      >>> attributes(C)['_namespace'] is globals()
      True

sage.misc.lazy_import.ensure_startup_finished()

  Make sure that the startup phase is finished.

  In contrast to `finish_startup()`, this function can be called repeatedly.

sage.misc.lazy_import.finish_startup()

  Finish the startup phase.

  This function must be called exactly once at the end of the Sage import process (`all`).

sage.misc.lazy_import.get_star_imports(module_name)

  Lookup the list of names in a module that would be imported with “import *” either via a cache or actually importing.

  EXAMPLES:

  Sage

      sage: from sage.misc.lazy_import import get_star_imports
      sage: 'get_star_imports' in get_star_imports('sage.misc.lazy_import')
      True
      sage: 'EllipticCurve' in get_star_imports('sage.schemes.all')                   # needs sage.schemes
      True

  Python

      >>> from sage.all import *
      >>> from sage.misc.lazy_import import get_star_imports
      >>> 'get_star_imports' in get_star_imports('sage.misc.lazy_import')
      True
      >>> 'EllipticCurve' in get_star_imports('sage.schemes.all')                   # needs sage.schemes
      True

sage.misc.lazy_import.is_during_startup()

  Return whether Sage is currently starting up.

  OUTPUT: boolean

sage.misc.lazy_import.lazy_import(module, names, as_=None, at_startup=False, namespace=None, deprecation=None, feature=None)

  Create a lazy import object and inject it into the caller’s global namespace. For the purposes of introspection and calling, this is like performing a lazy “from module import name” where the import is delayed until the object actually is used or inspected.

  INPUT:

  - `module` – string representing the module to import

  - `names` – string or list of strings representing the names to import from module

  - `as_` – (optional) a string or list of strings representing the names of the objects in the importing module. This is analogous to `from ... import ... as ...`.

  - `at_startup` – boolean (default: `False`); whether the lazy import is supposed to be resolved at startup time

  - `namespace` – the namespace where importing the names; by default, import the names to current namespace

  - `deprecation` – (optional) if not `None`, a deprecation warning will be issued when the object is actually imported; `deprecation` should be either a trac number (integer) or a pair `(issue_number, message)`

  - `feature` – a python module (optional), if it cannot be imported an appropriate error is raised

  See also

  `sage.misc.lazy_import`, `LazyImport`

  EXAMPLES:

  Sage

      sage: lazy_import('sage.rings.integer_ring', 'ZZ')
      sage: type(ZZ)
      <class 'sage.misc.lazy_import.LazyImport'>
      sage: ZZ(4.0)
      4
      sage: lazy_import('sage.rings.real_double', 'RDF', 'my_RDF')
      sage: my_RDF._get_object() is RDF
      True
      sage: my_RDF(1/2)
      0.5

      sage: lazy_import('sage.rings.rational_field', ['QQ', 'frac'], ['my_QQ', 'my_frac'])
      sage: my_QQ._get_object() is QQ
      True
      sage: my_frac._get_object() is sage.rings.rational_field.frac
      True

  Python

      >>> from sage.all import *
      >>> lazy_import('sage.rings.integer_ring', 'ZZ')
      >>> type(ZZ)
      <class 'sage.misc.lazy_import.LazyImport'>
      >>> ZZ(RealNumber('4.0'))
      4
      >>> lazy_import('sage.rings.real_double', 'RDF', 'my_RDF')
      >>> my_RDF._get_object() is RDF
      True
      >>> my_RDF(Integer(1)/Integer(2))
      0.5

      >>> lazy_import('sage.rings.rational_field', ['QQ', 'frac'], ['my_QQ', 'my_frac'])
      >>> my_QQ._get_object() is QQ
      True
      >>> my_frac._get_object() is sage.rings.rational_field.frac
      True

  Upon the first use, the object is injected directly into the calling namespace:

  Sage

      sage: lazy_import('sage.rings.integer_ring', 'ZZ', 'my_ZZ')
      sage: my_ZZ is ZZ
      False
      sage: my_ZZ(37)
      37
      sage: my_ZZ is ZZ
      True

  [Python]

      >>> from sage.all import *
      >>> lazy_import('sage.rings.integer_ring', 'ZZ', 'my_ZZ')
      >>> my_ZZ is ZZ
      False
      >>> my_ZZ(Integer(37))
      37
      >>> my_ZZ is ZZ
      True

  We check that `lazy_import()` also works for methods:

  Sage

      sage: class Foo():
      ....:     lazy_import('sage.plot.plot', 'plot')
      sage: class Bar(Foo):
      ....:     pass
      sage: type(Foo.__dict__['plot'])
      <class 'sage.misc.lazy_import.LazyImport'>
      sage: 'EXAMPLES' in Bar.plot.__doc__                                            # needs sage.plot
      True
      sage: type(Foo.__dict__['plot'])                                                # needs sage.plot
      <... 'function'>

  Python

      >>> from sage.all import *
      >>> class Foo():
      ...     lazy_import('sage.plot.plot', 'plot')
      >>> class Bar(Foo):
      ...     pass
      >>> type(Foo.__dict__['plot'])
      <class 'sage.misc.lazy_import.LazyImport'>
      >>> 'EXAMPLES' in Bar.plot.__doc__                                            # needs sage.plot
      True
      >>> type(Foo.__dict__['plot'])                                                # needs sage.plot
      <... 'function'>

  If deprecated then a deprecation warning is issued:

  Sage

      sage: lazy_import('sage.rings.padics.factory', 'Qp', 'my_Qp',
      ....:             deprecation=14275)
      sage: my_Qp(5)                                                                  # needs sage.rings.padics
      doctest:...: DeprecationWarning:
      Importing my_Qp from here is deprecated;
      please use "from sage.rings.padics.factory import Qp as my_Qp" instead.
      See https://github.com/sagemath/sage/issues/14275 for details.
      5-adic Field with capped relative precision 20

  [Python]

      >>> from sage.all import *
      >>> lazy_import('sage.rings.padics.factory', 'Qp', 'my_Qp',
      ...             deprecation=Integer(14275))
      >>> my_Qp(Integer(5))                                                                  # needs sage.rings.padics
      doctest:...: DeprecationWarning:
      Importing my_Qp from here is deprecated;
      please use "from sage.rings.padics.factory import Qp as my_Qp" instead.
      See https://github.com/sagemath/sage/issues/14275 for details.
      5-adic Field with capped relative precision 20

  An example of deprecation with a message:

  Sage

      sage: lazy_import('sage.rings.padics.factory', 'Qp', 'my_Qp_msg',
      ....:             deprecation=(14275, "This is an example."))
      sage: my_Qp_msg(5)                                                              # needs sage.rings.padics
      doctest:...: DeprecationWarning: This is an example.
      See https://github.com/sagemath/sage/issues/14275 for details.
      5-adic Field with capped relative precision 20

  Python

      >>> from sage.all import *
      >>> lazy_import('sage.rings.padics.factory', 'Qp', 'my_Qp_msg',
      ...             deprecation=(Integer(14275), "This is an example."))
      >>> my_Qp_msg(Integer(5))                                                              # needs sage.rings.padics
      doctest:...: DeprecationWarning: This is an example.
      See https://github.com/sagemath/sage/issues/14275 for details.
      5-adic Field with capped relative precision 20

  An example of an import relying on a feature:

  Sage

      sage: from sage.features import PythonModule
      sage: lazy_import('ppl', 'equation',
      ....:             feature=PythonModule('ppl', spkg='pplpy', type='standard'))
      sage: equation                                                                  # needs pplpy
      <cyfunction equation at ...>
      sage: lazy_import('PyNormaliz', 'NmzListConeProperties',
      ....:             feature=PythonModule('PyNormaliz', spkg='pynormaliz'))
      sage: NmzListConeProperties                             # optional - pynormaliz
      <built-in function NmzListConeProperties>
      sage: lazy_import('foo', 'not_there',
      ....:             feature=PythonModule('foo', spkg='non-existing-package'))
      sage: not_there
      Failed lazy import:
      foo is not available.
      Importing not_there failed: No module named 'foo'...

  [Python]

      >>> from sage.all import *
      >>> from sage.features import PythonModule
      >>> lazy_import('ppl', 'equation',
      ...             feature=PythonModule('ppl', spkg='pplpy', type='standard'))
      >>> equation                                                                  # needs pplpy
      <cyfunction equation at ...>
      >>> lazy_import('PyNormaliz', 'NmzListConeProperties',
      ...             feature=PythonModule('PyNormaliz', spkg='pynormaliz'))
      >>> NmzListConeProperties                             # optional - pynormaliz
      <built-in function NmzListConeProperties>
      >>> lazy_import('foo', 'not_there',
      ...             feature=PythonModule('foo', spkg='non-existing-package'))
      >>> not_there
      Failed lazy import:
      foo is not available.
      Importing not_there failed: No module named 'foo'...

sage.misc.lazy_import.save_cache_file()

  Used to save the cached * import names.

sage.misc.lazy_import.test_fake_startup()

  For testing purposes only.

  Switch the startup lazy import guard back on.

  EXAMPLES:

  Sage

      sage: sage.misc.lazy_import.test_fake_startup()
      sage: lazy_import('sage.rings.integer_ring', 'ZZ', 'my_ZZ')
      sage: my_ZZ(123)
      doctest:warning...
      UserWarning: Resolving lazy import ZZ during startup
      123
      sage: sage.misc.lazy_import.finish_startup()

  Python

      >>> from sage.all import *
      >>> sage.misc.lazy_import.test_fake_startup()
      >>> lazy_import('sage.rings.integer_ring', 'ZZ', 'my_ZZ')
      >>> my_ZZ(Integer(123))
      doctest:warning...
      UserWarning: Resolving lazy import ZZ during startup
      123
      >>> sage.misc.lazy_import.finish_startup()