You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python 3.11+创建无参数__init__的Enum:旧代码失效问题

Python 3.11+ 枚举空元组赋值异常问题

问题背景

我有一段在Python 3.9和3.10中正常运行的枚举代码:

from enum import Enum


class Environment(Enum):
    DEV = ()
    INT = ()
    PROD = ()

    def __init__(self):
        self._value_ = self.name.lower()

    def __str__(self):
        return self.name

    def get_url(self):
        if self is Environment.DEV:
            return 'http://localhost'
        else:
            return f'https://{self.value}.my.domain'

在Python 3.9/3.10的REPL中执行结果符合预期:

>>> Environment.DEV.get_url()
'http://localhost'
>>> Environment.INT.get_url()
'https://int.my.domain'
>>> Environment.PROD.get_url()
'https://prod.my.domain'

这么写是为了避免冗余易出错的显式赋值,比如:

DEV = 'DEV'
    INT = 'INT'
    PROD = 'PROD'

但在Python 3.11或3.12中运行时,所有枚举值都指向第一个枚举项,get_url调用全部返回localhost:

>>> Environment.DEV.get_url()
'http://localhost'
>>> Environment.INT.get_url()
'http://localhost'
>>> Environment.PROD.get_url()
'http://localhost'
>>> Environment.DEV
<Environment.DEV: 'dev'>
>>> Environment.INT
<Environment.DEV: 'dev'>
>>> Environment.PROD
<Environment.DEV: 'dev'>

请问Python 3.11中是否改变了空元组赋值的规则?如何在3.11+版本中实现相同效果,即无需显式传参给__init__来声明枚举值?


原因分析

Python 3.11对Enum的内部实现做了调整:当枚举成员赋值为相同的不可变值时,会被视为同一个成员的别名。空元组()是不可变对象,所有成员都赋值为(),在3.11+中会被判定为重复值,后续成员会直接指向第一个创建的枚举实例(也就是DEV),这就导致所有成员看起来都是同一个。

而在3.11之前,Enum的成员值校验逻辑没有这么严格,允许通过__init__方法在实例化后修改_value_来覆盖初始值,不会触发别名逻辑。


解决方案

要在3.11+中实现无需显式赋值、自动用小写名称作为枚举值的效果,有几种靠谱的方式:

方式1:使用enum.auto()配合自定义_generate_next_value_

Enum提供了auto()用于自动生成值,同时可以重写_generate_next_value_方法来控制自动生成的规则:

from enum import Enum, auto


class Environment(Enum):
    DEV = auto()
    INT = auto()
    PROD = auto()

    def _generate_next_value_(name, start, count, last_values):
        # 直接返回成员名称的小写形式作为值
        return name.lower()

    def __str__(self):
        return self.name

    def get_url(self):
        if self is Environment.DEV:
            return 'http://localhost'
        else:
            return f'https://{self.value}.my.domain'

方式2:利用__new__方法初始化

这种方式依然可以用空元组占位,但通过__new__在实例创建时就设置好_value_,避免3.11+的别名逻辑触发:

from enum import Enum


class Environment(Enum):
    DEV = ()
    INT = ()
    PROD = ()

    def __new__(cls):
        instance = object.__new__(cls)
        # 获取当前正在创建的成员名称,转为小写作为值
        instance._value_ = cls._member_names_[len(cls._member_map_)].lower()
        return instance

    def __str__(self):
        return self.name

    def get_url(self):
        if self is Environment.DEV:
            return 'http://localhost'
        else:
            return f'https://{self.value}.my.domain'

验证效果

在Python 3.11+中运行上述方案的代码,会得到和3.9/3.10一致的结果:

>>> Environment.DEV.get_url()
'http://localhost'
>>> Environment.INT.get_url()
'https://int.my.domain'
>>> Environment.PROD.get_url()
'https://prod.my.domain'
>>> Environment.DEV
<Environment.DEV: 'dev'>
>>> Environment.INT
<Environment.INT: 'int'>
>>> Environment.PROD
<Environment.PROD: 'prod'>

内容的提问来源于stack exchange,提问作者Aisteru Firë

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 05:13:28