Convert dict attributes in multiple attributes in

2019-08-06 12:09发布

问题:

I have a class with a dict attribute, like this :

class MyClass:

    def __init__(self):
        self.mydict = {'var1': 'value1', 'var2': 'value2', ...}

When I want to get the values, I have to do this :

cls = MyClass()
print(cls.mydict['var1'])
print(cls.mydict['var2'])

What is the solution to get the values directly in attributes please :

cls = MyClass()
print(cls.var1)
print(cls.var2)

回答1:

You could add an additional function to the class that will be able to parse the dict and insert the relevant attributes:

def assign_variables( self ):
  for key, val in self.mydict.items():
    setattr( self, key, val )

I'm using the built-in setattr() function here to set attributes with dynamic names/values:

This is the counterpart of getattr(). The arguments are an object, a string and an arbitrary value. The string may name an existing attribute or a new attribute. The function assigns the value to the attribute, provided the object allows it.
For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.

You can call this function inside your constructor after the mydict variable is defined or even just place the loop in the constructor.



回答2:

Using setattr, you can set attribute dynamically:

>>> class MyClass:
...     def __init__(self):
...         mydict = {'var1': 'value1', 'var2': 'value2'}
...         for key, value in mydict.items():
...             setattr(self, key, value)
...
>>> instance = MyClass()
>>> instance.var1
'value1'
>>> instance.var2
'value2'


回答3:

Another solution is to implement __getattr__:

class MyClass(object):
    def __init__(self):
        self.mydict = {'var1': 'value1', 'var2': 'value2', ...}

    def __getattr__(self, name):
        try:
            return self.mydict[name]
        except KeyError:
            raise AttributeError("object %s has no attribute '%s'" % (self, name))


回答4:

You can directly update the __dict__ attribute of your object I believe:

class MyClass:

    def __init__(self):
        self.mydict = {'var1': 'value1', 'var2': 'value2', ...}
        self.__dict__.update(self.mydict)


回答5:

Another way would be to override __getattr__ and __setattr__ together, which avoids having two object references to the same attribute in the class instance object (one O.R. to value1 inside myobj.mydict['var1'], and another O.R. to value1 in myobj.__dict__['var1']):

class MyClass():
    def __init__(self):
        self.__dict__['_mydict'] = {'var1': 'value1', 'var2': 'value2'}
    #Note: using the @property (descriptor) is optional. I think it makes 
    #things more convenient.
    @property
    def mydict(self):
        return self._mydict
    #NOTE: __getattr__ won't get called if att is found in self.__dict__ first! 
    def __getattr__(self,att):
        if att in self.mydict:       # or without using @property: if att in self._mydict:
            return self.mydict[att]  #                                  return self._mydict[att]
        else:
            raise AttributeError("'{n}' object has no attribute '{a}'".format(n = type(self).__name__, a = att))
    def __setattr__(self,att,val):
        super().__setattr__(att, val)
        if att in self.mydict:
            self.mydict[att] = val   # or without using @property: self._mydict[att] = val
            self.__delattr__(att)    # remove duplicate copy of object reference to att

Note that doing it this way means you cannot add more key,value pairs to mydict unless you call the property directly, e.g.:

myobj = MyClass()
myobj.x = 1
myobj.mydict #note that result does not contain an entry for 'x'
myobj.mydict['x'] = 2
myobj.mydict #now it does

Also note that getting and deleting the mydict members will be overridden by any existing attribute of the same name (not only that, but you can't delete mydict members at all unless you also override __delattr__ to enable this behavior):

#continuing the above code session
assert myobj.x == 1 #myobj.x is coming from self.x, not from self._mydict['x']
myobj.var1 = 'a new value'
myobj.var1 #results in 'a new value' as expected, but where is it coming from? 
assert myobj.mydict['var1'] == 'a new value' #Ah: it's coming from self._mydict as expected
assert myobj.__dict__['var1'] #ERROR, as expected
del myobj.x
assert myobj.x == 2 #now myobj.x is coming from self._mydict['x'], self.x having been deleted
del myobj.var1 #raises AttributeError; attributes in mydict can't be deleted (unless you also override __delattr__

If you want to change this behavior, you have to override __getattribute__ (EDIT: which, as bruno desthuilliers notes below, is usually not a good idea).