Do you know if there is a built-in function to build a dictionary from an arbitrary object? I'd like to do something like this:
>>> class Foo:
... bar = 'hello'
... baz = 'world'
...
>>> f = Foo()
>>> props(f)
{ 'bar' : 'hello', 'baz' : 'world' }
NOTE: It should not include methods. Only 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...
Instead of x.__dict__, it's actually more pythonic to use vars(x).
The dir builtin will give you all the object's attributes, including special methods like __str__, __dict__ and a whole bunch of others which you probably don't want. But you can do something like:
>>> class Foo(object):
... bar = 'hello'
... baz = 'world'
...
>>> f = Foo()
>>> [name for name in dir(f) if not name.startswith('__')]
[ 'bar', 'baz' ]
>>> dict((name, getattr(f, name)) for name in dir(f) if not name.startswith('__'))
{ 'bar': 'hello', 'baz': 'world' }
So can extend this to only return data attributes and not methods, by defining your props function like this:
import inspect
def props(obj):
pr = {}
for name in dir(obj):
value = getattr(obj, name)
if not name.startswith('__') and not inspect.ismethod(value):
pr[name] = value
return pr
I've settled with a combination of both answers:
dict((key, value) for key, value in f.__dict__.iteritems()
if not callable(value) and not key.startswith('__'))
I thought I'd take some time to show you how you can translate an object to dict via dict(obj).
class A(object):
d = '4'
e = '5'
f = '6'
def __init__(self):
self.a = '1'
self.b = '2'
self.c = '3'
def __iter__(self):
# first start by grabbing the Class items
iters = dict((x,y) for x,y in A.__dict__.items() if x[:2] != '__')
# then update the class items with the instance items
iters.update(self.__dict__)
# now 'yield' through the items
for x,y in iters.items():
yield x,y
a = A()
print(dict(a))
# prints "{'a': '1', 'c': '3', 'b': '2', 'e': '5', 'd': '4', 'f': '6'}"
The key section of this code is the __iter__ function.
As the comments explain, the first thing we do is grab the Class items and prevent anything that starts with '__'.
Once you've created that dict, then you can use the update dict function and pass in the instance __dict__.
These will give you a complete class+instance dictionary of members. Now all that's left is to iterate over them and yield the returns.
Also, if you plan on using this a lot, you can create an #iterable class decorator.
def iterable(cls):
def iterfn(self):
iters = dict((x,y) for x,y in cls.__dict__.items() if x[:2] != '__')
iters.update(self.__dict__)
for x,y in iters.items():
yield x,y
cls.__iter__ = iterfn
return cls
#iterable
class B(object):
d = 'd'
e = 'e'
f = 'f'
def __init__(self):
self.a = 'a'
self.b = 'b'
self.c = 'c'
b = B()
print(dict(b))
A downside of using __dict__ is that it is shallow; it won't convert any subclasses to dictionaries.
If you're using Python3.5 or higher, you can use jsons:
>>> import jsons
>>> jsons.dump(f)
{'bar': 'hello', 'baz': 'world'}
To build a dictionary from an arbitrary object, it's sufficient to use __dict__.
This misses attributes that the object inherits from its class. For example,
class c(object):
x = 3
a = c()
hasattr(a, 'x') is true, but 'x' does not appear in a.__dict__
Python3.x
return dict((key, value) for key, value in f.__dict__.items() if not callable(value) and not key.startswith('__'))
Late answer but provided for completeness and the benefit of googlers:
def props(x):
return dict((key, getattr(x, key)) for key in dir(x) if key not in dir(x.__class__))
This will not show methods defined in the class, but it will still show fields including those assigned to lambdas or those which start with a double underscore.
vars() is great, but doesn't work for nested objects of objects
Convert nested object of objects to dict:
def to_dict(self):
return json.loads(json.dumps(self, default=lambda o: o.__dict__))
I think the easiest way is to create a getitem attribute for the class. If you need to write to the object, you can create a custom setattr . Here is an example for getitem:
class A(object):
def __init__(self):
self.b = 1
self.c = 2
def __getitem__(self, item):
return self.__dict__[item]
# Usage:
a = A()
a.__getitem__('b') # Outputs 1
a.__dict__ # Outputs {'c': 2, 'b': 1}
vars(a) # Outputs {'c': 2, 'b': 1}
dict generates the objects attributes into a dictionary and the dictionary object can be used to get the item you need.
In 2021, and for nested objects/dicts/json use pydantic BaseModel - will convert nested dicts and nested json objects to python objects and JSON and vice versa:
https://pydantic-docs.helpmanual.io/usage/models/
>>> class Foo(BaseModel):
... count: int
... size: float = None
...
>>>
>>> class Bar(BaseModel):
... apple = 'x'
... banana = 'y'
...
>>>
>>> class Spam(BaseModel):
... foo: Foo
... bars: List[Bar]
...
>>>
>>> m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
Object to dict
>>> print(m.dict())
{'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}
Object to JSON
>>> print(m.json())
{"foo": {"count": 4, "size": null}, "bars": [{"apple": "x1", "banana": "y"}, {"apple": "x2", "banana": "y"}]}
Dict to object
>>> spam = Spam.parse_obj({'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y2'}]})
>>> spam
Spam(foo=Foo(count=4, size=None), bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y2')])
JSON to object
>>> spam = Spam.parse_raw('{"foo": {"count": 4, "size": null}, "bars": [{"apple": "x1", "banana": "y"}, {"apple": "x2", "banana": "y"}]}')
>>> spam
Spam(foo=Foo(count=4, size=None), bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')])
Dataclass(from Python 3.7) is another option which can be used for converting class properties to dict. asdict can be used along with dataclass objects
for the conversion.
Example:
#dataclass
class Point:
x: int
y: int
p = Point(10, 20)
asdict(p) # it returns {'x': 10, 'y': 20}
As mentioned in one of the comments above, vars currently isn't universal in that it doesn't work for objects with __slots__ instead of a normal __dict__. Moreover, some objecs (e.g., builtins like str or int) have neither a __dict__ nor __slots__.
For now, a more versatile solution could be this:
def instance_attributes(obj: Any) -> Dict[str, Any]:
"""Get a name-to-value dictionary of instance attributes of an arbitrary object."""
try:
return vars(obj)
except TypeError:
pass
# object doesn't have __dict__, try with __slots__
try:
slots = obj.__slots__
except AttributeError:
# doesn't have __dict__ nor __slots__, probably a builtin like str or int
return {}
# collect all slots attributes (some might not be present)
attrs = {}
for name in slots:
try:
attrs[name] = getattr(obj, name)
except AttributeError:
continue
return attrs
Example:
class Foo:
class_var = "spam"
class Bar:
class_var = "eggs"
__slots__ = ["a", "b"]
>>> foo = Foo()
>>> foo.a = 1
>>> foo.b = 2
>>> instance_attributes(foo)
{'a': 1, 'b': 2}
>>> bar = Bar()
>>> bar.a = 3
>>> instance_attributes(bar)
{'a': 3}
>>> instance_attributes("baz")
{}
Rant:
It's a pity that this isn't built into vars already. Many builtins in Python promise to be "the" solution to a problem but then there's always several special cases that aren't handled... And one just ends up having to write the code manually in any case.
If you want to list part of your attributes, override __dict__:
def __dict__(self):
d = {
'attr_1' : self.attr_1,
...
}
return d
# Call __dict__
d = instance.__dict__()
This helps a lot if your instance get some large block data and you want to push d to Redis like message queue.
PYTHON 3:
class DateTimeDecoder(json.JSONDecoder):
def __init__(self, *args, **kargs):
JSONDecoder.__init__(self, object_hook=self.dict_to_object,
*args, **kargs)
def dict_to_object(self, d):
if '__type__' not in d:
return d
type = d.pop('__type__')
try:
dateobj = datetime(**d)
return dateobj
except:
d['__type__'] = type
return d
def json_default_format(value):
try:
if isinstance(value, datetime):
return {
'__type__': 'datetime',
'year': value.year,
'month': value.month,
'day': value.day,
'hour': value.hour,
'minute': value.minute,
'second': value.second,
'microsecond': value.microsecond,
}
if isinstance(value, decimal.Decimal):
return float(value)
if isinstance(value, Enum):
return value.name
else:
return vars(value)
except Exception as e:
raise ValueError
Now you can use above code inside your own class :
class Foo():
def toJSON(self):
return json.loads(
json.dumps(self, sort_keys=True, indent=4, separators=(',', ': '), default=json_default_format), cls=DateTimeDecoder)
Foo().toJSON()
Try:
from pprint import pformat
a_dict = eval(pformat(an_obj))
Python 3.7+ in 2023
You can add the dataclass decorator to your class and define a custom JSON serializer, then json.dumps will work (and you can extend it to work with non-serializable attributes by providing a custom encoder to cls).
f=Foo()
json.dumps(f, cls=CustomJSONEncoder)
{"bar": "hello", "baz": "world", "modified": "2023-02-08T11:49:15.675837"}
A custom JSON serializer can be easily modified to make it compatible with any type that isn't natively JSON serializable.
from datetime import datetime
import dataclasses
import json
#dataclasses.dataclass # <<-- add this decorator
class Foo():
"""An example dataclass."""
bar: str = "hello"
baz: str = "world"
modified: datetime = Column(DateTime(timezone=True), default=datetime.utcnow)
class CustomJSONEncoder(json.JSONEncoder): # <<-- Add this custom encoder
"""Custom JSON encoder for the DB class."""
def default(self, o):
if dataclasses.is_dataclass(o): # this serializes anything dataclass can handle
return dataclasses.asdict(o)
if isinstance(o, datetime): # this adds support for datetime
return o.isoformat()
return super().default(o)
To further extend it for any non-serializable type, add another if statement to the custom encoder class that returns something serializable (e.g. str).