Skip to content

Commit

Permalink
Document that Either should not be used in new code (#1699)
Browse files Browse the repository at this point in the history
This PR adds notes to the user manual and API documentation to discourage users from using Either in new code. (Union should be preferred.)
  • Loading branch information
mdickinson committed Aug 10, 2022
1 parent 11c6a87 commit c5cfdde
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 56 deletions.
5 changes: 3 additions & 2 deletions docs/source/traits_api_reference/trait_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,11 @@ Traits
.. autoclass:: ToolbarButton
:show-inheritance:

.. autoclass:: Either
.. autoclass:: Union
:show-inheritance:

.. autoclass:: Union
.. autoclass:: Either
:show-inheritance:

.. autoclass:: Symbol
:show-inheritance:
Expand Down
114 changes: 61 additions & 53 deletions docs/source/traits_user_manual/defining.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pass it as an argument to the trait::

account_balance = Float(10.0)

Most predefined traits are callable, [2]_ and can accept a default value and
Most predefined traits are callable [1]_, and can accept a default value and
possibly other arguments; all that are callable can also accept metadata as
keyword arguments. (See :ref:`other-predefined-traits` for information on trait
signatures, and see :ref:`trait-metadata` for information on metadata
Expand Down Expand Up @@ -269,7 +269,7 @@ the table.
.. index:: PythonValue(), Range(), ReadOnly(), Regex()
.. index:: Set() String(), This, Time()
.. index:: ToolbarButton(), true, Tuple(), Type()
.. index:: undefined, UUID(), ValidatedTuple(), WeakRef()
.. index:: undefined, Union(), UUID(), ValidatedTuple(), WeakRef()

.. _predefined-traits-beyond-simple-types-table:

Expand Down Expand Up @@ -321,7 +321,7 @@ the table.
+------------------+----------------------------------------------------------+
| Disallow | n/a |
+------------------+----------------------------------------------------------+
| Either | Either( *val1*\ [, *val2*, ..., *valN*, |
| Either [2]_ | Either( *val1*\ [, *val2*, ..., *valN*, |
| | \*\*\ *metadata*] ) |
+------------------+----------------------------------------------------------+
| Enum | Enum( *values*\ [, \*\*\ *metadata*] ) |
Expand Down Expand Up @@ -408,7 +408,7 @@ the table.
| Union | Union( *val1*\ [, *val2*, ..., *valN*, |
| | \*\*\ *metadata*] ) |
+------------------+----------------------------------------------------------+
| UUID [4]_ | UUID( [\*\*\ *metadata*] ) |
| UUID | UUID( [\*\*\ *metadata*] ) |
+------------------+----------------------------------------------------------+
| ValidatedTuple | ValidatedTuple( [\*\ *traits*, *fvalidate* = None, |
| | *fvalidate_info* = '' , \*\*\ *metadata*] ) |
Expand Down Expand Up @@ -638,56 +638,12 @@ prefix. Instantiating the class produces the following::
>>> print(bob.married)
no

.. index:: Either trait

.. _either:

Either
::::::
Another predefined trait that merits special explanation is Either. The
Either trait is intended for attributes that may take a value of more than
a single trait type, including None. The default value of Either is None, even
if None is not one of the types the user explicitly defines in the constructor,
but a different default value can be provided using the ``default`` argument.

.. index::
pair: Either trait; examples

The following is an example of using Either::

# either.py --- Example of Either predefined trait

from traits.api import HasTraits, Either, Str

class Employee(HasTraits):
manager_name = Either(Str, None)

This example defines an Employee class, which has a **manager_name** trait
attribute, which accepts either an Str instance or None as its value, and
will raise a TraitError if a value of any other type is assigned. For example::

>>> from traits.api import HasTraits, Either, Str
>>> class Employee(HasTraits):
... manager_name = Either(Str, None)
...
>>> steven = Employee(manager_name="Jenni")
>>> # Here steven's manager is named "Jenni"
>>> steven.manager_name
'Jenni'
>>> eric = Employee(manager_name=None)
>>> # Eric is the boss, so he has no manager.
>>> eric.manager_name is None
True
>>> # Assigning a value that is neither a string nor None will fail.
>>> steven.manager_name = 5
traits.trait_errors.TraitError: The 'manager_name' trait of an Employee instance must be a string or None, but a value of 5 <type 'int'> was specified.

.. index:: Union trait

.. _union:

Union
::::::
:::::
The Union trait accepts a value that is considered valid by at least one
of the traits in its definition. It is a simpler and therefore less error-prone
alternative to the `Either` trait, which allows more complex constructs and
Expand Down Expand Up @@ -719,7 +675,7 @@ attribute, which accepts either an Str instance or None as its value, a
**salary** trait that accepts an instance of Salary or Float and will raise a
TraitError if a value of any other type is assigned. For example::

>>> from traits.api import HasTraits, Either, Str
>>> from traits.api import HasTraits, Str, Union
>>> class Employee(HasTraits):
... manager_name = Union(Str, None)
...
Expand All @@ -731,7 +687,7 @@ TraitError if a value of any other type is assigned. For example::

The following example illustrates the difference between `Either` and `Union`::

>>> from traits.api import HasTraits, Either, Union, Str
>>> from traits.api import Either, HasTraits, Str, Union
>>> class IntegerClass(HasTraits):
... primes = Either([2], None, {'3':6}, 5, 7, 11)
...
Expand All @@ -744,6 +700,56 @@ The following example illustrates the difference between `Either` and `Union`::
... primes = Union([2], None, {'3':6}, 5, 7, 11)
ValueError: Union trait declaration expects a trait type or an instance of trait type or None, but got [2] instead

.. index:: Either trait

.. _either:

Either
::::::

.. note::
The :class:`~.Either` trait type may eventually be deprecated, and should
not be used in new code. Use the more well-behaved :class:`~.Union` trait
type instead.

Another predefined trait that merits special explanation is Either. The
Either trait is intended for attributes that may take a value of more than
a single trait type, including None. The default value of Either is None, even
if None is not one of the types the user explicitly defines in the constructor,
but a different default value can be provided using the ``default`` argument.

.. index::
pair: Either trait; examples

The following is an example of using Either::

# either.py --- Example of Either predefined trait

from traits.api import HasTraits, Either, Str

class Employee(HasTraits):
manager_name = Either(Str, None)

This example defines an Employee class, which has a **manager_name** trait
attribute, which accepts either an Str instance or None as its value, and
will raise a TraitError if a value of any other type is assigned. For example::

>>> from traits.api import HasTraits, Either, Str
>>> class Employee(HasTraits):
... manager_name = Either(Str, None)
...
>>> steven = Employee(manager_name="Jenni")
>>> # Here steven's manager is named "Jenni"
>>> steven.manager_name
'Jenni'
>>> eric = Employee(manager_name=None)
>>> # Eric is the boss, so he has no manager.
>>> eric.manager_name is None
True
>>> # Assigning a value that is neither a string nor None will fail.
>>> steven.manager_name = 5
traits.trait_errors.TraitError: The 'manager_name' trait of an Employee instance must be a string or None, but a value of 5 <type 'int'> was specified.


.. _migration_either_to_union:

Expand Down Expand Up @@ -1041,14 +1047,16 @@ the metadata attribute::
print(t.trait( 'any' ).is_trait_type( Str )) # False

.. rubric:: Footnotes
.. [2] Most callable predefined traits are classes, but a few are functions.
.. [1] Most callable predefined traits are classes, but a few are functions.
The distinction does not make a difference unless you are trying to
extend an existing predefined trait. See the *Traits API Reference* for
details on particular traits, and see Chapter 5 for details on extending
existing traits.
.. [2] The :class:`~.Either` trait type is likely to be deprecated at some
point in the future. The :class:`~.Union` trait type should be preferred
to :class:`~.Either` in new code.
.. [3] The Function and Method trait types are now deprecated. See |Function|,
|Method|
.. [4] Available in Python 2.5.
..
external urls
Expand Down
2 changes: 1 addition & 1 deletion traits/trait_numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ class ArrayOrNone(CArray):
""" A coercing trait whose value may be either a NumPy array or None.
This trait is designed to avoid the comparison issues with numpy arrays
that can arise from the use of constructs like Either(None, Array).
that can arise from the use of constructs like Union(None, Array).
The default value is None.
"""
Expand Down
7 changes: 7 additions & 0 deletions traits/trait_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4124,6 +4124,13 @@ def __init__(
class Either(TraitType):
""" A trait type whose value can be any of of a specified list of traits.
.. note::
This class has some unusual corner-case behaviours and is not
recommended for use in new code. It may eventually be deprecated and
removed. For new code, consider using the :class:`~.Union` trait type
instead.
Parameters
----------
*traits
Expand Down

0 comments on commit c5cfdde

Please sign in to comment.