Convert a namedtuple into a dictionary
Asked Answered
N

6

239

I have a named tuple class in python

class Town(collections.namedtuple('Town', [
    'name', 
    'population',
    'coordinates',
    'population', 
    'capital', 
    'state_bird'])):
    # ...

I'd like to convert Town instances into dictionaries. I don't want it to be rigidly tied to the names or number of the fields in a Town.

Is there a way to write it such that I could add more fields, or pass an entirely different named tuple in and get a dictionary.

I can not alter the original class definition as its in someone else's code. So I need to take an instance of a Town and convert it to a dictionary.

Nubilous answered 3/10, 2014 at 14:8 Comment(7)
btw... look at tab completion or the dir command, which will show you the fields for any object... that would have shown the _asdict function directly.Verne
it looks like what you really want to do is subclass from dict instead of 'namedtuple', and pass the namedtuple into the initializer. Remember that if you're used to Cxx, class Town(x) isn't the constructor, def __init__(self, *args, **kwargs) inside it is.Verne
I can not alter the original class as its in someone elses code. so I have to subclass from namedtoubleNubilous
@CorleyBrigman can you explain this more? I tried to find documentation on the named touple, or find what I could call on it and I couldnt figure out how. (Again python is not my strongest language)Nubilous
which part? dir is just a python built-in... you can run it on any python object, in a console or in a script (where it returns a list you can print or do whatever with), and it will return a list of (nearly) all the attributes the object. helpful if you're trying to figure out how an unknown object works.Verne
I had no idea, thats is incredibly helpful. My biggest struggle with python is the lack of API documentation. I never know what I can do with objects, this is exactly what I needed!Nubilous
For going the other way (dictionary into namedtuple), look here: https://mcmap.net/q/119390/-pythonic-way-to-convert-a-dictionary-into-namedtuple-or-another-hashable-dict-like/674039Potpie
P
414

TL;DR: there's a method _asdict provided for this.

Here is a demonstration of the usage:

>>> from collections import namedtuple
>>> fields = ['name', 'population', 'coordinates', 'capital', 'state_bird']
>>> Town = namedtuple('Town', fields)
>>> funkytown = Town('funky', 300, 'somewhere', 'lipps', 'chicken')
>>> funkytown._asdict()
{'name': 'funky',
 'population': 300,
 'coordinates': 'somewhere',
 'capital': 'lipps',
 'state_bird': 'chicken'}

This is a documented method of namedtuples, i.e. unlike the usual convention in python the leading underscore on the method name isn't there to discourage use. Along with the other methods added to namedtuples, _make, _replace, _source, _fields, it has the underscore only to try and prevent conflicts with possible field names.


Note: For some 2.7.5 < python version < 3.5.0 code out in the wild, you might see this version:

>>> vars(funkytown)
OrderedDict([('name', 'funky'),
             ('population', 300),
             ('coordinates', 'somewhere'),
             ('capital', 'lipps'),
             ('state_bird', 'chicken')])

For a while the documentation had mentioned that _asdict was obsolete (see here), and suggested to use the built-in method vars. That advice is now outdated; in order to fix a bug related to subclassing, the __dict__ property which was present on namedtuples has again been removed by this commit.

Potpie answered 3/10, 2014 at 14:12 Comment(6)
Does anyone know if there an established precedent that suggests _asdict shouldn't be aliased in the standard library to asdict?Badgett
@Badgett then you couldn't have "asdict" be one of the tuple names.Morrow
infuriating! vars is the natural, idiomatic way to do this! Could it not handle namedtuple as a special case? Doesn't seem to me like it would be tooo hardGrider
@Grider vars(obj) is supposed to be equivalent to obj.__dict__, so the special-case would be changing the documented behavior of vars (tuple instances don't carry around a __dict__)Potpie
yes, I understand the excuse, but it doesn't cut any ice with me. What you are describing is how it is implemented, and vars "hides" that implementation (to use the OO parlance). I guess there's probably a "good" reason why they couldn't simply overload the method, but to me it seems like a slip in standards.Grider
As of 3.8, _asdict() returns a dict and not an OrderedDict since as of version 3.7, dicts are ordered. See docs.python.org/3/library/…Landseer
N
35

There's a built in method on namedtuple instances for this, _asdict.

As discussed in the comments, on some versions vars() will also do it, but it's apparently highly dependent on build details, whereas _asdict should be reliable. In some versions _asdict was marked as deprecated, but comments indicate that this is no longer the case as of 3.4.

Nudicaul answered 3/10, 2014 at 14:13 Comment(12)
I wasn't the downvoter, but it could be because the _asdict method has been obsoleted in python3 (in favour of vars)Potpie
Conversely, it looks like vars does't work on some older versions - on 2.7 it raises a TypeError, since that version's namedtuple class does not have an __dict__ attribute.Nudicaul
yes, Martijn and I have discussed that here. It will work on newer versions of 2.7 btw (I'm on 2.7.6 and it works)Potpie
Past the edit window on my above comment - it fails on 2.7.5 so it must be new as of 2.7.6. Unless my 2.7.5 build is off, as Martijn's was at the linked answer? Anyway, it seems like whether or not it works on 2.7.5 depends on build specifics.Nudicaul
Just a heads up: _asdict is not longer obseleted (and returns an OrderedDict now), and vars produces an error with Python 3.4 (from the removal of the dict attribute of namedtuples).Hypercorrection
@AlexanderHuszagh - thanks, updated the answer with that information. Looks like _asdict is the most consistent way to do it, even if it was deprecated in some versions.Nudicaul
@AlexanderHuszagh That's inaccurate, it's still working in python 3.5.0.Potpie
@wim, I said in 3.4. The __dict__ magic method has been removed and inserted many times in various Python versions. The class does have it, however, the instance does not, meaning vars will not reliably work. _asdict was once deprecated, however, it is no longer and works in recent 2.7.x and 3.2+ versions, meaning it is the most reliable way to convert namedtuples to a mapping structure.Hypercorrection
Yes, I understand. But it is plainly incorrect that vars() is no longer working in 3.4 (as OP has edited into the answer). Online python 3.4 example of it working here.Potpie
I can't keep track of the apparently often-changing state of vars(). Open to suggestions for how else to accurately describe it.Nudicaul
So I just tested it on 5 different builds. 3.4.3+ on Ubuntu (GCC 5.2.1, installed via apt-get) fails, 3.5.1 on Ubuntu (GCC 5.2.1, installed via makefile) fails, Windows 3.4.1 installed by MSI succeeds, it works on Python 3.3.6 (GCC 5.2.1, Ubuntu, makefile), and works on Python 3.2.6 (Ubuntu, GCC 5.2.1, makefile). I would say it does not work fairly reliably, and that should be a key point. Python is meant to be portable, if it fails on half of all CPython builds, that's a very low standard for portability. This is why I say use _asdict. It's reliable.Hypercorrection
On all of the above, _asdict works. I get that this is highly build dependent, but considering that _asdict works, reliably, and also that __dict__ in Python3.x is just a property of _asdict in the Python3.3 and Python3.2 methods (looked manually at the source code in collections/__init__.py). Since __dict__ just is a property of _asdict, and is intermittently present and missing, shouldn't _asdict be the preferred method? (Line 274 in Python3.3.6, collections/__init__.py)Hypercorrection
H
9

Normally _asdict() returns a OrderedDict. this is how to convert from OrderedDict to a regular dict


town = Town('funky', 300, 'somewhere', 'lipps', 'chicken')
dict(town._asdict())

the output will be

{'capital': 'lipps',
 'coordinates': 'somewhere',
 'name': 'funky',
 'population': 300,
 'state_bird': 'chicken'}
Hybridize answered 7/4, 2021 at 15:9 Comment(1)
This is no longer necessary. As of Python 3.8, _asdict() returns a dict and not an OrderedDictLandseer
L
3

On Ubuntu 14.04 LTS versions of python2.7 and python3.4 the __dict__ property worked as expected. The _asdict method also worked, but I'm inclined to use the standards-defined, uniform, property api instead of the localized non-uniform api.

$ python2.7

# Works on:
# Python 2.7.6 (default, Jun 22 2015, 17:58:13)  [GCC 4.8.2] on linux2
# Python 3.4.3 (default, Oct 14 2015, 20:28:29)  [GCC 4.8.4] on linux

import collections

Color = collections.namedtuple('Color', ['r', 'g', 'b'])
red = Color(r=256, g=0, b=0)

# Access the namedtuple as a dict
print(red.__dict__['r'])  # 256

# Drop the namedtuple only keeping the dict
red = red.__dict__
print(red['r'])  #256

Seeing as dict is the semantic way to get a dictionary representing soemthing, (at least to the best of my knowledge).


It would be nice to accumulate a table of major python versions and platforms and their support for __dict__, currently I only have one platform version and two python versions as posted above.

| Platform                      | PyVer     | __dict__ | _asdict |
| --------------------------    | --------- | -------- | ------- |
| Ubuntu 14.04 LTS              | Python2.7 | yes      | yes     |
| Ubuntu 14.04 LTS              | Python3.4 | yes      | yes     |
| CentOS Linux release 7.4.1708 | Python2.7 | no       | yes     |
| CentOS Linux release 7.4.1708 | Python3.4 | no       | yes     |
| CentOS Linux release 7.4.1708 | Python3.6 | no       | yes     |
Libriform answered 3/10, 2014 at 14:8 Comment(1)
Linux-3.10.0-693.el7.x86_64-x86_64-with-centos-7.4.1708-Core, Python 2.7 -- __dict__ does not work.Fealty
P
-1

Case #1: one dimension tuple

TUPLE_ROLES = (
    (912,"Role 21"),
    (913,"Role 22"),
    (925,"Role 23"),
    (918,"Role 24"),
)


TUPLE_ROLES[912]  #==> Error because it is out of bounce. 
TUPLE_ROLES[  2]  #==> will show Role 23.
DICT1_ROLE = {k:v for k, v in TUPLE_ROLES }
DICT1_ROLE[925] # will display "Role 23" 

Case #2: Two dimension tuple
Example: DICT_ROLES[961] # will show 'Back-End Programmer'

NAMEDTUPLE_ROLES = (
    ('Company', ( 
            ( 111, 'Owner/CEO/President'), 
            ( 113, 'Manager'),
            ( 115, 'Receptionist'),
            ( 117, 'Marketer'),
            ( 119, 'Sales Person'),
            ( 121, 'Accountant'),
            ( 123, 'Director'),
            ( 125, 'Vice President'),
            ( 127, 'HR Specialist'),
            ( 141, 'System Operator'),
    )),
    ('Restaurant', ( 
            ( 211, 'Chef'), 
            ( 212, 'Waiter/Waitress'), 
    )),
    ('Oil Collector', ( 
            ( 211, 'Truck Driver'), 
            ( 213, 'Tank Installer'), 
            ( 217, 'Welder'),
            ( 218, 'In-house Handler'),
            ( 219, 'Dispatcher'),
    )),
    ('Information Technology', ( 
            ( 912, 'Server Administrator'),
            ( 914, 'Graphic Designer'),
            ( 916, 'Project Manager'),
            ( 918, 'Consultant'),
            ( 921, 'Business Logic Analyzer'),
            ( 923, 'Data Model Designer'),
            ( 951, 'Programmer'),
            ( 953, 'WEB Front-End Programmer'),
            ( 955, 'Android Programmer'),
            ( 957, 'iOS Programmer'),
            ( 961, 'Back-End Programmer'),
            ( 962, 'Fullstack Programmer'),
            ( 971, 'System Architect'),
    )),
)

#Thus, we need dictionary/set

T4 = {}
def main():
    for k, v in NAMEDTUPLE_ROLES:
        for k1, v1 in v:
            T4.update ( {k1:v1}  )
    print (T4[961]) # will display 'Back-End Programmer'
    # print (T4) # will display all list of dictionary

main()
Pursuance answered 20/3, 2019 at 17:57 Comment(0)
C
-5

Python 3. Allocate any field to the dictionary as the required index for the dictionary, I used 'name'.

import collections

Town = collections.namedtuple("Town", "name population coordinates capital state_bird")

town_list = []

town_list.append(Town('Town 1', '10', '10.10', 'Capital 1', 'Turkey'))
town_list.append(Town('Town 2', '11', '11.11', 'Capital 2', 'Duck'))

town_dictionary = {t.name: t for t in town_list}
Colza answered 6/1, 2017 at 19:52 Comment(1)
Not helpful as you know name is there. it should be a blind methodKep

© 2022 - 2024 — McMap. All rights reserved.