Replacing default __dict__ for object with Ordered

2019-05-07 08:54发布

问题:

I have two classes in my code. first is the parent, which second inherits.

class first(object):
    def __init(self,**kwargs):  
        pass

    def __setattr__(self,name,value):
        self.__dict__[name] = value

class second(first):
    def do_something(self):
        self.a = 1
        self.b = 2
        self.c = 3

when I am printing the class second (by e.g. second.__dict__) I get the unordered dictionary. This is obvious. I want to change this behavior to get an ordered dictionary using the OrderedDict class, but it does not work. I am changing implementation of first in the following way:

class first(OrderedDict):   
    def __init__(self,**kwargs):  
        super(first,self).__init__(**kwargs)  
    def __setattr__(self,name_value):  
        super(first,self).__setattr__(name_value)  

I would like to print second using __dict__ or __repr__, but I got the unordered dictionary. What should I change?

回答1:

You can simply redirect all attribute access to an OrderedDict:

class first(object):
    def __init__(self, *args, **kwargs):  
        self._attrs = OrderedDict(*args, **kwargs)

    def __getattr__(self, name):
        try:
            return self._attrs[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        if name == '_attrs':
            return super(first, self).__setattr__(name, value)
        self._attrs[name] = value

Demo:

>>> from collections import OrderedDict
>>> class first(object):
...     def __init__(self, *args, **kwargs):  
...         self._attrs = OrderedDict(*args, **kwargs)
...     def __getattr__(self, name):
...         try:
...             return self._attrs[name]
...         except KeyError:
...             raise AttributeError(name)
...     def __setattr__(self, name, value):
...         if name == '_attrs':
...             return super(first, self).__setattr__(name, value)
...         self._attrs[name] = value
... 
>>> class second(first):
...     def do_something(self):
...         self.a = 1
...         self.b = 2
...         self.c = 3
... 
>>> s = second()
>>> s.do_something()
>>> s._attrs
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

You can't otherwise replace the __dict__ attribute with an OrderedDict instance, because Python optimises instance attribute access by using the concrete class API to access the dictionary internals in C, bypassing the OrderedDict.__setitem__ hook altogether (see issue #1475692).



回答2:

I think the solutions in this thread focus too much on using OrderedDict as if it is a necessity. The class already has a builtin __dict__ method, the only problem is ordering the keys. Here is how I am retrieving (key, value) pairs from my class in the order they are entered:

class MyClass:

    def __init__(self, arg1, arg2, arg3):
        self._keys = []
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def __setattr__(self, key, value):
        # store new attribute (key, value) pairs in builtin __dict__
        self.__dict__[key] = value
        # store the keys in self._keys in the order that they are initialized
        # do not store '_keys' itelf and don't enter any key more than once 
        if key not in ['_keys'] + self._keys:
            self._keys.append(key)

    def items(self):
        # retrieve (key, value) pairs in the order they were initialized using _keys
        return [(k, self.__dict__[k]) for k in self._keys]

>>> x = MyClass('apple', 'orange', 'banana')
>>> print x.items()
[('arg1', 'apple'), ('arg2', 'orange'), ('arg3', 'banana')]
>>> x.arg1 = 'pear'
>>> print x.items()
[('arg1', 'pear'), ('arg2', 'orange'), ('arg3', 'banana')]

I'm using a class to store about 70 variables used to configure and run a much bigger program. I save a text copy of the initial (key, value) pairs that can be used to initialize new instances of the class. I also save a text copy of the (key, value) pairs after running the program because several of them are set or altered during the program run. Having the (key, value) pairs in order simply improves the readability of the text file when I want to scan through the results.



回答3:

You can try by actually replacing __dict__ with OrderedDict:

from collections import OrderedDict

class Test(object):
    def __init__(self):
        self.__dict__ = OrderedDict()
        self.__dict__['a'] = 0
        self.__dict__['b'] = 1
        self.__dict__['c'] = 2

test = Test()
print test.__dict__
test.a, test.b, test.c = 'a', 'b', 'c'
print test.__dict__

This should printout:

OrderedDict([('a', 0), ('b', 1), ('c', 2)])
OrderedDict([('a', 'a'), ('b', 'b'), ('c', 'c')])


回答4:

Another option; you can also manipulate new if you wish.

from collections import OrderedDict
class OrderedClassMeta(type):
    @classmethod
        def __prepare__(cls, name, bases, **kwds):
        return OrderedDict()
class OrderedClass(metaclass=OrderedClassMeta):
    pass

class A(OrderedClass):
    def __init__(self):
        self.b=1
        self.a=2
    def do(self):
        print('do')
class B(OrderedClass):
    def __init__(self):
        self.a=1
        self.b=2
    def do(self):
        print('do')

 a=A()
 print(a.__dict__)
 b=B()
 print(b.__dict__)