[Python-ideas] Docstrings for namedtuple

Tim Delaney timothy.c.delaney at gmail.com
Sun Dec 16 22:55:10 CET 2012


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 at gmail.com> wrote:

> An improvement would be to cache the namedtuple types so that each only
> gets created once.
>
> Tim Delaney
>
>
> On 17 December 2012 08:09, Tim Delaney <timothy.c.delaney at gmail.com>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 at gmail.com>wrote:
>>
>>> 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.
>>> >>> import namedtuple_baseclass
>>> >>> class Point(namedtuple_baseclass.NamedTuple):
>>> ...     x = 0
>>> ...     y = 0
>>> ...
>>> >>> print(Point())
>>>  Point(x=0, y=0)
>>> >>> print(Point(1, 2))
>>> Point(x=1, y=2)
>>> >>> print(Point(1))
>>> Traceback (most recent call last):
>>>   File "<stdin>", line 1, in <module>
>>>   File ".\namedtuple_baseclass.py", line 38, in __new__
>>>     return collections.namedtuple(cls.__name__, list(cls.fields),
>>> rename=cls.rename, verbose=cls.verbose)(*p)
>>> TypeError: __new__() missing 1 required positional argument: 'y'
>>> >>> print(Point(1, 2, 3))
>>> Traceback (most recent call last):
>>>   File "<stdin>", line 1, in <module>
>>>   File ".\namedtuple_baseclass.py", line 38, in __new__
>>>     return collections.namedtuple(cls.__name__, list(cls.fields),
>>> rename=cls.rename, verbose=cls.verbose)(*p)
>>> TypeError: __new__() takes 3 positional arguments but 4 were given
>>> >>>
>>>
>>> Tim Delaney
>>>
>>>
>>> On 17 December 2012 07:05, Terry Reedy <tjreedy at udel.edu> wrote:
>>>
>>>> On 12/16/2012 8:22 AM, Eli Bendersky 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
>>>>>
>>>>
>>>> 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
>>>>
>>>>
>>>> ______________________________**_________________
>>>> Python-ideas mailing list
>>>> Python-ideas at python.org
>>>> http://mail.python.org/**mailman/listinfo/python-ideas<http://mail.python.org/mailman/listinfo/python-ideas>
>>>>
>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20121217/55da8e82/attachment.html>


More information about the Python-ideas mailing list