[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