Using namedtuples field names for column indices in a list of lists

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Jan 8 01:42:49 EST 2017


On Sunday 08 January 2017 16:39, Deborah Swanson wrote:

> What I've done so far:
> 
> with open('E:\\Coding projects\\Pycharm\\Moving\\Moving 2017 in.csv',
> 'r') as infile:
>     ls = list(csv.reader(infile))
>     lst = namedtuple('lst', ls[0])
> 
> where 'ls[0]' is the header row of the csv, and it works perfectly well.
> 'lst' is a namedtuple instance with each of the column titles as field
> names.

Are you sure? namedtuple() returns a class, not a list:

py> from collections import namedtuple
py> names = ['A', 'B', 'C']
py> namedtuple('lst', names)
<class '__main__.lst'>

The way namedtuple() is intended to be used is like this:


py> from collections import namedtuple
py> names = ['A', 'B', 'C']
py> Record = namedtuple('Record', names)
py> instance = Record(10, 20, 30)
py> print(instance)
Record(A=10, B=20, C=30)


There is no need to call fget directly to access the individual fields:

py> instance.A
10
py> instance.B
20
py> instance[1]  # indexing works too
20


which is *much* simpler than:

py> Record.A.fget(instance)
10



I think you should be doing something like this:

pathname = 'E:\\Coding projects\\Pycharm\\Moving\\Moving 2017 in.csv'
with open(pathname, 'r') as infile:
    rows = list(csv.reader(infile))
    Record = namedtuple("Record", rows[0])
    for row in rows[1:]:  # skip the first row, the header
        row = Record(row)
        # process this row...
        if row.location == 0:
            ...

[...]
> But I haven't found a way to assign new values to a list element. using
> namedtuple.fieldname. I think a basic problem is that namedtuples have
> the properties of tuples, and you can't assign to an existing tuple
> because they're immutable.

Indeed. Being tuples, you have to create a new one. You can do it with slicing, 
like ordinary tuples, but that's rather clunky:

py> print(instance)
Record(A=10, B=20, C=30)
py> Record(999, *instance[1:])
Record(A=999, B=20, C=30)


The recommended way is with the _replace method:

py> instance._replace(A=999)
Record(A=999, B=20, C=30)
py> instance._replace(A=999, C=888)
Record(A=999, B=20, C=888)


Note that despite the leading underscore, _replace is *not* a private method of 
the class. It is intentionally documented as public. The leading underscore is 
so that it won't clash with any field names.




-- 
Steven
"Ever since I learned about confirmation bias, I've been seeing 
it everywhere." - Jon Ronson




More information about the Python-list mailing list