astro py:docs

- Index
- Modules

### Navigation

- Astropy v0.3 »
- Module code »

# Source code for astropy.constants.constant

    # Licensed under a 3-clause BSD style license - see LICENSE.rst
    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    from ..extern import six

    import functools
    import types
    import warnings

    from ..units.core import Unit, UnitsError
    from ..units.quantity import Quantity
    from ..utils import lazyproperty
    from ..utils.exceptions import AstropyUserWarning

    __all__ = ['Constant', 'EMConstant']

    class ConstantMeta(type):
        """Metaclass for the :class:`Constant`. The primary purpose of this is to
        wrap the double-underscore methods of :class:`Quantity` which is the
        superclass of :class:`Constant`.

        In particular this wraps the operator overloads such as `__add__` to
        prevent their use with constants such as ``e`` from being used in
        expressions without specifying a system.  The wrapper checks to see if the
        constant is listed (by name) in ``Constant._has_incompatible_units``, a set
        of those constants that are defined in different systems of units are
        physically incompatible.  It also performs this check on each `Constant` if
        it hasn't already been performed (the check is deferred until the
        `Constant` is actually used in an expression to speed up import times,
        among other reasons).
        """

        def __new__(mcls, name, bases, d):
            def wrap(meth):
                @functools.wraps(meth)
                def wrapper(self, *args, **kwargs):
                    name_lower = self.name.lower()
                    instances = Constant._registry[name_lower]
                    if not self._checked_units:
                        for inst in six.itervalues(instances):
                            try:
                                self.unit.to(inst.unit)
                            except UnitsError:
                                Constant._has_incompatible_units.add(name_lower)
                        self._checked_units = True

                    if (not self.system and
                            name_lower in Constant._has_incompatible_units):
                        systems = sorted([x for x in instances if x])
                        raise TypeError(
                            'Constant {0!r} does not have physically compatible '
                            'units across all systems of units and cannot be '
                            'combined with other values without specifying a '
                            'system (eg. {1}.{2})'.format(self.abbrev, self.abbrev,
                                                          systems[0]))

                    return meth(self, *args, **kwargs)

                return wrapper

            # The wrapper applies to so many of the __ methods that it's easier to
            # just exclude the ones it doesn't apply to
            exclude = set(['__new__', '__array_finalize__', '__array_wrap__',
                           '__dir__', '__getattr__', '__init__', '__str__',
                           '__repr__', '__hash__', '__iter__', '__getitem__',
                           '__len__', '__nonzero__'])
            for attr, value in list(six.iteritems(vars(Quantity))):
                if (isinstance(value, types.FunctionType) and
                        attr.startswith('__') and attr.endswith('__') and
                        attr not in exclude):
                    d[attr] = wrap(value)

            return super(ConstantMeta, mcls).__new__(mcls, name, bases, d)

    @six.add_metaclass(ConstantMeta)
    class Constant(Quantity):
        """A physical or astronomical constant.

        These objects are quantities that are meant to represent physical
        constants.
        """
        _registry = {}
        _has_incompatible_units = set()

        def __new__(cls, abbrev, name, value, unit, uncertainty, reference,
                    system=None):
            name_lower = name.lower()
            instances = Constant._registry.setdefault(name_lower, {})
            if system in instances:
                warnings.warn('Constant {0!r} is already has a definition in the '
                              '{1!r} system'.format(name, system), AstropyUserWarning)

            inst = super(Constant, cls).__new__(cls, value)

            for c in six.itervalues(instances):
                if system is not None and not hasattr(c.__class__, system):
                    setattr(c, system, inst)
                if c.system is not None and not hasattr(inst.__class__, c.system):
                    setattr(inst, c.system, c)

            instances[system] = inst

            return inst

        def __init__(self, abbrev, name, value, unit, uncertainty, reference,
                     system=None):
            self._abbrev = abbrev
            self._name = name
            self._value = value
            self._unit = unit
            self._uncertainty = uncertainty
            self._reference = reference
            self._system = system

            self._checked_units = False

        def __repr__(self):
            return ('<Constant name={0!r} value={1} error={2} units={3!r} '
                    'reference={4!r}>'.format(self.name, self.value,
                                              self.uncertainty, str(self.unit),
                                              self.reference))

        def __str__(self):
            return ('  Name   = {0}\n'
                    '  Value  = {1}\n'
                    '  Error  = {2}\n'
                    '  Units  = {3}\n'
                    '  Reference = {4}'.format(self.name, self.value,
                                               self.uncertainty, self.unit,
                                               self.reference))

        @property
        def abbrev(self):
            """A typical ASCII text abbreviation of the constant, also generally
            the same as the Python variable used for this constant.
            """

            return self._abbrev

        @property
        def name(self):
            """The full name of the constant."""

            return self._name

        @lazyproperty
        def unit(self):
            """The unit(s) in which this constant is defined."""

            return Unit(self._unit)

        @property
        def uncertainty(self):
            """The known uncertainty in this constant's value."""

            return self._uncertainty

        @property
        def reference(self):
            """The source used for the value of this constant."""

            return self._reference

        @property
        def system(self):
            """The system of units in which this constant is defined (typically
            `None` so long as the constant's units can be directly converted
            between systems).
            """

            return self._system

        @property
        def si(self):
            """If the Constant is defined in the SI system return that instance of
            the constant, else convert to a Quantity in the appropriate SI units.
            """

            instances = Constant._registry[self.name.lower()]
            return instances.get('si') or super(Constant, self).si

        @property
        def cgs(self):
            """If the Constant is defined in the CGS system return that instance of
            the constant, else convert to a Quantity in the appropriate CGS units.
            """

            instances = Constant._registry[self.name.lower()]
            return instances.get('cgs') or super(Constant, self).cgs

    class EMConstant(Constant):
        """An electromagnetic constant."""

        @property
        def cgs(self):
            """Overridden for EMConstant to raise a `TypeError` emphasizing that
            there are multiple EM extensions to CGS.
            """

            raise TypeError("Cannot convert EM constants to cgs because there "
                            "are different systems for E.M constants within the "
                            "c.g.s system (ESU, Gaussian, etc.). Instead, "
                            "directly use the constant with the appropriate "
                            "suffix (e.g. e.esu, e.gauss, etc.).")