定制 “按值返回” 在 IntEnum 中

huangapple go评论79阅读模式
英文:

Customize "return by value" in IntEnum

问题

我有一个扩展IntEnum类的类,它定义了位编码变量中的位置:

    from enum import IntEnum
    
    class Bits(IntEnum):
    
        @classmethod
        def data(cls, value: int):
            return [ e for e in cls if (1 << e) & value ]
    
    class Status(Bits):
        READY = 0
        ERROR = 1
        WARNING = 2
    
    x = Status.data(3) # x <- [<Status.READY: 0>, <Status.ERROR: 1>]
    y = Status.data(4) # y <- [<Status.WARNING: 2>]
    z = Status.data(8) # z <- []

能否定制IntEnum的“按值返回”而不破坏任何东西呢?因为将有多个类扩展Bits类,理想情况下,这个功能应该在Bits类中实现。

我想要实现的是这样的:

    x = Status(3) # x <- [<Status.READY: 0>, <Status.ERROR: 1>]
    y = Status(4) # y <- [<Status.WARNING: 2>]
    z = Status(8) # z <- []

我尝试重写__call__方法(请参见enum — Support for enumerations),但在这种情况下似乎没有调用该方法。

英文:

I have a class that extends IntEnum class that defines positions in a bit-encoded variable:

from enum import IntEnum

class Bits(IntEnum):

    @classmethod
    def data(cls, value: int):
        return [ e for e in cls if (1 << e) & value ]

class Status(Bits):
    READY = 0
    ERROR = 1
    WARNING = 2

x = Status.data(3) # x <- [<Status.READY: 0>, <Status.ERROR: 1>]
y = Status.data(4) # y <- [<Status.WARNING: 2>]
z = Status.data(8) # z <- []

Would it be possible to customize "return by value" of IntEnum without breaking anything? Since there will be multiple classes that will extend the Bits class, ideally this functionality should be implemented in the Bits class.

What I want to achieve is this:

x = Status(3) # x <- [<Status.READY: 0>, <Status.ERROR: 1>]
y = Status(4) # y <- [<Status.WARNING: 2>]
z = Status(8) # z <- []

I tried overriding the __call__ method (see enum — Support for enumerations), but it does not seem to be called in this case.

答案1

得分: 2

是的,您可以通过为Status类定义自定义的__new__方法来定制IntEnum类的返回值。这样可以改变新实例的创建方式,而不会破坏IntEnum的功能。

英文:

Yes you could customize the return value of an IntEnum class by defining a custom __new__ method for your Status class, this way it will change the behavior of how new instances are created without breaking the functionality of the IntEnum.

from enum import IntEnum, EnumMeta

class BitsMeta(EnumMeta):
    def __call__(cls, value):
        if isinstance(value, int):
            return [e for e in cls if (1 << e.value) & value]
        return super().__call__(value)

class Bits(IntEnum, metaclass=BitsMeta):

    @classmethod
    def data(cls, value: int):
        return [e for e in cls if (1 << e.value) & value]

class Status(Bits):
    READY = 0
    ERROR = 1
    WARNING = 2

x = Status(3)  # x <- [<Status.READY: 0>, <Status.ERROR: 1>]
y = Status(4)  # y <- [<Status.WARNING: 2>]
z = Status(8)  # z <- []

print(x)
print(y)
print(z)

答案2

得分: 1

根据你需要如何使用 xyzIntFlag 可能适合你:

class Status(IntFlag):
    READY = 0
    ERROR = 1
    WARNING = 2

x = Status(3)
repr(x)
# <Status.ERROR|WARNING: 3>

Status.Error in x
# True

list(x)
# [<Status.Error: 1>, <Status.Warning: 2>]

这似乎更正确,因为 Status.READY 不应该是 Status(3) 的一部分,而 Status.WARNING 应该是。


更仔细看了一下,我看到你的值实际上是位位置 - 因此,虽然 Status.READYvalue 属性是 0,但如果传入 1,它就被视为设置... 我可以理解这可能会令人困惑。

这是我会怎么做的:

class Status(IntFlag, boundary=CONFORM):
    READY = auto()
    ERROR = auto()
    WARNING = auto()

x = Status(3)
repr(x)
# <Status.READY|ERROR: 3>

CONFORM 边界意味着超出范围的值会被忽略(就像你的原文中的 8)。

现在,我的解决方案和你的解决方案之间的区别在于单个成员显示的值:

我的

<Status.READY: 1>

你的

<Status.READY: 0>

你需要决定哪个版本在你的环境中更有意义。


披露:我是 Python 标准库中的 Enum 作者enum34 回溯高级枚举(aenum 库的作者。

英文:

Depending on how you need to use x, y, and z, IntFlag may work for you:

class Status(IntFlag):
    READY = 0
    ERROR = 1
    WARNING = 2

x = Status(3)
repr(x)
# &lt;Status.ERROR|WARNING: 3&gt;

Status.Error in x
# True

list(x)
# [&lt;Status.Error: 1&gt;, &lt;Status.Warning: 2&gt;]

This also seems more correct, since Status.READY shouldn't be part of Status(3), and Status.WARNING should be.


Looking closer, I see your values are actually bit positions -- so while the value attribute of Status.READY is 0, it's considered set if 1 is passed in... I can see that being confusing.

Here's how I would do it:

class Status(IntFlag, boundary=CONFORM):
    READY = auto()
    ERROR = auto()
    WARNING = auto()

x = Status(3)
repr(x)
# &lt;Status.READY|ERROR: 3&gt;

The CONFORM boundary means out-of-range values are ignored (like 8 in your OP).

The difference now between my solution and yours is the value shown for a single member:

# mine
&lt;Status.READY: 1&gt;

# yours
&lt;Status.READY: 0&gt;

You'll have to decide which version makes more sense in your environment.


Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

huangapple
  • 本文由 发表于 2023年4月19日 17:17:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76052775.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定