创建后,将混合蛋白添加到Python枚举中?
这个问题
假设我有一个枚举:
from enum import Enum, auto
class Foo(Enum):
DAVE_GROHL = auto()
MR_T = auto()
我想使用特定方法仅(即没有新的枚举成员):
class MixinFoo(Foo):
def catch_phrase(self):
if self is Foo.MR_T:
return "I pity da foo!"
else:
return "There goes my hero!"
这将失败,因为type> type>定义成员后,无法扩展设计枚举。 Python Enum允许您在另一个方向上执行此操作(即:不使用成员定义Mixin并为Mixin子类添加成员),但是在我的特定用例中,定义
mixInfoo
foo foo <<
/代码>用作基类的选择不是一个选项(例如:foo
在另一个模块中定义,我不想重新创建它或修改该模块的代码)。
到目前为止,我考虑的是:
调整enummeta
。 enummeta
检查__准备__
方法中基类中现有成员的检查,因此覆盖此方法并将支票推迟到新的元口中可以工作。不幸的是,enummeta
检查了其他几个功能中现有成员的检查,重新定义所有这些功能似乎是不好的实践(许多重复的代码)。
事后添加功能。我知道可以做类似的事情:
def catch_phrase(self):
...
Foo.catch_phrase = catch_phrase
尽管这可能起作用,但我想避免一些重要的缺点(例如:对于大量功能,静态/类方法非常麻烦,正常类定义的属性可能是难以实施)
解决继承的组成问题:
class FooWrapper():
def __init__(self, foo):
self._foo = foo
def catch_phrase(self):
...
尽管这是可能的,但我并不是这种方法的忠实拥护者。
一些相关问题:
为什么enummeta
对__准备__
之外的现有成员有检查?从设计的角度来看,这似乎不仅是多余的,而且还使得不必要地扩展enummeta
,就像我的情况下一样。我了解不允许更多具有更多成员的枚举的背后的理由,但是在枚举库的设计中显然考虑了Mixins。这是枚举设计的监督吗?是否被考虑和拒绝?是故意出于某种原因吗?
此外,现有成员的检查之一埋在_enumdict
类中,该类是无法访问的,因此我不确定如果不重新创建相当大的一部分,我想做什么枚举库。
总体意图是我有一个常见的枚举,其中成员是已知和固定的,但是用例和方法是特定于应用程序的(即,使用foo
的不同应用程序会有不同的问候要添加,但是无需添加其他foo
s)。
The Problem
Suppose I have an enum defined:
from enum import Enum, auto
class Foo(Enum):
DAVE_GROHL = auto()
MR_T = auto()
and I want to extend this class with specific methods only (ie, no new enum members):
class MixinFoo(Foo):
def catch_phrase(self):
if self is Foo.MR_T:
return "I pity da foo!"
else:
return "There goes my hero!"
This will fail with TypeError
, since by design enum's can't be extended once members have been defined. Python enum allows you to do this in the other direction (ie: define mixin with no members and subclass the mixin to add members), but in my specific use case, defining MixinFoo
before Foo
to use as a base class is not an option (eg: Foo
is defined in another module and I don't want to recreate it or modify that module's code).
What I've considered so far:
Tweaking the EnumMeta
. EnumMeta
checks for existing members in the base class in the __prepare__
method, so overriding this method and deferring the check in a new metaclass could work. Unfortunately, EnumMeta
checks for existing members in several other functions, and redefining all those functions seems like bad practice in general (lots of duplicated code).
Adding functions after the fact. I know it's possible to do something like:
def catch_phrase(self):
...
Foo.catch_phrase = catch_phrase
and while this might work, there are some significant disadvantages that I would like to avoid (eg: very cumbersome for a large number of functions, static/class methods, properties of normal class definition might be difficult to implement)
Solving the problem with composition over inheritance:
class FooWrapper():
def __init__(self, foo):
self._foo = foo
def catch_phrase(self):
...
While this is possible, I'm not a huge fan of this method.
Some Related Questions:
Why does EnumMeta
have checks for existing members outside of __prepare__
? From a design perspective, this seems to not only be redundant, but also makes it unnecessarily difficult to extend the EnumMeta
as in my case. I understand the rationale behind not allowing extension of enums with more members, but mixins were obviously considered in the design of the enum library. Was this an oversight in the design of the enum? Was it considered and rejected? Is it intentional for some reason?
Furthermore, one of the checks for existing members is buried in the _EnumDict
class, which is not accessible, so I'm not sure what I'm trying to do is possible without recreating a fairly substantial portion of the enum library.
The overall intent is that I have a common enum where the members are known and fixed, but the use case and methods are application specific (ie, different applications that use Foo
would have different greetings and possibly other functions to add, but no application would need to add other Foo
s).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
选项1:将新方法分配给现有枚举
确实简单地将方法分配给
foo
有效吗?像
foo.catch = lambda self = none:print(如果self是没有其他“ mr_t”的话,如果self是foo.mr_t else't else“ ka-bong”)
如果不是,为什么?它确实像一种方法一样行事,并且如果该方法是从成员调用的,则“自我”将被“ foo”成员填充。
如果您想从Python创建类的机制和“类”语句语法中受益,那么使用“ Pseudo” Metaclass:可召唤的可呼叫,可以占用类主体的内容,以及名称和基础, - 它可以只需将新创建的方法和属性添加到现有枚举类别:
这将使您在
此选项的情况下扩展
foo
,mixInfoo.member1是foo.member1
:foo类是猴子修补的 - 无需关心导入顺序:使用“ foo”的每个人都可以使用“ catch_phrase”。选项2 -mixInfoo!= foo:
如果必须保留独特的
foo
类,并且需要foo.member1不是mixinfoo.member.member1
sovererts true true-伪 - 米特类是倒转事物,并创建foo
的副本,该副本将从Mixin中定义的新类成员中得出。 Metaclass代码只能以相反的顺序来实现,以便枚举类稍后出现。只需将其用作上面的元素,您就会获得一个独立的“ MixInfoo”类,可以将其传递并独立于FOO。缺点是,不仅其成员“不是” foo成员,只有副本,而且MixInfoo根本不会是一个子类或其他与Foo相关的子类。
(顺便说一句,只需忽略允许一个人使用的机制,如果您不使用它们,则可以合并多个枚举。我也没有测试过该部分)
选项3 -mixInfoo!= foo,但是IssubClass(MixInfoo,foo):
如果必须将mixInfoo作为foo的一个独特类,并且仍然具有
mixInfoo.member1是foo.member1
和issubClass(mixinfoo,foo)
soverert true,好吧...当人们尝试使用Mixin方法扩展枚举时,我们拥有:
确保这是一个实现细节 - 但是此时所有的Enummeta检查都是... Member_names - 恰好是可分配的。
如果一个人简单地将虚假值分配给“ foo。 member_names ”,则可以随意通过额外的mixin方法和普通属性进行子分类。
._ Member_names _
以后可以还原。这样做,这些断言存在:
foomixin.member1是foo.member1
,foomixin不是foo
, issubclass(foomixin,foomixin,foo)。但是,新方法不能直接在成员上调用 - 也就是说,在前两个选项中,一个可以执行foomixin.member1.catch_phrase()
,因为成员枚举保持class
没有新方法,不起作用。 (解决方法是foomixin.catch_phrase(foomixin.member1
)。如果有人希望这最后一部分起作用,则foo.members可以拥有其
类__ class __ class __
属性更新为foomixin
- 它将起作用,但是原始的foo.members
也已更新Intherplo。
Option 1: assign new methods to existing Enum
Does simply assigning a method to
Foo
works?Like in
Foo.catch = lambda self=None: print(Foo if self is None else "MR_T" if self is Foo.MR_T else "Ka-bong")
If not, why? It does behave, for all purposes as a method would, and "self" is filled with the "Foo" member if the method is called from the member.
If you want to benefit from Python's mechanism for creating classes and the "class" statement block syntax, this is easily feasible with a "pseudo" metaclass: a callable that will take in the contents of the class body, along with the name and bases - it can them just add the newly created methods and attributes to the existing Enum class:
This will allow you to extend
Foo
in place withWith this option,
MixinFoo is Foo
andMixinFoo.member1 is Foo.member1
: the Foo class is monkey patched in place - no need to care about import order: everyone using "Foo" can use "catch_phrase".Option 2 - MixinFoo != Foo:
If one must preserve a distinct
Foo
class and need thatFoo.member1 is not MixinFoo.member1
asserts True - the way out, still using a pseudo-metaclass is to invert things, and create a copy ofFoo
that would derive from the new class-members defined in the Mixin. The metaclass code just does that in the reversed order, so that the Enum class comes later into the mix.Just use that as the metaclas, like above, and you get an independent "MixinFoo" class, that can be passed around and used independent from Foo. The drawback is that not only its members "are not" Foo members, just copies, but MixinFoo won't be a subclass or otherwise related to Foo at all.
(btw, just ignore the mechanisms allowing one to merge more than one Enum if you won't use them. I had not tested that part either)
Option 3 - MixinFoo != Foo, but issubclass(MixinFoo, Foo):
If one must have MixinFoo as a distinct class from Foo and still have
MixinFoo.member1 is Foo.member1
andissubclass(MixinFoo, Foo)
to assert True,Well... looking into the traceback when one tries to extend an Enum with mixin methods we have:
Sure this is an implementation detail - but all EnumMeta checks at this point is... member_names - which happens to be assignable.
If one simply assigns a falsey value to "Foo.member_names", it can be subclassed, with extra mixin methods and ordinary attributes at will. The
._member_names_
can be restored afterwards.Doing this, these assertions hold:
FooMixin.member1 is Foo.member1
,FooMixin is not Foo
,issubclass(FooMixin, Foo)
. However, the new methods can not be called directly on the members - that is, in the previous two options one can doFooMixin.member1.catch_phrase()
, while under this, as the member enum keeps the classFoo
which does not have the new method, that does not work. (The workaround isFooMixin.catch_phrase(FooMixin.member1
) .If one wants this last part to work, Foo.members can have their
__class__
attribute updated toFooMixin
- and it will work, but then the originalFoo.members
are updated inplace as well.I have the intuition this final form is what you are really asking for here -
选项4-
@jsbueno构建的IssubClass(foo,mixinfoo),从 @l4mpi in @in 这个问题。在这里,我们是monkeypatch
foo
,因此它是mixInfoo
的子类,就像典型的枚举混音一样。我们只需将mixInfoo
添加到foo
的基础:Option 4 - issubclass(Foo, MixinFoo)
Building on @jsbueno's with inspiration from an answer from @l4mpi in this question. Here we monkeypatch
Foo
such that it is a subclass ofMixinFoo
, as would be the case for a typical Enum mixin. We simply addMixinFoo
toFoo
's bases:好问题。它不再(从3.11起)。
no中,
_enumdict
in Check in 中已定义的成员 :我喜欢您的选项4 - 但是,当然,它应该是装饰:
在行动中:
Good question. It no longer does (as of 3.11).
No, the check in
_EnumDict
is for members already defined in that enum:I like your Option 4 -- but of course, it should be a decorator:
and in action: