我有一种情况需要强制执行,并为用户提供多个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()
答案 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__()