如何定义作为函数的枚举值?

时间:2016-10-31 08:24:03

标签: python function enums

我有一种情况需要强制执行,并为用户提供多个select函数之一的选项,作为参数传递给另一个函数:

我真的想要实现以下目标:

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

所以可以执行以下操作:

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() 

3 个答案:

答案 0 :(得分:15)

您的假设错误。值可以是任意的,它们仅限于整数。来自the documentation

  

上面的示例使用整数来表示枚举值。使用整数   简短而方便(并且由Functional API默认提供),   但没有严格执行。在绝大多数用例中,一个   不关心枚举的实际值是什么。 但是如果   值很重要,枚举可以有任意值。

然而函数的问题是它们被认为是方法定义而不是属性!

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>

您可以使用包装类或functools.partial

解决此问题
from functools import partial

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

示例运行:

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>)>

或者使用包装类:

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>>]

另请注意,如果需要,可以在枚举类中定义__call__方法,以使值可调用:

In [1]: from enum import Enum

In [2]: from functools import partial

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

In [4]: 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)

答案 1 :(得分:1)

建立在 @bakuriu 的方法之上,我只想强调我们还可以使用多个函数的字典作为值,并具有更广泛的多态性,类似于 Java 中的枚举。这是一个虚构的例子来说明我的意思:

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()

显然,字典方法在只有一个函数时没有额外的好处,所以当有多个函数时使用这种方法。确保所有值的键是统一的,否则使用不会是多态的。

__getattr__ 方法是可选的,它仅用于语法糖(即,如果没有它,mode.execute() 将变成 mode.value['execute']()

由于不能将字典设为只读,因此使用 namedtuple 会更好,并且只需要对上述内容稍作改动即可。

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)

答案 2 :(得分:0)

除了Bakuriu的答案之外...如果使用上述包装方法,则会丢失有关原始功能的信息,例如__name____repr__ 等包裹后。例如,如果您想使用sphinx生成源代码文档,这将引起问题。因此,将以下内容添加到包装器类中。

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__()