2

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?

kolypto
  • 31,774
  • 17
  • 105
  • 99

1 Answers1

5

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
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Yes, simple, but then I'll also need to add `__repr__()`, `__str__()`, `__unicode__()`, and potentially more in the future :) Here, I'm asking for a kind of universal approach – kolypto Aug 26 '14 at 16:28
  • @kolypto: why complicate it this much? – Martijn Pieters Aug 26 '14 at 16:29
  • To make it user-friendly so a wrapped value really behaves like the original one. Don't you think my `type()` magic will work as expected? – kolypto Aug 26 '14 at 16:30
  • 1
    While @MartijnPieters's solution satisifies every stated requirement, it leaves surprises for the class's user. For example, `marked != 'name'` and `marked < 'name'` yield surprising results. – Robᵩ Aug 26 '14 at 16:31
  • @kolypto: sure, for special method names you'd need to provide additional proxies or your own versions. But *don't try to be everything*. – Martijn Pieters Aug 26 '14 at 16:31
  • @Robᵩ: the point was, I thought, to act properly as dictionary keys. – Martijn Pieters Aug 26 '14 at 16:31
  • @kolypto: your `Wrapper` will work too; I just prefer more explicit classes. You stated you needed something to act as the same key in dictionaries, not that it had to behave like the wrapped object in every other way. – Martijn Pieters Aug 26 '14 at 16:33
  • @MartijnPieters, thanks for the recipe anyway :) Good for completeness! – kolypto Aug 26 '14 at 16:46