34

I have a situation where I need to enforce and give the user the option of one of a number of select functions, to be passed in as an argument to another function:

I really want to achieve something like the following:

from enum import Enum

#Trivial Function 1
def functionA():
    pass

#Trivial Function 2
def functionB():
    pass

#This is not allowed (as far as i can tell the values should be integers)
#But pseudocode for what I am after
class AvailableFunctions(Enum):
    OptionA = functionA
    OptionB = functionB

So the following can be executed:

def myUserFunction(theFunction = AvailableFunctions.OptionA):
   #Type Check
   assert isinstance(theFunction,AvailableFunctions) 

   #Execute the actual function held as value in the enum or equivalent
   return theFunction.value() 
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
Nicholas Hamilton
  • 10,044
  • 6
  • 57
  • 88
  • Another option: put the functions in a dict, with the function name as the key. – PM 2Ring Oct 31 '16 at 08:30
  • You discovered the edge case of enums... when python builds a class it adds the functional attributes as methods of the class. That's why they don't end up as values of the `Enum`. As I show in my answer it is easy to "fool" the interpreter into not making those as methods. – Bakuriu Oct 31 '16 at 09:36

5 Answers5

53

Your assumption is wrong. Values can be arbitrary, they are not limited to integers. From the documentation:

The examples above use integers for enumeration values. Using integers is short and handy (and provided by default by the Functional API), but not strictly enforced. In the vast majority of use-cases, one doesn’t care what the actual value of an enumeration is. But if the value is important, enumerations can have arbitrary values.

However the issue with functions is that they are considered to be method definitions instead of attributes!

In [1]: from enum import Enum

In [2]: def f(self, *args):
   ...:     pass
   ...: 

In [3]: class MyEnum(Enum):
   ...:     a = f
   ...:     def b(self, *args):
   ...:         print(self, args)
   ...:         

In [4]: list(MyEnum)  # it has no values
Out[4]: []

In [5]: MyEnum.a
Out[5]: <function __main__.f>

In [6]: MyEnum.b
Out[6]: <function __main__.MyEnum.b>

You can work around this by using a wrapper class or just functools.partial or (only in Python2) staticmethod:

from functools import partial

class MyEnum(Enum):
    OptionA = partial(functionA)
    OptionB = staticmethod(functionB)

Sample run:

In [7]: from functools import partial

In [8]: class MyEnum2(Enum):
   ...:     a = partial(f)
   ...:     def b(self, *args):
   ...:         print(self, args)
   ...:         

In [9]: list(MyEnum2)
Out[9]: [<MyEnum2.a: functools.partial(<function f at 0x7f4130f9aae8>)>]

In [10]: MyEnum2.a
Out[10]: <MyEnum2.a: functools.partial(<function f at 0x7f4130f9aae8>)>

Or using a wrapper class:

In [13]: class Wrapper:
    ...:     def __init__(self, f):
    ...:         self.f = f
    ...:     def __call__(self, *args, **kwargs):
    ...:         return self.f(*args, **kwargs)
    ...:     

In [14]: class MyEnum3(Enum):
    ...:     a = Wrapper(f)
    ...:     

In [15]: list(MyEnum3)
Out[15]: [<MyEnum3.a: <__main__.Wrapper object at 0x7f413075b358>>]

Also note that if you want you can define the __call__ method in your enumeration class to make the values callable:

In [1]: from enum import Enum

In [2]: def f(*args):
   ...:     print(args)
   ...:     

In [3]: class MyEnum(Enum):
   ...:     a = partial(f)
   ...:     def __call__(self, *args):
   ...:         self.value(*args)
   ...:         

In [5]: MyEnum.a(1,2,3)   # no need for MyEnum.a.value(1,2,3)
(1, 2, 3)
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • 1
    Note that the `__call__` method should contain a `return` statement or else when you call the functions, you won't get the intended response. I was stuck on that for 2 hours. Seems obvious in hindsight, but in case someone else gets stuck here... – Logister Feb 29 '20 at 20:41
  • @Logister Only if the functions return a value. – Bakuriu Mar 01 '20 at 09:57
  • 2
    No need to import `partial`, btw, you can just do `a = staticmethod(f)` instead. – mar77i Mar 17 '20 at 21:35
  • 1
    @mar77i That's right. I've edited the answer to also show that option. Thanks. – Bakuriu Mar 21 '20 at 10:46
  • I did not get `staticmethod` to work, however, `functools.partial` worked flawlessly. Thank you for this clever solution! – Eerik Sven Puudist Jun 01 '20 at 17:51
  • I can confirm that staticmethod does not work, at least not with Python 3.6. However `functools.partial` does work. – Varun Madiath Jun 05 '20 at 18:29
  • @VarunMadiath You are right, it works only in python2 with staticmethod. Clarified. The last example did "work", in the sense that you could call `MyEnum.a(1,2,3)` but that was simply because `a` was a staticmethod, it was not part of the members of the enum. In python2 it would have been. – Bakuriu Jun 06 '20 at 10:41
  • I was able to combine the `partial` approach over `lambda` functions to build polymorphism, pretty cool! – haridsv Mar 03 '21 at 09:13
7

Since Python 3.11 there is much more concise and understandable way. member and nonmember functions were added to enum among other improvements, so you can now do the following:

from enum import Enum, member

def fn(x):
    print(x)

class MyEnum(Enum):
    meth = fn
    mem = member(fn)
    @classmethod
    def this_is_a_method(cls):
        print('No, still not a member')
    def this_is_just_function():
        print('No, not a member')
    @member
    def this_is_a_member(x):
        print('Now a member!', x)

And now

>>> list(MyEnum)
[<MyEnum.mem: <function fn at ...>>, <MyEnum.this_is_a_member: <function MyEnum.this_is_a_member at ...>>]

>>> MyEnum.meth(1)
1

>>> MyEnum.mem(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'MyEnum' object is not callable

>>> MyEnum.mem.value(1)
1

>>> MyEnum.this_is_a_method()
No, still not a member

>>> MyEnum.this_is_just_function()
No, not a member

>>> MyEnum.this_is_a_member()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'MyEnum' object is not callable

>>> MyEnum.this_is_a_member.value(1)
Now a member! 1
STerliakov
  • 4,983
  • 3
  • 15
  • 37
5

Another less clunky solution is to put the functions in a tuple. As Bakuriu mentioned, you may want to make the enum callable.

from enum import Enum

def functionA():
    pass

def functionB():
    pass

class AvailableFunctions(Enum):
    OptionA = (functionA,)
    OptionB = (functionB,)

    def __call__(self, *args, **kwargs):
        self.value[0](*args, **kwargs)

Now you can use it like this:

AvailableFunctions.OptionA() # calls functionA
2

In addition to the answer of Bakuriu... If you use the wrapper approach like above you loose information about the original function like __name__, __repr__ and so on after wrapping it. This will cause problems for example if you want to use sphinx for generation of source code documentation. Therefore add the following to your wrapper class.

class wrapper:
    def __init__(self, function):
        self.function = function
        functools.update_wrapper(self, function)
    def __call__(self,*args, **kwargs):
        return self.function(*args, **kwargs)
    def __repr__(self):
        return self.function.__repr__()
pafi
  • 619
  • 1
  • 8
  • 20
2

Building on top of @bakuriu's approach, I just want to highlight that we can also use dictionaries of multiple functions as values and have a broader polymorphism, similar to enums in Java. Here is a fictitious example to show what I mean:

from enum import Enum, unique

@unique
class MyEnum(Enum):
    test = {'execute': lambda o: o.test()}
    prod = {'execute': lambda o: o.prod()}

    def __getattr__(self, name):
        if name in self.__dict__:
            return self.__dict__[name]
        elif not name.startswith("_"):
            value = self.__dict__['_value_']
            return value[name]
        raise AttributeError(name)

class Executor:
    def __init__(self, mode: MyEnum):
        self.mode = mode

    def test(self):
        print('test run')

    def prod(self):
        print('prod run')

    def execute(self):
        self.mode.execute(self)

Executor(MyEnum.test).execute()
Executor(MyEnum.prod).execute()

Obviously, the dictionary approach provides no additional benefit when there is only a single function, so use this approach when there are multiple functions. Ensure that the keys are uniform across all values as otherwise, the usage won't be polymorphic.

The __getattr__ method is optional, it is only there for syntactic sugar (i.e., without it, mode.execute() would become mode.value['execute']().

Since dictionaries can't be made readonly, using namedtuple would be better and require only minor changes to the above.

from enum import Enum, unique
from collections import namedtuple

EnumType = namedtuple("EnumType", "execute")

@unique
class MyEnum(Enum):
    test = EnumType(lambda o: o.test())
    prod = EnumType(lambda o: o.prod())

    def __getattr__(self, name):
        if name in self.__dict__:
            return self.__dict__[name]
        elif not name.startswith("_"):
            value = self.__dict__['_value_']
            return getattr(value, name)
        raise AttributeError(name)
haridsv
  • 9,065
  • 4
  • 62
  • 65