Travis Parks
Travis Parks

Reputation: 8695

Creating Truly Empty Types in Python

On a regular basis, I want to create completely empty objects in Python. Basically, I want to have a dict where the keys are attributes. In Python 2.x, I can create an old-style type like this:

class Empty: pass

This will create a type that only has two attributes (__doc__ and __module__). In Python 3.x, everything is a new-style class, so I get 18 attributes.

In my current situation, I have a class that allows you to specify types that need monkey patched within a unit test. When the patches are applied, I am creating a type with attributes with the names of each mocked-out type. This is pretty much what my current implementation is doing:

class EmptyType: pass
...
mocks = EmptyType()
for mock_name, mock in patches:
    setattr(mocks, mock_name, mock)

My concern is that should someone be mocking a private member, they might run into naming collisions with the names in the EmptyType object. That's why I'd like to keep as few attributes in the EmptyType as possible. And it is way easier to say mocks.find_users than it is to say mocks["find_users"], especially when I know the name has to be a valid identifier.

Right now, I have provided the ability to give mocks different names, other than what would otherwise be the default. Still, it would be nice to avoid confusing errors in the first place. It is very easy to create almost empty types in JavaScript and I was hoping there was something similar in Python, since I keep finding good uses for them.

Upvotes: 4

Views: 850

Answers (1)

jdi
jdi

Reputation: 92569

What about creating your own custom container?

class Empty(object):

    def __init__(self, **kwargs):
        object.__setattr__(self, '_obj', kwargs)

    def __getattribute__(self, name):
        obj = object.__getattribute__(self, '_obj')
        try:
            return obj[name]
        except KeyError:
            cls_name = object.__getattribute__(self, '__class__').__name__
            raise AttributeError(
                "'%(cls_name)s' object has no attribute '%(name)s'" % locals())

    def __setattr__(self, name, val):
        obj = object.__getattribute__(self, '_obj')
        obj[name] = val

    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, val):
        return setattr(self, key, val)

Usage:

e = Empty(initial='optional-value')
e.initial
# 'optional-value'
e.val = 'foo'
e.val
# 'foo'
e.bad
# AttributeError: 'Empty' object has no attribute 'bad'
setattr(e, 'user', 'jdi')
e.user
# 'jdi'
e['user']
# 'jdi'

# note that you dont even see anything when you dir()
dir(e)
# []

# and trying to access _obj is protected
e._obj
#AttributeError: 'Empty' object has no attribute '_obj'

# But you could even set your own _obj attribute
e._obj = 1
e._obj
# 1

It will store everything under a _obj dictionary so you basically get a clean space that doesn't conflict with the actual instance attributes.

Upvotes: 6

Related Questions