Python collections.namedtuple() is confused

Python collections.namedtuple() is confused … here is a solution to the problem.

Python collections.namedtuple() is confused

documentation says that any Python valid identifier can be field_name, except for those that start with an underscore, which is fine.

If the rename parameter is true, it replaces the invalid field name with a valid field name, but in the example specified here, it replaces it with _1 or _3, how? These begin with an underscore!

The documentation also says:

If verbose is true, the class definition is printed just before being built

What exactly does this mean?

Solution

The reason why you cannot use an underscore at the beginning of a name is that these might conflict with the method name provided by the class (for example, _replace).

Because just a number is not a valid Python name

, any name that is invalid as an attribute (and therefore not a valid Python identifier or name that begins with an underscore) is replaced with an underscore + position number. This means that these generated names cannot conflict with valid names or with methods provided on the type.

This does not contradict the name you can choose; Given the limitations, it’s actually the perfect fallback. In addition, the names generated in this way are easy to deduce; The properties of these values are directly related to their indexes in the tuple.

As for setting verbose to True, it follows the instructions on the tin box. The source code for the generated namedtuple class is printed to sys.stdout:

>>> from collections import namedtuple
>>> namedtuple('foo', 'bar baz', verbose=True)
class foo(tuple):
    'foo(bar, baz)'

__slots__ = ()

_fields = ('bar', 'baz')

def __new__(_cls, bar, baz):
        'Create new instance of foo(bar, baz)'
        return _tuple.__new__(_cls, (bar, baz))

@classmethod
    def _make(cls, iterable, new=tuple.__new__, len=len):
        'Make a new foo object from a sequence or iterable'
        result = new(cls, iterable)
        if len(result) != 2:
            raise TypeError('Expected 2 arguments, got %d' % len(result))
        return result

def __repr__(self):
        'Return a nicely formatted representation string'
        return 'foo(bar=%r, baz=%r)' % self

def _asdict(self):
        'Return a new OrderedDict which maps field names to their values'
        return OrderedDict(zip(self._fields, self))

def _replace(_self, **kwds):
        'Return a new foo object replacing specified fields with new values'
        result = _self._make(map(kwds.pop, ('bar', 'baz'), _self))
        if kwds:
            raise ValueError('Got unexpected field names: %r' % kwds.keys())
        return result

def __getnewargs__(self):
        'Return self as a plain tuple.  Used by copy and pickle.'
        return tuple(self)

__dict__ = _property(_asdict)

def __getstate__(self):
        'Exclude the OrderedDict from pickling'
        pass

bar = _property(_itemgetter(0), doc='Alias for field number 0')

baz = _property(_itemgetter(1), doc='Alias for field number 1')

<class '__main__.foo'>

This allows you to examine the exact content generated for your class.

Related Problems and Solutions