pythonpython-2.7

How can I convert python class with slots to dictionary?


I am using class with slots to reduce the memory which the instance will occupy. Now, how can I convert a slot instance to dictionary ?

The slot class look like this :

class Foo(object):
       __slots__ = ['x','y','z']
       def __init__(self):
           self.x = 1
           self.y = 2 
           self.z = 3

I expect something like this:

y = Foo()
y.__dict__
{'x': 1, 'y': 2, 'z': 3}

Solution

  • Use the __slots__ attribute plus getattr() in a dictionary comprehension:

    {
        s: getattr(obj, s)
        for s in {
            s
            for cls in type(obj).__mro__
            for s in getattr(cls, '__slots__', ())
        }
        if hasattr(obj, s)
    }
    

    which collects the slot names from all base classes and skips any attributes not set.

    Alternative, setting missing attributes to None:

    {
        s: getattr(obj, s, None)
        for s in {
            s
            for cls in type(obj).__mro__
            for s in getattr(cls, '__slots__', ())
        }
    }
    

    Demo:

    >>> class Foo(object):
    ...     __slots__ = ('bar', 'spam')
    ... 
    >>> obj = Foo()
    >>> obj.bar = 42
    >>> {s: getattr(obj, s) for s in {s for cls in type(obj).__mro__ for s in getattr(cls, '__slots__', ())} if hasattr(obj, s)}
    {'bar': 42}
    >>> {s: getattr(obj, s, None) for s in {s for cls in type(obj).__mro__ for s in getattr(cls, '__slots__', ())}}
    {'spam': None, 'bar': 42}
    

    You can even make that a property of the class and vars() will make use of it:

    >>> class Foo(object):
    ...     __slots__ = ('bar', 'spam')
    ...     @property
    ...     def __dict__(self):
    ...         return {
    ...             s: getattr(self, s)
    ...             for s in {
    ...                 s
    ...                 for cls in type(self).__mro__
    ...                 for s in getattr(cls, '__slots__', ())
    ...             }
    ...             if hasattr(self, s)
    ...         }
    ... 
    >>> f = Foo()
    >>> f.bar = 42
    >>> f.__dict__
    {'bar': 42}
    >>> f.spam = 'eggs'
    >>> f.__dict__
    {'spam': 'eggs', 'bar': 42}
    >>> vars(f)
    {'spam': 'eggs', 'bar': 42}
    >>> f.hello = 'world'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Foo' object has no attribute 'hello'
    

    However, there probably is a lot of code around that makes other assumptions about the __dict__ attribute on instances that might make using the above in production code more problematic than it’s worth.