[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