1

The first part of this blog post does a nice job of summarizing the general use case for a dispatch table, but I can't help wondering if it could be even more succinct and elegant using Enums instead...

Of course, the big hurdle in using an Enum is that functions are automatically ignored as potential members.

So, is it possible?

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237

2 Answers2

1

In a word: Yes1.

I will show a solution using the aenum2 library -- while it is possible to do most of this with the stdlib version it would require writing extra plumbing that already exists in aenum.

First, the base class:

from aenum import Enum, enum

class CallableEnum(Enum):

    def __new__(cls, *args, **kwds):
        member = object.__new__(cls)
        member._impl = args[0]
        if member._impl.__doc__ is not None:
            member._value_ = member._impl.__doc__
        else:
            member._value_ = repr(member._impl)
        return member

    def __call__(self, *args, **kwds):
        return self._impl(*args, **kwds) 

and an example dispatch Enum:

class TestEnum(CallableEnum):

    @enum
    def hello(text):
        "a pleasant greeting"
        print('hello,', text)

    @enum
    def goodbye(text):
        print('goodbye,', text) 

and in use:

>>> list(TestEnum)
[
  <TestEnum.hello: 'a pleasant greeting'>,
  <TestEnum.goodbye: '<function goodbye at 0xb7264844>'>,
]

>>> print(TestEnum.hello)
TestEnum.hello

>>> TestEnum['hello']('how are you?')
'hello, how are you?'

>>> TestEnum['goodbye']('see you soon!')
'goodbye, see you soon!' 

1 See this answer for the standard Enum usage.

2 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

Community
  • 1
  • 1
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • unfortunately this is not picklable on OSX at least – amohr Feb 13 '20 at 00:10
  • @amohr: It seems to work on Ubuntu. Can you make a new question with the sample code you used and the errors/exceptions raised? – Ethan Furman Feb 13 '20 at 00:34
  • indeed, it was working in our docker containers but not locally. The issue is that the value on OSX ends up being the \__repr__ of the value, which is a garbage string. – amohr Feb 21 '20 at 12:22
0

So first I used the above implementation, but then realized it wasn't pickling correctly on OSX, so now I'm using this:

    def _utcnow_plus_delta(delta: timedelta, utcnow_attr: tOptional[str] = None, delta_attr: tOptional[str] = None):
        dt = datetime.utcnow()
        if utcnow_attr:
            dt = getattr(dt, utcnow_attr)
            if callable(dt):
                dt = dt()

        dt += delta

        if delta_attr:
            delta_val = getattr(dt, delta_attr)
            return delta_val() if callable(delta_val) else delta_val

        return dt


    @unique
    class DynamicValue(Enum):
        DATE_TODAY_PLUS_ONE_DAY = partial(_utcnow_plus_delta, timedelta(days=1), 'date')
        DATE_TODAY_PLUS_TWO_YEARS = partial(_utcnow_plus_delta, timedelta(weeks=52 * 2), 'date')
        DATE_TODAY_PLUS_FIVE_YEARS = partial(_utcnow_plus_delta, timedelta(weeks=52 * 5), 'date')
        DATE_TODAY_PLUS_TEN_YEARS = partial(_utcnow_plus_delta, timedelta(weeks=52 * 10), 'date')
        YEAR_TODAY_PLUS_FIVE_YEARS = partial(_utcnow_plus_delta, timedelta(weeks=52 * 5), 'date', delta_attr='year')
        TIMESTAMP_NOW_PLUS_FIVE_YEARS = partial(_utcnow_plus_delta, timedelta(weeks=52 * 5), delta_attr='timestamp')

        def __reduce_ex__(self, proto):
            assert proto >= 4
            return self.__class__.__qualname__ + '.' + self._name_

I think the above CallableEnum can be fixed by adding an appropriate reduce_ex method to ensure it gets re-created from the global value and not the repr. It's a little annoying in that you have to do enum.value() but no biggie I think

amohr
  • 455
  • 3
  • 10