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
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~