Can named arguments be used with Python enums?

核能气质少年 提交于 2019-12-06 17:01:14

问题


Example:

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

Ref: https://docs.python.org/3/library/enum.html#planet

Why do I want to do this? If there are a few primitive types (int, bool) in the constructor list, it would be nice to used named arguments.


回答1:


While you can't use named arguments the way you describe with enums, you can get a similar effect with a 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.

... which to my mind is cleaner, since you don't have to write an __init__ method.

Example use:

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

Note that, as per the docs, "mix-in types must appear before Enum itself in the sequence of bases".




回答2:


The accepted answer by @zero-piraeus can be slightly extended to allow default arguments as well. This is very handy when you have a large enum with most entries having the same value for an element.

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)

Beware pickling will not work without the __getnewargs__.

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



回答3:


If going beyond the namedtuple mix-in check out the aenum library1. Besides having a few extra bells and whistles for Enum it also supports NamedConstant and a metaclass-based NamedTuple.

Using aenum.Enum the above code could look like:

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

and in use:

--> 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 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.




回答4:


For Python 3.6.1+ the typing.NamedTuple can be used, which also allows for setting default values, which leads to prettier code. The example by @shao.lo then looks like this:

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)

This also supports pickling. The typing.Any can be used if you don't want to specify the type.

Credit to @monk-time, who's answer here inspired this solution.



来源:https://stackoverflow.com/questions/26691784/can-named-arguments-be-used-with-python-enums

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!