On Fri, Apr 14, 2017 at 06:06:29PM -0700, Stephan Hoyer wrote: > One way that I've found myself using enums recently is for dispatching (as > keys in a dictionary) between different interchangeable functions or > classes. My code looks something like this: > > from enum import Enum > > def foo(...): > ... > > def bar(...): > ... > > class Implementation(Enum): > FOO = 1 > BAR = 2 > > _IMPLEMENTATIONS = { > Implementation.FOO: foo, > Implementation.BAR: bar, > } > > def call_implementation(implementation, *args, **kwargs): > return _IMPLEMENTATIONS[implementation](*args, **kwargs)
To me, this looks like a case where you want to give each instance a custom per-instance method. (Surely this must be a named Design Pattern?) There's not really good syntax for that, although if the method can be written with lambda you can at least avoid a global function: from types import MethodType class Colour(Enum): RED = 1 BLUE = 2 Colour.RED.paint = MethodType( lambda self: print("Painting the town", self.name), Colour.RED) Colour.BLUE.paint = MethodType( lambda self: print("I'm feeling", self.name), Colour.BLUE) but of course one can write a helper function or decorator to make it a little less ugly. See below. This doesn't work for dunder methods, not directly. You can't say: Colour.RED.__call__ = ... to make the RED instance callable. But you can do this: class Colour(Enum): RED = 1 BLUE = 2 def __call__(self): return self._call() Colour.RED._call = MethodType( ... ) and now Colours.RED() will call your per-instance method. We can use a decorator as a helper: # untested def add_per_instance_method(instance): def decorator(function): instance._call = MethodType(function, instance) return function return decorator and now this should work: @add_per_instance_method(Colour.RED) def _call(self): # implementation goes here ... @add_per_instance_method(Colour.BLUE) def _call(self): ... The only thing left to do is clean up at the end and remove the left over namespace pollution: del _call if you can be bothered. And now you have callable Enums with a per-instance method each, as well as a pretty enumeration value for debugging. Much nicer than <function foo at 0xb7b02cd4>. Does this solve your problem? If not, what's missing? [...] > Obviously, enums are better than strings, because they're static declared > and already grouped together. But it would be nice if we could do one > better, by eliminating the dictionary, moving the dictionary values to the > enum and making the enum instances. I think you missed a word. Making the enum instances... what? Callable? [...] > The problem is that when you assign a function to an Enum, it treats it as > a method instead of an enum value: > http://stackoverflow.com/questions/40338652/how-to-define-enum-values-that-are-functions That's a minor, and not very important, limitation. I consider that equivalent to the restriction that functions defined inside a class body are automatically converted to instance methods. If you want to avoid that, you need to decorate them as a class method or static method or similar. > Instead, you need to wrap function values in a callable class, e.g., > > from functools import partial > > class Implementation(CallableEnum): > FOO = partial(foo) > BAR = partial(bar) > > This is OK, but definitely uglier and more error prone than necessary. It's > easy to forget to add a partial, which results in an accidental method > declaration. Python doesn't have to protect the programmer from every possible source of human error. I don't think it is Python's responsibility to protect people from accidentally doing something like this: class Colours(Enum): RED = partial(red) BLUE = partial(blue) GREEN = partial(green) YELLOW = yellow # oops Sometimes the answer is Then Don't Do That. > It would be nice to have a CallableEnum class that works like Enum, but > adds the __call_ method and doesn't allow defining any new methods: all > functions assigned in the class body become Enum instances instead of > methods. I wouldn't be happy with the restriction "all methods are Enum instances". Seems over-eager. It might be suitable for *your* specific use-case, but I expect that other users of Enum will want to have both methods and functions as Enum values: class Thing(Enum): WIDGET = 'x' DOODAD = 5 GIZMO = function # how to protect this? THINGAMAJIG = 'something' def method(self, arg): ... Given that wanting to use a function as the enumeration value is quite unusual in the first place, I don't think this belongs in the standard library. -- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/