What is the difference between SimpleNamespace and empty class definition?

The following seems to work either way. What is the advantage (other than the nice repr) of using types.SimpleNamespace? Or is it the same thing?

>>> import types
>>> class Cls():
...     pass
... 
>>> foo = types.SimpleNamespace() # or foo = Cls()
>>> foo.bar = 42
>>> foo.bar
42
>>> del foo.bar
>>> foo.bar
AttributeError: 'types.SimpleNamespace' object has no attribute 'bar'

This is explained pretty well in the types module description. It shows you that types.SimpleNamespace is roughly equivalent to this:

class SimpleNamespace:
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __repr__(self):
        keys = sorted(self.__dict__)
        items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys)
        return "{}({})".format(type(self).__name__, ", ".join(items))

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

This provides the following advantages over an empty class:

  1. It allows you to initialize attributes while constructing the object: sn = SimpleNamespace(a=1, b=2)
  2. It provides a readable repr(): eval(repr(sn)) == sn
  3. It overrides the default comparison. Instead of comparing by id(), it compares attribute values instead.

A class types.SimpleNamespace provides a mechanism to instantiate an object that can hold attributes and nothing else. It is, in effect, an empty class with a fancier __init__() and a helpful __repr__():

>>> from types import SimpleNamespace
>>> sn = SimpleNamespace(x = 1, y = 2)
>>> sn
namespace(x=1, y=2)
>>> sn.z = 'foo'
>>> del(sn.x)
>>> sn
namespace(y=2, z='foo')

or

from types import SimpleNamespace

sn = SimpleNamespace(x = 1, y = 2)
print(sn)

sn.z = 'foo'
del(sn.x)
print(sn)

output:

namespace(x=1, y=2)
namespace(y=2, z='foo')

This answer may also be helpful.