[Python-ideas] [semi-OT] NamedTuple from aenum library [was: JavaScript-Style Object Creation in Python (using a constructor function instead of a class to create objects)]

Ethan Furman ethan at stoneleaf.us
Wed May 17 17:06:51 EDT 2017


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~


More information about the Python-ideas mailing list