Docstrings for namedtuple

What interface is better for specifying namedtuple field docstrings? Point = namedtuple('Point', 'x y', doc='Point: 2-dimensional coordinate', field_docs=['abscissa', 'ordinate']) or Point = namedtuple('Point', [('x', 'absciss'), ('y', 'ordinate')], doc='Point: 2-dimensional coordinate') ? http://bugs.python.org/issue16669

On 12.12.12 21:35, Antoine Pitrou wrote:
field_docs={'x': 'abscissa', 'y': 'ordinate'} perhaps?
This will force repeat the field names twice. If we have such docs_dict, we can use it as: field_names = ['x', 'y'] Point = namedtuple('Point', field_names, field_docs=list(map(docs_dict.get, field_names))) or as Point = namedtuple('Point', [(f, docs_dict.get(f)) for f in field_names]) In case of ordered dict it can be even simpler: Point = namedtuple('Point', ordered_dict.keys(), field_docs=list(ordered_dict.values())) or Point = namedtuple('Point', ordered_dict.items())

On 12.12.2012 18:40, Serhiy Storchaka wrote:
IMO, attributes should be documented in the existing doc parameter, not separately. This makes the intention clear and the code overall more readable. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Dec 12 2012)
2012-12-05: Released eGenix pyOpenSSL 0.13 ... http://egenix.com/go37 2012-11-28: Released eGenix mx Base 3.2.5 ... http://egenix.com/go36 2013-01-22: Python Meeting Duesseldorf ... 41 days to go ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/

On 12.12.12 21:56, M.-A. Lemburg wrote:
Sorry, I didn't understand what you mean. There is no doc parameter for namedtuple yet. For overloading class docstring we can use inheritance idiom. But there is no way to change field docstring. All field docstrings generated using template 'Alias for field number {index:d}'.

On 12.12.2012 21:12, Serhiy Storchaka wrote:
Ah, sorry. Please scratch the "existing" in my reply :-) +1 on a doc parameter on namedtuple() - property() already has such a parameter, which is probably why I got confused. -0 on having separate doc strings for the fields. Their meaning will usually be clear from the main doc string.
For overloading class docstring we can use inheritance idiom. But there is no way to change field docstring. All field docstrings generated using template 'Alias for field number {index:d}'.
Yes, I've seen that: http://docs.python.org/2/library/collections.html?highlight=namedtuple#colle... It may not be too helpful, but it's an accurate description of the field's purpose :-) -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Dec 12 2012)
2012-12-05: Released eGenix pyOpenSSL 0.13 ... http://egenix.com/go37 2012-11-28: Released eGenix mx Base 3.2.5 ... http://egenix.com/go36 2013-01-22: Python Meeting Duesseldorf ... 41 days to go ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/

On Wed, Dec 12, 2012 at 9:40 AM, Serhiy Storchaka <storchaka@gmail.com>wrote:
This may be a good time to say that personally I always disliked namedtuple's creation syntax. It is unpleasant in two respects: 1. You have to repeat the name 2. You have to specify the fields in a space-separated string I wish there was an alternative of something like: @namedtuple class Point: x = 0 y = 0 Eli

Err, can class bodies ever be order-sensitive? I was under the impression names bound there work just like names bound anywhere... Unless of course that magical decorator is secretly an AST hack, in which case, yes, it can do whatever it wants :) On Sun, Dec 16, 2012 at 3:06 PM, Joao S. O. Bueno <jsbueno@python.org.br>wrote:
-- cheers lvh

Yep. You just have to define a metaclass with a __prepare__() that returns an OrderedDict (or similar). http://docs.python.org/3.4/reference/datamodel.html#preparing-the-class-name... http://docs.python.org/2/library/collections.html#ordereddict-objects Cheers, Chris -- http://rebertia.com

On 12/16/2012 8:22 AM, Eli Bendersky wrote:
Pretty easy, once one figures out metaclass basics. import collections as co class ntmeta(): def __prepare__(name, bases, **kwds): return co.OrderedDict() def __new__(cls, name, bases, namespace): print(namespace) # shows why filter is needed return co.namedtuple(name, filter(lambda s: s[0] != '_', namespace)) class Point(metaclass=ntmeta): x = 0 y = 0 p = Point(1,2) print(p) # OrderedDict([('__module__', '__main__'), ('__qualname__', 'Point'), ('x', 0), ('y', 0)]) Point(x=1, y=2) To use the filtered namespace values as defaults (Antoine's suggestion), first replace namedtuple() with its body. Then modify the header of generated name.__new__. For Point, change def __new__(_cls, x, y): #to def __new__(_cls, x=0, y=0): Also change the newclass docstring. For Point, change 'Point(x, y)' to 'Point(x=0, y=0)' -- Terry Jan Reedy

It can be made a bit more intelligent. I haven't done anything with docstrings here, but it wouldn't be hard to add. This automatically handles defaults (you can call the namedtuple with either zero parameters or the exact number). You can specify __rename__ = True, which will then only exclude __dunder_names__ (otherwise all names starting with an underscore are excluded). You can also pass verbose=[True|False] to the subclass constructor. import collections class NamedTupleMetaClass(type): # The prepare function @classmethod def __prepare__(metacls, name, bases): # No keywords in this case return collections.OrderedDict() # The metaclass invocation def __new__(cls, name, bases, classdict): fields = collections.OrderedDict() rename = False verbose = False for f in classdict: if f == '__rename__': rename = classdict[f] elif f == '__verbose__': verbose = classdict[f] for f in classdict: if f.startswith('_'): if not rename: continue if f.startswith('__') and f.endswith('__'): continue fields[f] = classdict[f] result = type.__new__(cls, name, bases, classdict) result.fields = fields result.rename = rename result.verbose = verbose return result class NamedTuple(metaclass=NamedTupleMetaClass): def __new__(cls, *p, **kw): print(p) if not p: p = cls.fields.values() try: verbose = kw['verbose'] except KeyError: verbose = cls.verbose return collections.namedtuple(cls.__name__, list(cls.fields), rename=cls.rename, verbose=verbose)(*p) Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
Tim Delaney On 17 December 2012 07:05, Terry Reedy <tjreedy@udel.edu> wrote:

And ignore that extra debugging print in there ;) class NamedTuple(metaclass=NamedTupleMetaClass): def __new__(cls, *p, **kw): if not p: p = cls.fields.values() try: verbose = kw['verbose'] except KeyError: verbose = cls.verbose return collections.namedtuple(cls.__name__, list(cls.fields), rename=cls.rename, verbose=verbose)(*p) Tim Delaney On 17 December 2012 08:08, Tim Delaney <timothy.c.delaney@gmail.com> wrote:

Improved version, with caching (verbose and non-verbose versions are different classes) and only parsing the fields once per class. import collections class NamedTupleMetaClass(type): # The prepare function @classmethod def __prepare__(metacls, name, bases): # No keywords in this case return collections.OrderedDict() # The metaclass invocation def __new__(cls, name, bases, classdict): result = type.__new__(cls, name, bases, classdict) result._classdict = classdict return result class NamedTuple(metaclass=NamedTupleMetaClass): _cache = {} def __new__(cls, *p, **kw): verbose = False try: verbose = kw_verbose = kw['verbose'] except KeyError: kw_verbose = None try: nt, fields = cls._cache[cls.__module__, cls.__qualname__, verbose] except KeyError: classdict = cls._classdict fields = collections.OrderedDict() rename = False for f in classdict: if f == '__rename__': rename = classdict[f] elif f == '__verbose__': verbose = classdict[f] for f in classdict: if f.startswith('_'): if not rename: continue if f.startswith('__') and f.endswith('__'): continue fields[f] = classdict[f] if kw_verbose is not None: verbose = kw_verbose nt = collections.namedtuple(cls.__name__, fields.keys(), rename=rename, verbose=verbose) nt, fields = cls._cache[cls.__module__, cls.__qualname__, verbose] = nt, list(fields.values()) if not p: p = fields return nt(*p) Tim Delaney On 17 December 2012 08:21, Tim Delaney <timothy.c.delaney@gmail.com> wrote:

On 12.12.12 21:35, Antoine Pitrou wrote:
field_docs={'x': 'abscissa', 'y': 'ordinate'} perhaps?
This will force repeat the field names twice. If we have such docs_dict, we can use it as: field_names = ['x', 'y'] Point = namedtuple('Point', field_names, field_docs=list(map(docs_dict.get, field_names))) or as Point = namedtuple('Point', [(f, docs_dict.get(f)) for f in field_names]) In case of ordered dict it can be even simpler: Point = namedtuple('Point', ordered_dict.keys(), field_docs=list(ordered_dict.values())) or Point = namedtuple('Point', ordered_dict.items())

On 12.12.2012 18:40, Serhiy Storchaka wrote:
IMO, attributes should be documented in the existing doc parameter, not separately. This makes the intention clear and the code overall more readable. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Dec 12 2012)
2012-12-05: Released eGenix pyOpenSSL 0.13 ... http://egenix.com/go37 2012-11-28: Released eGenix mx Base 3.2.5 ... http://egenix.com/go36 2013-01-22: Python Meeting Duesseldorf ... 41 days to go ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/

On 12.12.12 21:56, M.-A. Lemburg wrote:
Sorry, I didn't understand what you mean. There is no doc parameter for namedtuple yet. For overloading class docstring we can use inheritance idiom. But there is no way to change field docstring. All field docstrings generated using template 'Alias for field number {index:d}'.

On 12.12.2012 21:12, Serhiy Storchaka wrote:
Ah, sorry. Please scratch the "existing" in my reply :-) +1 on a doc parameter on namedtuple() - property() already has such a parameter, which is probably why I got confused. -0 on having separate doc strings for the fields. Their meaning will usually be clear from the main doc string.
For overloading class docstring we can use inheritance idiom. But there is no way to change field docstring. All field docstrings generated using template 'Alias for field number {index:d}'.
Yes, I've seen that: http://docs.python.org/2/library/collections.html?highlight=namedtuple#colle... It may not be too helpful, but it's an accurate description of the field's purpose :-) -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Dec 12 2012)
2012-12-05: Released eGenix pyOpenSSL 0.13 ... http://egenix.com/go37 2012-11-28: Released eGenix mx Base 3.2.5 ... http://egenix.com/go36 2013-01-22: Python Meeting Duesseldorf ... 41 days to go ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/

On Wed, Dec 12, 2012 at 9:40 AM, Serhiy Storchaka <storchaka@gmail.com>wrote:
This may be a good time to say that personally I always disliked namedtuple's creation syntax. It is unpleasant in two respects: 1. You have to repeat the name 2. You have to specify the fields in a space-separated string I wish there was an alternative of something like: @namedtuple class Point: x = 0 y = 0 Eli

Err, can class bodies ever be order-sensitive? I was under the impression names bound there work just like names bound anywhere... Unless of course that magical decorator is secretly an AST hack, in which case, yes, it can do whatever it wants :) On Sun, Dec 16, 2012 at 3:06 PM, Joao S. O. Bueno <jsbueno@python.org.br>wrote:
-- cheers lvh

Yep. You just have to define a metaclass with a __prepare__() that returns an OrderedDict (or similar). http://docs.python.org/3.4/reference/datamodel.html#preparing-the-class-name... http://docs.python.org/2/library/collections.html#ordereddict-objects Cheers, Chris -- http://rebertia.com

On 12/16/2012 8:22 AM, Eli Bendersky wrote:
Pretty easy, once one figures out metaclass basics. import collections as co class ntmeta(): def __prepare__(name, bases, **kwds): return co.OrderedDict() def __new__(cls, name, bases, namespace): print(namespace) # shows why filter is needed return co.namedtuple(name, filter(lambda s: s[0] != '_', namespace)) class Point(metaclass=ntmeta): x = 0 y = 0 p = Point(1,2) print(p) # OrderedDict([('__module__', '__main__'), ('__qualname__', 'Point'), ('x', 0), ('y', 0)]) Point(x=1, y=2) To use the filtered namespace values as defaults (Antoine's suggestion), first replace namedtuple() with its body. Then modify the header of generated name.__new__. For Point, change def __new__(_cls, x, y): #to def __new__(_cls, x=0, y=0): Also change the newclass docstring. For Point, change 'Point(x, y)' to 'Point(x=0, y=0)' -- Terry Jan Reedy

It can be made a bit more intelligent. I haven't done anything with docstrings here, but it wouldn't be hard to add. This automatically handles defaults (you can call the namedtuple with either zero parameters or the exact number). You can specify __rename__ = True, which will then only exclude __dunder_names__ (otherwise all names starting with an underscore are excluded). You can also pass verbose=[True|False] to the subclass constructor. import collections class NamedTupleMetaClass(type): # The prepare function @classmethod def __prepare__(metacls, name, bases): # No keywords in this case return collections.OrderedDict() # The metaclass invocation def __new__(cls, name, bases, classdict): fields = collections.OrderedDict() rename = False verbose = False for f in classdict: if f == '__rename__': rename = classdict[f] elif f == '__verbose__': verbose = classdict[f] for f in classdict: if f.startswith('_'): if not rename: continue if f.startswith('__') and f.endswith('__'): continue fields[f] = classdict[f] result = type.__new__(cls, name, bases, classdict) result.fields = fields result.rename = rename result.verbose = verbose return result class NamedTuple(metaclass=NamedTupleMetaClass): def __new__(cls, *p, **kw): print(p) if not p: p = cls.fields.values() try: verbose = kw['verbose'] except KeyError: verbose = cls.verbose return collections.namedtuple(cls.__name__, list(cls.fields), rename=cls.rename, verbose=verbose)(*p) Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information.
Tim Delaney On 17 December 2012 07:05, Terry Reedy <tjreedy@udel.edu> wrote:

And ignore that extra debugging print in there ;) class NamedTuple(metaclass=NamedTupleMetaClass): def __new__(cls, *p, **kw): if not p: p = cls.fields.values() try: verbose = kw['verbose'] except KeyError: verbose = cls.verbose return collections.namedtuple(cls.__name__, list(cls.fields), rename=cls.rename, verbose=verbose)(*p) Tim Delaney On 17 December 2012 08:08, Tim Delaney <timothy.c.delaney@gmail.com> wrote:

Improved version, with caching (verbose and non-verbose versions are different classes) and only parsing the fields once per class. import collections class NamedTupleMetaClass(type): # The prepare function @classmethod def __prepare__(metacls, name, bases): # No keywords in this case return collections.OrderedDict() # The metaclass invocation def __new__(cls, name, bases, classdict): result = type.__new__(cls, name, bases, classdict) result._classdict = classdict return result class NamedTuple(metaclass=NamedTupleMetaClass): _cache = {} def __new__(cls, *p, **kw): verbose = False try: verbose = kw_verbose = kw['verbose'] except KeyError: kw_verbose = None try: nt, fields = cls._cache[cls.__module__, cls.__qualname__, verbose] except KeyError: classdict = cls._classdict fields = collections.OrderedDict() rename = False for f in classdict: if f == '__rename__': rename = classdict[f] elif f == '__verbose__': verbose = classdict[f] for f in classdict: if f.startswith('_'): if not rename: continue if f.startswith('__') and f.endswith('__'): continue fields[f] = classdict[f] if kw_verbose is not None: verbose = kw_verbose nt = collections.namedtuple(cls.__name__, fields.keys(), rename=rename, verbose=verbose) nt, fields = cls._cache[cls.__module__, cls.__qualname__, verbose] = nt, list(fields.values()) if not p: p = fields return nt(*p) Tim Delaney On 17 December 2012 08:21, Tim Delaney <timothy.c.delaney@gmail.com> wrote:
participants (9)
-
Antoine Pitrou
-
Chris Rebert
-
Eli Bendersky
-
Joao S. O. Bueno
-
Laurens Van Houtven
-
M.-A. Lemburg
-
Serhiy Storchaka
-
Terry Reedy
-
Tim Delaney