修改枚举类 IntFlag 的 __repr__ 方法

3

我写了一个小工具,用于解码由位标志组成的日志输出。在解码这些位标志时,我使用枚举。

python --version
Python 3.8.6

出于简洁的原因,我只想打印枚举成员名称。我已将我的问题简化为以下代码片段:

from enum import IntFlag
class Foo(IntFlag):
    A = 0x01
    B = 0x02
    C = 0x04
    def __str__(self):
        return self.name
    __repr__ = __str__

如果我将它们单独显示或作为列表的一部分打印,那么它们就可以正常工作。但是我无法打印一个或表达式。我想在表格上显示标志 A|B|C

>>> Foo.A
A
>>> Foo.B
B
>>> [Foo.A, Foo.B]
[A, C]
>>> Foo.A | Foo.B
TypeError: __repr__ returned non-string (type NoneType)

更新: 我看错了源代码的版本。对于3.8.6标签,IntFlag::__repr__确实包含了打印每个成员的代码https://github.com/python/cpython/blob/v3.8.6/Lib/enum.py#L761

我尝试阅读 CPython 的Lib/enum.py,但无法弄清楚默认的 IntFlag(没有我的重写的__repr__)如何以 Foo.A|Foo.B 的形式打印输出。~~

我可以看到IntFlag.__or__通过调用__class__返回一个新对象(我不太知道__class__是什么)。而Enum.__repr__只打印单个类名和枚举名。

如何编写一个__repr__函数和一个__str__函数,以返回当前设置的枚举成员名称的字符串表示形式?


1
你正在阅读错误版本的源代码 - 你所寻找的 __repr__ 行为最近已经在主干分支中删除。 (始终查看您实际使用的版本的代码!) - user2357112
1
“enum” 里面有太多奇怪的魔法。 - user2357112
@user2357112支持Monica 哦,你说得对:https://github.com/python/cpython/blob/v3.8.6/Lib/enum.py#L761 有一个循环打印枚举成员。我也同意你的看法,enum.py 内部确实有很多东西。 - Daniel Näslund
2个回答

2
from enum import Flag, _power_of_two

class BetterFlag(Flag):
    def __repr__(self):
        if self.value == 0:
            return "%s(0)" % self.__class__.__name__
        return '|'.join(
                m.name
                for m in self.__class__
                if m.value & self.value and _power_of_two(m.value)
                )
    __str__ = __repr__

Python 3.10及更早版本通过测试name属性是否为None来执行此操作,如果是,则循环遍历内容。对于更奇特的枚举类型,循环方式存在缺陷,并在3.11中进行了重新设计。

注意:以上内容向前和向后兼容。请让您的FlagBetterFlag继承。


class 成员使得编写具有 repr 和 str 实现的 StrMixin 成为可能。对于我来说非常有用,因为我想要修改 IntEnum 和 IntFlag 两者。 - Daniel Näslund
_power_of_two真的是公共API吗?它以一个下划线开头。按惯例,这些函数不是私有的吗? - Daniel Näslund
1
这不是公共API,但它能胜任工作。你可以1)将其复制到自己的代码中;2)添加测试以便在它出现问题时知道;3)在不再需要时删除“BetterFlag”解决方法。我建议要么是(1),要么是(2)(3)。 - Ethan Furman

1

我想出了这个解决方案

class Foo(IntFlag):
    A = 0x01
    B = 0x02
    C = 0x04
    def __repr__(self):
        return '|'.join(val.name for val in Foo if self.value & val)

并在 repl 中演示:

>>> Foo.A | Foo.C | Foo.B
A|B|C
>>> Foo.A
A

1
只要你的“Flag”是简单的,你的解决方案就可以正常工作。但如果在“Flag”类定义中有命名组合,例如“AB = 0x03”,“BC = 0x06”,它将会失败。 - Ethan Furman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接