Null values and sentinels like, but not, None
Project description
| |travisci| |version| |downloads| |versions| |impls| |wheel| |coveage|
.. |travisci| image:: https://api.travis-ci.org/jonathaneunice/nulltype.svg
:target: http://travis-ci.org/jonathaneunice/nulltype
.. |version| image:: http://img.shields.io/pypi/v/nulltype.svg?style=flat
:alt: PyPI Package latest release
:target: https://pypi.python.org/pypi/nulltype
.. |downloads| image:: http://img.shields.io/pypi/dm/nulltype.svg?style=flat
:alt: PyPI Package monthly downloads
:target: https://pypi.python.org/pypi/nulltype
.. |versions| image:: https://img.shields.io/pypi/pyversions/nulltype.svg
:alt: Supported versions
:target: https://pypi.python.org/pypi/nulltype
.. |impls| image:: https://img.shields.io/pypi/implementation/nulltype.svg
:alt: Supported implementations
:target: https://pypi.python.org/pypi/nulltype
.. |wheel| image:: https://img.shields.io/pypi/wheel/nulltype.svg
:alt: Wheel packaging support
:target: https://pypi.python.org/pypi/nulltype
.. |coverage| image:: https://img.shields.io/badge/test_coverage-100%25-6600CC.svg
:alt: Test line coverage
:target: https://pypi.python.org/pypi/nulltype
Helps define 'null' values and sentinels parallel to, but different from, ``None``.
``None`` is a great `sentinel value <http://en.wikipedia.org/wiki/Sentinel_value>`_
and a classic implementation of the
`null object pattern <http://en.wikipedia.org/wiki/Null_Object_pattern>`_.
But there are times that you need more than one nullish value to
represent different aspects of emptiness. "Nothing there" is
logically different from "undefined," "prohibited,"
"end of data" and other kinds of null.
The core function of ``nulltype``
is representing emptiness and falsity in a way that doesn't overload ``None``
(or ``False``, ``0``, ``{}``, ``[]``, ``""``, or any of the other possible
"there's nothing here!" values).
It helps create designated identifiers with specific meanings
such as ``Passthrough``, ``Prohibited``, and ``Undefined``.
Usage
=====
::
from nulltype import NullType
Empty = NullType('Empty')
# following just to show it's working
assert bool(Empty) == False
assert len(Empty) == 0
assert list(Empty) == []
assert Empty.some_attribute is Empty
assert Empty[22] is Nothing
assert Empty("hey", 12) is Empty
You can create as many custom ``NullType``
values as you like. For your convenience, two default
values, ``Null`` and ``Nothing``, are exported. That way,
if you don't really want to create your own, you can
import a pre-constituted null value, such as::
from nulltype import Nothing
Dereferencing
=============
Alternate null types can be particularly useful when parsing
data or traversing data structures which might or might not be
present. This is common in dealing with the data returned by
`REST <http://en.wikipedia.org/wiki/Representational_state_transfer>`_
APIs, for instance.
As one example, `the documentation for Google's Gmail API <https://developers.google.com/gmail/api/quickstart/quickstart-python>`_
suggests the following code::
threads = gmail_service.users().threads().list(userId='me').execute()
if threads['threads']:
for thread in threads['threads']:
print 'Thread ID: %s' % (thread['id'])
There is a lot going on there just to avoid a problematic deference.
If instead you have a ``Nothing`` null type defined, the code is
shorter (and avoids an extra, very transient variable)::
results = gmail_service.users().threads().list(userId='me').execute()
for thread in results.get('threads', Nothing):
print 'Thread ID: %s' % (thread['id'])
Three lines versus four may not seem like a big advantage, but the value
increases with the complexity of the task. Many such "if it's there, then..."
constructs are deeply nested when dealing with API results, XML parse trees,
and other fundamentally nested information sources. Saving a guard condition
on every one of the nesting levels adds up quickly.
While you could almost do this in stock Python, unlike ``Nothing``, ``None``
is not iterable. You might use an empty list ``[]`` (or an equivalent global
such as ``EMPTYLIST``) as the alternative value for the
``get`` method.
Going by the documentation of many parsers and
APIs, however, such uses aren't
broadly idiomatic in today's Python community.
The ``EMPTYLIST`` approach also is very specific to routines
returning lists, whereas the "go ahead, get it if you can" ``nulltype``
model works well for longer chains of access::
results.get("payload", Nothing).get("headers", Nothing)
will return the correct object if it's there, but ``Nothing`` otherwise.
And if you then try to test it (e.g. with ``if`` or a logical expression)
or iterate over it (e.g. with ``for``), it will act as though it's an empty
list, or ``False``--whatever is most useful in a given context. Whether you're
iterating, indexing, dereferencing, calling, or otherwise accessing it, a
``NullType`` is unperturbed.
``Nothing`` isn't nothing. It's something that will simplify your code.
General Sentinels and Distinguished Values
==========================================
While ``nulltype`` is frequently used to define new kinds of "empty" values,
it's actually more general. Beyond different forms of 'null', ``NullType``
instances are good general-purpose sentinels or designated values. Instead of
the old::
class MySentinel(object):
pass
Use::
MySentinel = NullType('MySentinel')
That gives you a value with known truthiness properties and a nicer
printed representation.
On the off chance you want a sentinel value that is
`truthy <https://en.wikipedia.org/wiki/Truthiness>`_ rather than falsey /
empty, use ``NonNullType``, a companion to ``NullType`` that operates in
almost the exact same way, but that evaluates as true.::
from nulltype import NonNullType
Full = NonNullType('Full')
assert bool(Full) is True
assert len(Full) == 1
assert list(Full) == [Full]
assert Full.some_attribute is Full
assert Full[22] is Full
assert Full("hey", 12) is Full
Experience suggests that nullish sentinels are generally adequate and
preferable. And the "everything folds back to the same value" nature of
even ``NonNullType`` gives a somewhat null-like, or at least
non-reactive, nature. But if you do want a true-ish sentinel, there it is.
Uniqueness
==========
``NullType`` instances are meant to be
`singletons <http://en.wikipedia.org/wiki/Singleton_pattern>`_, with just one per
program. They almost are, though technically multiple ``NullType`` instances are
reasonable, making it more of a `multiton
pattern <http://en.wikipedia.org/wiki/Multiton_pattern>`_.
The uniqueness of each singleton is currently not enforced, making it a usage
convention rather than strict law. With even minimal care, this is a problem
roughly 0% of the time.
Notes
=====
* Version 2.2 inaugurates automated code coverage metrics. Test line coverage
100%. *Hooah!*
* Version 2.1.4 adds wheel packaging.
* Version 2.1.2 switches from BSD to Apache License 2.0 and integrates
``tox`` testing with ``setup.py``, and updates testing
with Travis CI and pyroma.
* Version 2.1 adds ``NonNullType``, an alternative for truthy sentinels.
(Most use cases should still use ``NullType``; "full" sentinels recommended
for odd cases only.)
* Version 2.0 starts major upgrade from just Boolean operations being nulled
to essentially all sorts of accesses and updates being nulled. It defines two
default ``NullType`` instances, ``Null`` and ``Nothing``. The ability
to have anonymous (unnamed) nulls has been removed as superfluous.
* Automated multi-version testing managed with `pytest
<http://pypi.python.org/pypi/pytest>`_, `pytest-cov
<http://pypi.python.org/pypi/pytest-cov>`_,
`coverage <https://pypi.python.org/pypi/coverage/4.0b1>`_
and `tox
<http://pypi.python.org/pypi/tox>`_. Continuous integration testing
with `Travis-CI <https://travis-ci.org/jonathaneunice/nulltype>`_.
Packaging linting with `pyroma <https://pypi.python.org/pypi/pyroma>`_.
Successfully packaged for, and
tested against, all late-model versions of Python: 2.6, 2.7, 3.2, 3.3,
3.4, and 3.5 pre-release (3.5.0b3) as well as PyPy 2.6.0 (based on
2.7.9) and PyPy3 2.4.0 (based on 3.2.5).
* Similar modules include `sentinels <http://pypi.python.org/pypi/sentinels>`_ and `null
<http://pypi.python.org/pypi/null>`_. Of these, I prefer ``sentinels`` because it
is clearly Python 3 ready, includes a ``pickle`` mechanism.
* The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
`@jeunice on Twitter <http://twitter.com/jeunice>`_
welcomes your comments and suggestions.
Installation
============
To install or upgrade to the latest version::
pip install -U nulltype
To ``easy_install`` under a specific Python version (3.3 in this example)::
python3.3 -m easy_install nulltype
(You may need to prefix these with ``sudo`` to authorize installation. In
environments without super-user privileges, you may want to use ``pip``'s
``--user`` option, to install only for a single user, rather than
system-wide.)
.. |travisci| image:: https://api.travis-ci.org/jonathaneunice/nulltype.svg
:target: http://travis-ci.org/jonathaneunice/nulltype
.. |version| image:: http://img.shields.io/pypi/v/nulltype.svg?style=flat
:alt: PyPI Package latest release
:target: https://pypi.python.org/pypi/nulltype
.. |downloads| image:: http://img.shields.io/pypi/dm/nulltype.svg?style=flat
:alt: PyPI Package monthly downloads
:target: https://pypi.python.org/pypi/nulltype
.. |versions| image:: https://img.shields.io/pypi/pyversions/nulltype.svg
:alt: Supported versions
:target: https://pypi.python.org/pypi/nulltype
.. |impls| image:: https://img.shields.io/pypi/implementation/nulltype.svg
:alt: Supported implementations
:target: https://pypi.python.org/pypi/nulltype
.. |wheel| image:: https://img.shields.io/pypi/wheel/nulltype.svg
:alt: Wheel packaging support
:target: https://pypi.python.org/pypi/nulltype
.. |coverage| image:: https://img.shields.io/badge/test_coverage-100%25-6600CC.svg
:alt: Test line coverage
:target: https://pypi.python.org/pypi/nulltype
Helps define 'null' values and sentinels parallel to, but different from, ``None``.
``None`` is a great `sentinel value <http://en.wikipedia.org/wiki/Sentinel_value>`_
and a classic implementation of the
`null object pattern <http://en.wikipedia.org/wiki/Null_Object_pattern>`_.
But there are times that you need more than one nullish value to
represent different aspects of emptiness. "Nothing there" is
logically different from "undefined," "prohibited,"
"end of data" and other kinds of null.
The core function of ``nulltype``
is representing emptiness and falsity in a way that doesn't overload ``None``
(or ``False``, ``0``, ``{}``, ``[]``, ``""``, or any of the other possible
"there's nothing here!" values).
It helps create designated identifiers with specific meanings
such as ``Passthrough``, ``Prohibited``, and ``Undefined``.
Usage
=====
::
from nulltype import NullType
Empty = NullType('Empty')
# following just to show it's working
assert bool(Empty) == False
assert len(Empty) == 0
assert list(Empty) == []
assert Empty.some_attribute is Empty
assert Empty[22] is Nothing
assert Empty("hey", 12) is Empty
You can create as many custom ``NullType``
values as you like. For your convenience, two default
values, ``Null`` and ``Nothing``, are exported. That way,
if you don't really want to create your own, you can
import a pre-constituted null value, such as::
from nulltype import Nothing
Dereferencing
=============
Alternate null types can be particularly useful when parsing
data or traversing data structures which might or might not be
present. This is common in dealing with the data returned by
`REST <http://en.wikipedia.org/wiki/Representational_state_transfer>`_
APIs, for instance.
As one example, `the documentation for Google's Gmail API <https://developers.google.com/gmail/api/quickstart/quickstart-python>`_
suggests the following code::
threads = gmail_service.users().threads().list(userId='me').execute()
if threads['threads']:
for thread in threads['threads']:
print 'Thread ID: %s' % (thread['id'])
There is a lot going on there just to avoid a problematic deference.
If instead you have a ``Nothing`` null type defined, the code is
shorter (and avoids an extra, very transient variable)::
results = gmail_service.users().threads().list(userId='me').execute()
for thread in results.get('threads', Nothing):
print 'Thread ID: %s' % (thread['id'])
Three lines versus four may not seem like a big advantage, but the value
increases with the complexity of the task. Many such "if it's there, then..."
constructs are deeply nested when dealing with API results, XML parse trees,
and other fundamentally nested information sources. Saving a guard condition
on every one of the nesting levels adds up quickly.
While you could almost do this in stock Python, unlike ``Nothing``, ``None``
is not iterable. You might use an empty list ``[]`` (or an equivalent global
such as ``EMPTYLIST``) as the alternative value for the
``get`` method.
Going by the documentation of many parsers and
APIs, however, such uses aren't
broadly idiomatic in today's Python community.
The ``EMPTYLIST`` approach also is very specific to routines
returning lists, whereas the "go ahead, get it if you can" ``nulltype``
model works well for longer chains of access::
results.get("payload", Nothing).get("headers", Nothing)
will return the correct object if it's there, but ``Nothing`` otherwise.
And if you then try to test it (e.g. with ``if`` or a logical expression)
or iterate over it (e.g. with ``for``), it will act as though it's an empty
list, or ``False``--whatever is most useful in a given context. Whether you're
iterating, indexing, dereferencing, calling, or otherwise accessing it, a
``NullType`` is unperturbed.
``Nothing`` isn't nothing. It's something that will simplify your code.
General Sentinels and Distinguished Values
==========================================
While ``nulltype`` is frequently used to define new kinds of "empty" values,
it's actually more general. Beyond different forms of 'null', ``NullType``
instances are good general-purpose sentinels or designated values. Instead of
the old::
class MySentinel(object):
pass
Use::
MySentinel = NullType('MySentinel')
That gives you a value with known truthiness properties and a nicer
printed representation.
On the off chance you want a sentinel value that is
`truthy <https://en.wikipedia.org/wiki/Truthiness>`_ rather than falsey /
empty, use ``NonNullType``, a companion to ``NullType`` that operates in
almost the exact same way, but that evaluates as true.::
from nulltype import NonNullType
Full = NonNullType('Full')
assert bool(Full) is True
assert len(Full) == 1
assert list(Full) == [Full]
assert Full.some_attribute is Full
assert Full[22] is Full
assert Full("hey", 12) is Full
Experience suggests that nullish sentinels are generally adequate and
preferable. And the "everything folds back to the same value" nature of
even ``NonNullType`` gives a somewhat null-like, or at least
non-reactive, nature. But if you do want a true-ish sentinel, there it is.
Uniqueness
==========
``NullType`` instances are meant to be
`singletons <http://en.wikipedia.org/wiki/Singleton_pattern>`_, with just one per
program. They almost are, though technically multiple ``NullType`` instances are
reasonable, making it more of a `multiton
pattern <http://en.wikipedia.org/wiki/Multiton_pattern>`_.
The uniqueness of each singleton is currently not enforced, making it a usage
convention rather than strict law. With even minimal care, this is a problem
roughly 0% of the time.
Notes
=====
* Version 2.2 inaugurates automated code coverage metrics. Test line coverage
100%. *Hooah!*
* Version 2.1.4 adds wheel packaging.
* Version 2.1.2 switches from BSD to Apache License 2.0 and integrates
``tox`` testing with ``setup.py``, and updates testing
with Travis CI and pyroma.
* Version 2.1 adds ``NonNullType``, an alternative for truthy sentinels.
(Most use cases should still use ``NullType``; "full" sentinels recommended
for odd cases only.)
* Version 2.0 starts major upgrade from just Boolean operations being nulled
to essentially all sorts of accesses and updates being nulled. It defines two
default ``NullType`` instances, ``Null`` and ``Nothing``. The ability
to have anonymous (unnamed) nulls has been removed as superfluous.
* Automated multi-version testing managed with `pytest
<http://pypi.python.org/pypi/pytest>`_, `pytest-cov
<http://pypi.python.org/pypi/pytest-cov>`_,
`coverage <https://pypi.python.org/pypi/coverage/4.0b1>`_
and `tox
<http://pypi.python.org/pypi/tox>`_. Continuous integration testing
with `Travis-CI <https://travis-ci.org/jonathaneunice/nulltype>`_.
Packaging linting with `pyroma <https://pypi.python.org/pypi/pyroma>`_.
Successfully packaged for, and
tested against, all late-model versions of Python: 2.6, 2.7, 3.2, 3.3,
3.4, and 3.5 pre-release (3.5.0b3) as well as PyPy 2.6.0 (based on
2.7.9) and PyPy3 2.4.0 (based on 3.2.5).
* Similar modules include `sentinels <http://pypi.python.org/pypi/sentinels>`_ and `null
<http://pypi.python.org/pypi/null>`_. Of these, I prefer ``sentinels`` because it
is clearly Python 3 ready, includes a ``pickle`` mechanism.
* The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
`@jeunice on Twitter <http://twitter.com/jeunice>`_
welcomes your comments and suggestions.
Installation
============
To install or upgrade to the latest version::
pip install -U nulltype
To ``easy_install`` under a specific Python version (3.3 in this example)::
python3.3 -m easy_install nulltype
(You may need to prefix these with ``sudo`` to authorize installation. In
environments without super-user privileges, you may want to use ``pip``'s
``--user`` option, to install only for a single user, rather than
system-wide.)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
nulltype-2.2.0.zip
(18.3 kB
view details)
nulltype-2.2.0.tar.gz
(8.2 kB
view details)
Built Distribution
File details
Details for the file nulltype-2.2.0.zip
.
File metadata
- Download URL: nulltype-2.2.0.zip
- Upload date:
- Size: 18.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1b8023f4748fac555d93ed5a62eb91d6e12df8ebfdb0e845d11450b1687d5ca2 |
|
MD5 | 4b642cdc30bade008cc472754385f22f |
|
BLAKE2b-256 | d53abcb31bea9c992b0a70caf5cd8961ade6e0b639f8179ba184501518a14633 |
File details
Details for the file nulltype-2.2.0.tar.gz
.
File metadata
- Download URL: nulltype-2.2.0.tar.gz
- Upload date:
- Size: 8.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 68672dbb712cc7e0d384b510e9a21667855bc7a124147d45a355de169c1b7b0f |
|
MD5 | a7f7cd4653eb642a9e3a969ab68e86b1 |
|
BLAKE2b-256 | 67ec850ce63595fddd0e2d7789e3318211039a896eee0863c5b022a421bf8cd4 |
File details
Details for the file nulltype-2.2.0-py2.py3-none-any.whl
.
File metadata
- Download URL: nulltype-2.2.0-py2.py3-none-any.whl
- Upload date:
- Size: 10.7 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | fc65fac11c5e82ddbf2f462f0c517be31424c5b1ca19c60b4c3f679a33dfd2d7 |
|
MD5 | 8cf441f4f2bfee3f283bc2e882af9ab6 |
|
BLAKE2b-256 | b8a72b333e57767cfdab28593ac187a15c27b3cff16968cc42d2fa322d8838df |