Search code examples
pythonproxy-classes

Thin proxy class for hashables


I need some kind of thin wrapper object to mark dictionary keys, like:

d = {
  Required('name'): str,
  Optional('age'): int,
}

And these wrappers should behave like the wrapped object (comparison, hashing, etc):

marked = Required('name')
marked == 'name'  #-> True
d[marked] = 'hello'
d['name']  #-> 'hello'

With a single additional property: it should remember the added class:

isinstance(marked, Required)  #-> True

and the added class should have custom methods.

This is actually something like a mixin on hashable objects.


I dislike those heavyweight Proxy Patterns which mimic all special properties and thinking of the following idea instead:

class Wrapper(object):
    def __new__(cls, value):
        value_type = type(value)
        Type = type(
            value_type.__name__,  # same name
            (cls, value_type),  # Wrapper + bases
            {})
        Type.__new__ = value_type.__new__  # prevent recursion
        return Type(value)

    # Override method
    def __repr__(self):
        return 'Wrapper({})'.format(self)

Better ideas?


Solution

  • Your Wrapper class will work, yes. However, if all you wanted is a wrapper to act as a substitute key with extra methods, then I'd just create a dedicated class instead. Explicit is better than implicit here.

    This class only has to proxy the __eq__ and __hash__ methods:

    class HashableProxy(object):
        def __init__(self, wrapped):
            self._wrapped = wrapped
    
        def __eq__(self, other):
            return self._wrapped == other
    
        def __hash__(self):
            return hash(self._wrapped)
    
    class Required(HashableProxy):
        pass
    
    class Optional(HashableProxy):
        pass
    

    You can add methods to this as required.

    Demo:

    >>> marked = Required('name')
    >>> marked == 'name'
    True
    >>> d = {}
    >>> d[marked] = 'hello'
    >>> d['name']
    'hello'
    >>> isinstance(marked, Required)
    True