views:

138

answers:

4

Is it possible to create an object from a dictionary in python in such a way that each key is an attribute of that object?

Something like this:

 dict = { 'name': 'Oscar', 'lastName': 'Reyes', 'age':32 }

 e = Employee( dict ) 
 print e.name # Oscar 
 print e.age + 10 # 42 

I think it would be pretty much the inverse of this question: Python dictionary from an object's fields

+9  A: 

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)
Ian Clelland
If you pass initial data as `def __init__(self,**initial_data)` you get the added benefit of having an init method that can also do keyword arguments too (e.g. "e = Employee(name='Oscar')" or just take in a dictionary (e.g. "e = Employee(**dict)").
Brent Nash
Offering both the `Employee(some_dict)` and the `Employee(**some_dict)` APIs is inconsistent. Whichever is better should be supplied.
Mike Graham
(Also, you mean `if initial_data is not None`; in a strange enough circumstance, this could introduce code that does not work as intended. Also, you can't use a the key `'initial_data'` now using one of the APIs.)
Mike Graham
If you set your arg's default to `()` instead of `None`, you could do it like so: `def __init__(self, iterable=(), **kwargs): self.__dict__.update(iterable, **kwargs)`.
Matt Anderson
@Matt Anderson, that code seems a bit clever to me. It seems like a more readable solution would be logic to the effect that Ian used or, better yet, to choose a single, consistent API.
Mike Graham
@Mike Graham: Agreed, on both counts; although offering both interfaces is exactly what the python dict constructor does. I've updated the example to take any number of mapping objects, without reserving any names (except, perhaps necessarily, 'self')
Ian Clelland
@Ian Clelland, Much to its discredit if you ask me! ;) Good point, though.
Mike Graham
for key, value in initial_data.items():looks more pythonic
ralu
+4  A: 

You can access the attributes of an object with __dict__, and call the update method on it:

>>> class Employee(object):
...     def __init__(self, _dict):
...         self.__dict__.update(_dict)
... 


>>> dict = { 'name': 'Oscar', 'lastName': 'Reyes', 'age':32 }

>>> e = Employee(dict)

>>> e.name
'Oscar'

>>> e.age
32
Dave Kirby
`__dict__` is an implementation artifact and should not be used. Also, this ignores the existence of descriptors on the class.
Ignacio Vazquez-Abrams
@Ignacio what do you mean with "implementation artifact"? What we shouldn't not be aware of it? Or that it may not be present in different platforms? ( eg. Python in Windows vs. Python on Linux ) What would be an acceptable answer?
OscarRyz
I *was* going to say that there's no guarantee of it existing in a given Python implementation, but it is referred to multiple times in the langref. Ian's answer covers the other concern, i.e. passing it into a descriptor instead of clobbering it.
Ignacio Vazquez-Abrams
`__dict__` is a documented part of the language, not an implementation artifact.
Dave Kirby
@Dave is this it? http://docs.python.org/library/stdtypes.html#special-attributes
OscarRyz
If you know the class does not have any descriptors then `__dict__.update` is perfectly valid - it is done in lots of places in the standard library.
Dave Kirby
Using `setattr` is preferable to accessing `__dict__` directly. You have to keep in mind a lot of things that could lead to `__dict__` not being there or not doing what you want it to when you use `__dict__`, but `setattr` is virtually identical to actually doing `foo.bar = baz`.
Mike Graham
+3  A: 

I think that answer using settattr are the way to go if you really need to support dict.

But if Employee object is just a structure which you can access with dot syntax (.name) instead of dict syntax (['name']), you can use namedtuple like this:

from collections import namedtuple

Employee = namedtuple('Employee', 'name age')
e = Employee('noname01', 6)
print e
#>> Employee(name='noname01', age=6)

# create Employee from dictionary
d = {'name': 'noname02', 'age': 7}
e = Employee(**d)
print e
#>> Employee(name='noname02', age=7)
print e._asdict()
#>> {'age': 7, 'name': 'noname02'}

You do have _asdict() method to access all properties as dictionary, but you cannot add additional attributes later, only during the construction.

van
It's best to refer to attributes as "attributes" rather than "properties", since the latter term can be confused with what you get when you use `property`.
Mike Graham
good point - fixed
van
+3  A: 

Setting attributes in this way is almost certainly not the best way to solve a problem. Either:

  1. You know what all the fields should be ahead of time. In that case, you can set all the attributes explicitly. This would look like

    class Employee(object):
        def __init__(self, name, last_name, age):
            self.name = name
            self.last_name = last_name
            self.age = age
    
    
    d = {'name': 'Oscar', 'last_name': 'Reyes', 'age':32 }
    e = Employee(**d) 
    
    
    print e.name # Oscar 
    print e.age + 10 # 42 
    

    or

  2. You don't know what all the fields should be ahead of time. In this case, you should store the data as a dict instead of polluting an objects namespace. Attributes are for static access. This case would look like

    class Employee(object):
        def __init__(self, data):
            self.data = data
    
    
    d = {'name': 'Oscar', 'last_name': 'Reyes', 'age':32 }
    e = Employee(d) 
    
    
    print e.data['name'] # Oscar 
    print e.data['age'] + 10 # 42 
    

Another solution that is basically equivalent to case 1 is to use a collections.namedtuple. See van's answer for how to implement that.

Mike Graham