Python 枚举可以使用命名参数吗?

24 投票
4 回答
4307 浏览
提问于 2025-05-01 01:09

示例:

class Planet(Enum):

    MERCURY = (mass: 3.303e+23, radius: 2.4397e6)

    def __init__(self, mass, radius):
        self.mass = mass       # in kilograms
        self.radius = radius   # in meters

参考链接:https://docs.python.org/3/library/enum.html#planet

我为什么想这么做呢?如果构造函数里有几个基本类型(比如整数int,布尔值bool),那么使用命名参数会更方便。

暂无标签

4 个回答

6

如果你想深入了解 namedtuple 之外的内容,可以看看 aenum 这个库。除了为 Enum 提供一些额外的功能外,它还支持 NamedConstant 和基于元类的 NamedTuple

使用 aenum.Enum,上面的代码可能看起来像这样:

from aenum import Enum, enum, _reduce_ex_by_name

class Planet(Enum, init='mass radius'):
    MERCURY = enum(mass=3.303e+23, radius=2.4397e6)
    VENUS   = enum(mass=4.869e+24, radius=6.0518e6)
    EARTH   = enum(mass=5.976e+24, radius=3.3972e6)
    # replace __reduce_ex__ so pickling works
    __reduce_ex__ = _reduce_ex_by_name

在实际使用中:

--> for p in Planet:
...     print(repr(p))
<Planet.MERCURY: enum(radius=2439700.0, mass=3.3030000000000001e+23)>
<Planet.EARTH: enum(radius=3397200.0, mass=5.9760000000000004e+24)>
<Planet.VENUS: enum(radius=6051800.0, mass=4.8690000000000001e+24)>

--> print(Planet.VENUS.mass)
4.869e+24

1 说明:我就是 Python 标准库中的 Enumenum34 的回溯版本,以及 高级枚举库 (aenum) 的作者。

12

对于Python 3.6.1及以上版本,可以使用typing.NamedTuple,这个工具可以让你设置默认值,从而使代码看起来更整洁。@shao.lo的例子就变成了这样:

from enum import Enum
from typing import NamedTuple


class Body(NamedTuple):
    mass: float
    radius: float
    moons: int=0


class Planet(Body, Enum):
    MERCURY = Body(mass=3.303e+23, radius=2.4397e6)
    VENUS   = Body(mass=4.869e+24, radius=6.0518e6)
    EARTH   = Body(5.976e+24, 3.3972e6, moons=1)

这个方法也支持序列化(pickling)。如果你不想指定具体类型,可以使用typing.Any。

感谢@monk-time,他的回答在这里启发了这个解决方案。

14

@zero-piraeus 提出的答案可以稍微扩展一下,让默认参数也能使用。这在你有一个很大的枚举(enum)时特别有用,因为大多数条目在某个元素上的值是相同的。

class Body(namedtuple('Body', "mass radius moons")):
    def __new__(cls, mass, radius, moons=0):
        return super().__new__(cls, mass, radius, moons)
    def __getnewargs__(self):
        return (self.mass, self.radius, self.moons)

class Planet(Body, Enum):

    MERCURY = Body(mass=3.303e+23, radius=2.4397e6)
    VENUS   = Body(mass=4.869e+24, radius=6.0518e6)
    EARTH   = Body(5.976e+24, 3.3972e6, moons=1)

需要注意的是,如果没有 __getnewargs__,序列化(pickling)将无法正常工作。

class Foo:
    def __init__(self):
        self.planet = Planet.EARTH  # pickle error in deepcopy

from copy import deepcopy

f1 = Foo()
f2 = deepcopy(f1)  # pickle error here
35

虽然你不能像你描述的那样用枚举(enum)来使用命名参数,但你可以通过一个叫做 namedtuple 的混合类(mixin)来达到类似的效果:

from collections import namedtuple
from enum import Enum

Body = namedtuple("Body", ["mass", "radius"])

class Planet(Body, Enum):

    MERCURY = Body(mass=3.303e+23, radius=2.4397e6)
    VENUS   = Body(mass=4.869e+24, radius=6.0518e6)
    EARTH   = Body(mass=5.976e+24, radius=3.3972e6)
    # ... etc.

我觉得这样更简洁,因为你不需要写一个 __init__ 方法。

使用示例:

>>> Planet.MERCURY
<Planet.MERCURY: Body(mass=3.303e+23, radius=2439700.0)>
>>> Planet.EARTH.mass
5.976e+24
>>> Planet.VENUS.radius
6051800.0

需要注意的是,根据 文档 的说明,“混合类型必须在 Enum 本身之前出现在基类的顺序中”。

撰写回答