On 05/17/2017 10:43 AM, Ivan Levkivskyi wrote:
On 17 May 2017 at 19:40, Juancarlo Añez wrote:
On Wed, May 17, 2017 at 12:48 PM, Ivan Levkivskyi wrote:

class Foo(NamedTuple):
     """Foo is a very important class and
     you should totally use it.
     """
     bar: int
     baz: int = 0

     def grand_total(self):
         return self.bar + self.baz

Really?!

I didn't know that idiom existed.

It is enough for many use cases, and I was just about to require
typing and pathlib on my 2.7-compatible projects.

Unfortunately, this works _only_ in Python 3.6+.

You might want to check out the NamedTuple class from my aenum [1] library -- it is metaclass based (no execing), supports defaults, doc-strings, and other fun and possibly useful things.

Here's the NamedTuple section from the docs:

Creating NamedTuples
--------------------

Simple
^^^^^^

The most common way to create a new NamedTuple will be via the functional API::

    >>> from aenum import NamedTuple
    >>> Book = NamedTuple('Book', 'title author genre', module=__name__)

This creates a ``NamedTuple`` called ``Book`` that will always contain three
items, each of which is also addressable as ``title``, ``author``, or ``genre``.

``Book`` instances can be created using positional or keyword argements or a
mixture of the two::

    >>> b1 = Book('Lord of the Rings', 'J.R.R. Tolkien', 'fantasy')
    >>> b2 = Book(title='Jhereg', author='Steven Brust', genre='fantasy')
    >>> b3 = Book('Empire', 'Orson Scott Card', genre='scifi')

If too few or too many arguments are used a ``TypeError`` will be raised::

    >>> b4 = Book('Hidden Empire')
    Traceback (most recent call last):
    ...
    TypeError: values not provided for field(s): author, genre
    >>> b5 = Book(genre='business')
    Traceback (most recent call last):
    ...
    TypeError: values not provided for field(s): title, author

As a ``class`` the above ``Book`` ``NamedTuple`` would look like::

    >>> class Book(NamedTuple):
    ...     title = 0
    ...     author = 1
    ...     genre = 2
    ...

For compatibility with the stdlib ``namedtuple``, NamedTuple also has the
``_asdict``, ``_make``, and ``_replace`` methods, and the ``_fields``
attribute, which all function similarly::

    >>> class Point(NamedTuple):
    ...     x = 0, 'horizontal coordinate', 1
    ...     y = 1, 'vertical coordinate', -1
    ...
    >>> class Color(NamedTuple):
    ...     r = 0, 'red component', 11
    ...     g = 1, 'green component', 29
    ...     b = 2, 'blue component', 37
    ...
    >>> Pixel = NamedTuple('Pixel', Point+Color, module=__name__)
    >>> pixel = Pixel(99, -101, 255, 128, 0)

    >>> pixel._asdict()
    OrderedDict([('x', 99), ('y', -101), ('r', 255), ('g', 128), ('b', 0)])

    >>> Point._make((4, 5))
    Point(x=4, y=5)

    >>> purple = Color(127, 0, 127)
    >>> mid_gray = purple._replace(g=127)
    >>> mid_gray
    Color(r=127, g=127, b=127)

    >>> pixel._fields
    ['x', 'y', 'r', 'g', 'b']

    >>> Pixel._fields
    ['x', 'y', 'r', 'g', 'b']


Advanced
^^^^^^^^

The simple method of creating ``NamedTuples`` requires always specifying all
possible arguments when creating instances; failure to do so will raise
exceptions::

    >>> class Point(NamedTuple):
    ...     x = 0
    ...     y = 1
    ...
    >>> Point()
    Traceback (most recent call last):
    ...
    TypeError: values not provided for field(s): x, y
    >>> Point(1)
    Traceback (most recent call last):
    ...
    TypeError: values not provided for field(s): y
    >>> Point(y=2)
    Traceback (most recent call last):
    ...
    TypeError: values not provided for field(s): x

However, it is possible to specify both docstrings and default values when
creating a ``NamedTuple`` using the class method::

    >>> class Point(NamedTuple):
    ...     x = 0, 'horizontal coordinate', 0
    ...     y = 1, 'vertical coordinate', 0
    ...
    >>> Point()
    Point(x=0, y=0)
    >>> Point(1)
    Point(x=1, y=0)
    >>> Point(y=2)
    Point(x=0, y=2)

It is also possible to create ``NamedTuples`` that only have named attributes
for certain fields; any fields without names can still be accessed by index::

    >>> class Person(NamedTuple):
    ...     fullname = 2
    ...     phone = 5
    ...
    >>> p = Person('Ethan', 'Furman', 'Ethan Furman',
    ...            'ethan at stoneleaf dot us',
    ...            'ethan.furman', '999.555.1212')
    >>> p
    Person('Ethan', 'Furman', 'Ethan Furman', 'ethan at stoneleaf dot us',
           'ethan.furman', '999.555.1212')
    >>> p.fullname
    'Ethan Furman'
    >>> p.phone
    '999.555.1212'
    >>> p[0]
    'Ethan'

In the above example the last named field was also the last field possible; in
those cases where you don't need to have the last possible field named, you can
provide a ``_size_`` of ``TupleSize.minimum`` to declare that more fields are
okay::

    >>> from aenum import TupleSize
    >>> class Person(NamedTuple):
    ...     _size_ = TupleSize.minimum
    ...     first = 0
    ...     last = 1
    ...

or, optionally if using Python 3::

    >>> class Person(NamedTuple, size=TupleSize.minimum):      # doctest: +SKIP
    ...     first = 0
    ...     last = 1

and in use::

    >>> Person('Ethan', 'Furman')
    Person(first='Ethan', last='Furman')

    >>> Person('Ethan', 'Furman', 'ethan.furman')
    Person('Ethan', 'Furman', 'ethan.furman')

    >>> Person('Ethan', 'Furman', 'ethan.furman', 'yay Python!')
    Person('Ethan', 'Furman', 'ethan.furman', 'yay Python!')

    >>> Person('Ethan')
    Traceback (most recent call last):
    ...
    TypeError: values not provided for field(s): last

Also, for those cases where even named fields may not be present, you can
specify ``TupleSize.variable``::

    >>> class Person(NamedTuple):
    ...     _size_ = TupleSize.variable
    ...     first = 0
    ...     last = 1
    ...

    >>> Person('Ethan')
    Person('Ethan')

    >>> Person(last='Furman')
    Traceback (most recent call last):
    ...
    TypeError: values not provided for field(s): first

Creating new ``NamedTuples`` from existing ``NamedTuples`` is simple::

    >>> Point = NamedTuple('Point', 'x y')
    >>> Color = NamedTuple('Color', 'r g b')
    >>> Pixel = NamedTuple('Pixel', Point+Color, module=__name__)
    >>> Pixel
    <NamedTuple 'Pixel'>

The existing fields in the bases classes are renumbered to fit the new class,
but keep their doc strings and default values.  If you use standard
subclassing::

    >>> Point = NamedTuple('Point', 'x y')
    >>> class Pixel(Point):
    ...     r = 2, 'red component', 11
    ...     g = 3, 'green component', 29
    ...     b = 4, 'blue component', 37
    ...
    >>> Pixel.__fields__
    ['x', 'y', 'r', 'g', 'b']

You must manage the numbering yourself.

--
~Ethan~
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to