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.
016 Answers
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... 11Instead 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 3I'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('__')) 4I 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)) 1To 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__
2A 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'} 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__)) 0I 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.
1In 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:
>>> 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')]) Python3.x
return dict((key, value) for key, value in f.__dict__.items() if not callable(value) and not key.startswith('__')) 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))