Python dictionary from an object's fields
Note that best practice in Python 2.7 is to use new-style classes (not needed with Python 3), i.e.
class Foo(object):
...
Also, there's a difference between an 'object' and a 'class'. To build a dictionary from an arbitrary object, it's sufficient to use __dict__
. Usually, you'll declare your methods at class level and your attributes at instance level, so __dict__
should be fine. For example:
>>> class A(object):
... def __init__(self):
... self.b = 1
... self.c = 2
... def do_nothing(self):
... pass
...
>>> a = A()
>>> a.__dict__
{'c': 2, 'b': 1}
A better approach (suggested by robert in comments) is the builtin vars
function:
>>> vars(a)
{'c': 2, 'b': 1}
Alternatively, depending on what you want to do, it might be nice to inherit from dict
. Then your class is already a dictionary, and if you want you can override getattr
and/or setattr
to call through and set the dict. For example:
class Foo(dict):
def __init__(self):
pass
def __getattr__(self, attr):
return self[attr]
# etc...
In python, how do I cast a class object to a dict
There are at least five six ways. The preferred way depends on what your use case is.
Option 1:
Simply add an asdict()
method.
Based on the problem description I would very much consider the asdict
way of doing things suggested by other answers. This is because it does not appear that your object is really much of a collection:
class Wharrgarbl(object):
...
def asdict(self):
return {'a': self.a, 'b': self.b, 'c': self.c}
Using the other options below could be confusing for others unless it is very obvious exactly which object members would and would not be iterated or specified as key-value pairs.
Option 1a:
Inherit your class from 'typing.NamedTuple'
(or the mostly equivalent 'collections.namedtuple'
), and use the _asdict
method provided for you.
from typing import NamedTuple
class Wharrgarbl(NamedTuple):
a: str
b: str
c: str
sum: int = 6
version: str = 'old'
Using a named tuple is a very convenient way to add lots of functionality to your class with a minimum of effort, including an _asdict
method. However, a limitation is that, as shown above, the NT will include all the members in its _asdict
.
If there are members you don't want to include in your dictionary, you'll need to specify which members you want the named tuple _asdict
result to include. To do this, you could either inherit from a base namedtuple class using the older collections.namedtuple
API:
from collections import namedtuple as nt
class Wharrgarbl(nt("Basegarble", "a b c")):
# note that the typing info below isn't needed for the old API
a: str
b: str
c: str
sum: int = 6
version: str = 'old'
...or you could create a base class using the newer API, and inherit from that, using only the dictionary members in the base class:
from typing import NamedTuple
class Basegarbl(NamedTuple):
a: str
b: str
c: str
class Wharrgarbl(Basegarbl):
sum: int = 6
version: str = 'old'
Another limitation is that NT is read-only. This may or may not be desirable.
Option 2:
Implement __iter__
.
Like this, for example:
def __iter__(self):
yield 'a', self.a
yield 'b', self.b
yield 'c', self.c
Now you can just do:
dict(my_object)
This works because the dict()
constructor accepts an iterable of (key, value)
pairs to construct a dictionary. Before doing this, ask yourself the question whether iterating the object as a series of key,value pairs in this manner- while convenient for creating a dict
- might actually be surprising behavior in other contexts. E.g., ask yourself the question "what should the behavior of list(my_object)
be...?"
Additionally, note that accessing values directly using the get item obj["a"]
syntax will not work, and keyword argument unpacking won't work. For those, you'd need to implement the mapping protocol.
Option 3:
Implement the mapping protocol. This allows access-by-key behavior, casting to a dict
without using __iter__
, and also provides two types of unpacking behavior:
- mapping unpacking behavior:
{**my_obj}
- keyword unpacking behavior, but only if all the keys are strings:
dict(**my_obj)
The mapping protocol requires that you provide (at minimum) two methods together: keys()
and __getitem__
.
class MyKwargUnpackable:
def keys(self):
return list("abc")
def __getitem__(self, key):
return dict(zip("abc", "one two three".split()))[key]
Now you can do things like:
>>> m=MyKwargUnpackable()
>>> m["a"]
'one'
>>> dict(m) # cast to dict directly
{'a': 'one', 'b': 'two', 'c': 'three'}
>>> dict(**m) # unpack as kwargs
{'a': 'one', 'b': 'two', 'c': 'three'}
As mentioned above, if you are using a new enough version of python you can also unpack your mapping-protocol object into a dictionary comprehension like so (and in this case it is not required that your keys be strings):
>>> {**m}
{'a': 'one', 'b': 'two', 'c': 'three'}
Note that the mapping protocol takes precedence over the __iter__
method when casting an object to a dict
directly (without using kwarg unpacking, i.e. dict(m)
). So it is possible- and might be sometimes convenient- to cause the object to have different behavior when used as an iterable (e.g., list(m)
) vs. when cast to a dict
(dict(m)
).
But note also that with regular dictionaries, if you cast to a list, it will give the KEYS back, and not the VALUES as you require. If you implement another nonstandard behavior for __iter__
(returning values instead of keys), it could be surprising for other people using your code unless it is very obvious why this would happen.
EMPHASIZED: Just because you CAN use the mapping protocol, does NOT mean that you SHOULD do so. Does it actually make sense for your object to be passed around as a set of key-value pairs, or as keyword arguments and values? Does accessing it by key- just like a dictionary- really make sense? Would you also expect your object to have other standard mapping methods such as items
, values
, get
? Do you want to support the in
keyword and equality checks (==
)?
If the answer to these questions is yes, it's probably a good idea to not stop here, and consider the next option instead.
Option 4:
Look into using the 'collections.abc
' module.
Inheriting your class from 'collections.abc.Mapping
or 'collections.abc.MutableMapping
signals to other users that, for all intents and purposes, your class is a mapping * and can be expected to behave that way. It also provides the methods items
, values
, get
and supports the in
keyword and equality checks (==
) "for free".
You can still cast your object to a dict
just as you require, but there would probably be little reason to do so. Because of duck typing, bothering to cast your mapping object to a dict
would just be an additional unnecessary step the majority of the time.
This answer from me about how to use ABC
s might also be helpful.
As noted in the comments below: it's worth mentioning that doing this the abc way essentially turns your object class into a dict
-like class (assuming you use MutableMapping
and not the read-only Mapping
base class). Everything you would be able to do with dict
, you could do with your own class object. This may be, or may not be, desirable.
Also consider looking at the numerical abcs in the numbers
module:
https://docs.python.org/3/library/numbers.html
Since you're also casting your object to an int
, it might make more sense to essentially turn your class into a full fledged int
so that casting isn't necessary.
Option 5:
Look into using the dataclasses
module (Python 3.7+ only), which includes a convenient asdict()
utility method.
from dataclasses import dataclass, asdict, field, InitVar
@dataclass
class Wharrgarbl(object):
a: int
b: int
c: int
sum: InitVar[int] # note: InitVar will exclude this from the dict
version: InitVar[str] = "old"
def __post_init__(self, sum, version):
self.sum = 6 # this looks like an OP mistake?
self.version = str(version)
Now you can do this:
>>> asdict(Wharrgarbl(1,2,3,4,"X"))
{'a': 1, 'b': 2, 'c': 3}
Option 6:
Use typing.TypedDict
, which has been added in python 3.8.
NOTE: option 6 is likely NOT what the OP, or other readers based on the title of this question, are looking for. See additional comments below.
class Wharrgarbl(TypedDict):
a: str
b: str
c: str
Using this option, the resulting object is a dict
(emphasis: it is not a Wharrgarbl
). There is no reason at all to "cast" it to a dict (unless you are making a copy).
And since the object is a dict
, the initialization signature is identical to that of dict
and as such it only accepts keyword arguments or another dictionary.
>>> w = Wharrgarbl(a=1,b=2,b=3)
>>> w
{'a': 1, 'b': 2, 'c': 3}
>>> type(w)
<class 'dict'>
Emphasized: the above "class" Wharrgarbl
isn't actually a new class at all. It is simply syntactic sugar for creating typed dict
objects with specific keys ONLY and value fields of different types for the type checker. At run time, it is still nothing more than a dict
.
As such this option can be pretty convenient for signaling to readers of your code (and also to a type checker such as mypy) that such a dict
object is expected to have specific keys with specific value types.
But this means you cannot, for example, add other methods, although you can try:
class MyDict(TypedDict):
def my_fancy_method(self):
return "world changing result"
...but it won't work:
>>> MyDict().my_fancy_method()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'my_fancy_method'
* "Mapping" has become the standard "name" of the dict
-like duck type
Convert object attributes to dict
Do it with a recursive function. Get the vars for the object, then replace the children list with a list of converted children.
def convert(self):
d = dict(vars(self)) # Copy the vars() dictionary
d['children'] = [c.convert() for c in self.children]
return d
Set attributes from dictionary in python
Sure, something like this:
class Employee(object):
def __init__(self, initial_data):
for key in initial_data:
setattr(self, key, initial_data[key])
Update
As Brent Nash suggests, you can make this more flexible by allowing keyword arguments as well:
class Employee(object):
def __init__(self, *initial_data, **kwargs):
for dictionary in initial_data:
for key in dictionary:
setattr(self, key, dictionary[key])
for key in kwargs:
setattr(self, key, kwargs[key])
Then you can call it like this:
e = Employee({"name": "abc", "age": 32})
or like this:
e = Employee(name="abc", age=32)
or even like this:
employee_template = {"role": "minion"}
e = Employee(employee_template, name="abc", age=32)
Dictionary key from object field
Explicit is better than implicit.1 In this case, it means using an integer as the key rather than expecting an object to "become" an integer in this context.
Take RickyA's advice.
d[Foo(3).val] = 'bar'
1 Second item in the Zen of Python
Python: Accessing object attributes from objects stored in a dictionary
Change this
def make(name='', street='', city='', state='', zip='', count=0):
dic[count]=User(name='', street='', city='', state='', zip='')
to
def make(name='', street='', city='', state='', zip='', count=0):
dic[count]=User(name=name, street=street, city=city, state=state, zip=zip)
In case your attribute is containing whitespaces or an empty string, you can do that in some case, it will help you know if something is there.
print "<%s>" % my_dict[key].attribute
If you see <>
it's likely that this is an empty string, if you see something else in between, it could be spaces are tabs or newlines or anything that isn't visible.
The other possibility is that for some reasons, the attribute
you're getting is an object that override __repr__
or __str__
and returns an empty string. Without knowing with what you're dealing with, it is very hard to help more than that.
Python: Get values (objects) from a dictionary of objects in which one of the object's field matches a value (or condition)
To make a dict
from your dict
,
subdict = dict((k, v) for k, v in myDict.iteritems() if v.field2 >= 2)
Recursively convert python object graph to dictionary
An amalgamation of my own attempt and clues derived from Anurag Uniyal and Lennart Regebro's answers works best for me:
def todict(obj, classkey=None):
if isinstance(obj, dict):
data = {}
for (k, v) in obj.items():
data[k] = todict(v, classkey)
return data
elif hasattr(obj, "_ast"):
return todict(obj._ast())
elif hasattr(obj, "__iter__") and not isinstance(obj, str):
return [todict(v, classkey) for v in obj]
elif hasattr(obj, "__dict__"):
data = dict([(key, todict(value, classkey))
for key, value in obj.__dict__.items()
if not callable(value) and not key.startswith('_')])
if classkey is not None and hasattr(obj, "__class__"):
data[classkey] = obj.__class__.__name__
return data
else:
return obj
Convert Django Model object to dict with all of the fields intact
There are many ways to convert an instance to a dictionary, with varying degrees of corner case handling and closeness to the desired result.
1. instance.__dict__
instance.__dict__
which returns
{'_foreign_key_cache': <OtherModel: OtherModel object>,
'_state': <django.db.models.base.ModelState at 0x7ff0993f6908>,
'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
'foreign_key_id': 2,
'id': 1,
'normal_value': 1,
'readonly_value': 2}
This is by far the simplest, but is missing many_to_many
, foreign_key
is misnamed, and it has two unwanted extra things in it.
2. model_to_dict
from django.forms.models import model_to_dict
model_to_dict(instance)
which returns
{'foreign_key': 2,
'id': 1,
'many_to_many': [<OtherModel: OtherModel object>],
'normal_value': 1}
This is the only one with many_to_many
, but is missing the uneditable fields.
3. model_to_dict(..., fields=...)
from django.forms.models import model_to_dict
model_to_dict(instance, fields=[field.name for field in instance._meta.fields])
which returns
{'foreign_key': 2, 'id': 1, 'normal_value': 1}
This is strictly worse than the standard model_to_dict
invocation.
4. query_set.values()
SomeModel.objects.filter(id=instance.id).values()[0]
which returns
{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
'foreign_key_id': 2,
'id': 1,
'normal_value': 1,
'readonly_value': 2}
This is the same output as instance.__dict__
but without the extra fields.foreign_key_id
is still wrong and many_to_many
is still missing.
5. Custom Function
The code for django's model_to_dict
had most of the answer. It explicitly removed non-editable fields, so removing that check and getting the ids of foreign keys for many to many fields results in the following code which behaves as desired:
from itertools import chain
def to_dict(instance):
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields):
data[f.name] = f.value_from_object(instance)
for f in opts.many_to_many:
data[f.name] = [i.id for i in f.value_from_object(instance)]
return data
While this is the most complicated option, calling to_dict(instance)
gives us exactly the desired result:
{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
'foreign_key': 2,
'id': 1,
'many_to_many': [2],
'normal_value': 1,
'readonly_value': 2}
6. Use Serializers
Django Rest Framework's ModelSerializer allows you to build a serializer automatically from a model.
from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = "__all__"
SomeModelSerializer(instance).data
returns
{'auto_now_add': '2018-12-20T21:34:29.494827Z',
'foreign_key': 2,
'id': 1,
'many_to_many': [2],
'normal_value': 1,
'readonly_value': 2}
This is almost as good as the custom function, but auto_now_add is a string instead of a datetime object.
Bonus Round: better model printing
If you want a django model that has a better python command-line display, have your models child-class the following:
from django.db import models
from itertools import chain
class PrintableModel(models.Model):
def __repr__(self):
return str(self.to_dict())
def to_dict(instance):
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields):
data[f.name] = f.value_from_object(instance)
for f in opts.many_to_many:
data[f.name] = [i.id for i in f.value_from_object(instance)]
return data
class Meta:
abstract = True
So, for example, if we define our models as such:
class OtherModel(PrintableModel): pass
class SomeModel(PrintableModel):
normal_value = models.IntegerField()
readonly_value = models.IntegerField(editable=False)
auto_now_add = models.DateTimeField(auto_now_add=True)
foreign_key = models.ForeignKey(OtherModel, related_name="ref1")
many_to_many = models.ManyToManyField(OtherModel, related_name="ref2")
Calling SomeModel.objects.first()
now gives output like this:
{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
'foreign_key': 2,
'id': 1,
'many_to_many': [2],
'normal_value': 1,
'readonly_value': 2}
Related Topics
What's the Fastest Way in Python to Calculate Cosine Similarity Given Sparse Matrix Data
How to Override the [] Operator in Python
Get Lat/Long Given Current Point, Distance and Bearing
Find First Element in a Sequence That Matches a Predicate
Python Nested Functions Variable Scoping
What Is the Point of Indexing in Pandas
How to Apply Piecewise Linear Fit in Python
Binary Representation of Float in Python (Bits Not Hex)
Is the Server Bundled with Flask Safe to Use in Production
Underscore _ as Variable Name in Python
How to Add an Integer to Each Element in a List
Absolute VS. Explicit Relative Import of Python Module
Python Os.Path.Join on Windows