当前位置:首页>python>写了三年 Python 枚举,今天才发现 auto() 没法加前缀

写了三年 Python 枚举,今天才发现 auto() 没法加前缀

  • 2026-09-02 19:06:20
写了三年 Python 枚举,今天才发现 auto() 没法加前缀

你会学到

  • 为什么普通类属性定义字符串枚举很繁琐
  • 用枚举类有什么局限
  • 描述符的 __set_name__ 和 __get__ 如何工作
  • 怎样让描述符支持自定义格式

你有一组常量,想通过类属性访问:

classLogin:
    USER_NAME = 'login.user_name'
    PASSWORD = 'login.password'

用起来没问题:

print(Login.USER_NAME)   # login.user_name

但定义的时候,属性名和右边的值重复了。你有几十个这样的常量,每个都要写两遍语义相同的东西。

Python 的枚举来了:

from enum import Enum, auto

classLogin(Enum):
    USER_NAME = auto()
    PASSWORD = auto()
  • auto() 自动生成值,通常从 1 开始递增
  • 解决了重复写字符串的问题

但现在值变成了数字 1、2,不是你要的 login.user_name 格式。而且你没法给每个值统一加 login. 前缀。

你需要一种方式:自动生成 类名.属性名 格式的字符串。

描述符可以做到:

classPrefix:
def__set_name__(self, owner, name):
        self.prefix = f'{owner.__name__}.{name}'

def__get__(self, obj, objtype=None):
return self.prefix

classLogin:
    USER_NAME = Prefix()
    PASSWORD = Prefix()
  • __set_name__ 在类定义时被调用,owner 是类本身(Login),name 是属性名(USER_NAME)
  • 这里计算出 Login.USER_NAME 字符串存到 self.prefix
  • __get__ 在读取属性时被调用,直接返回存好的字符串

执行流程(类定义时):

class Login: 
    USER_NAME = Prefix()
        │
        ▼
Prefix().__set_name__(Login, 'USER_NAME')
        │
        ▼
self.prefix = 'Login.USER_NAME'

现在 Login.USER_NAME 得到字符串 'Login.USER_NAME'。

但你还想要模块名。比如文件叫 login.py,你要 login.Login.USER_NAME:

import inspect
from pathlib import Path

classPrefix:
def__set_name__(self, owner, name):
        module = inspect.getmodule(owner)
        module_name = Path(module.__file__).stem
        self.value = f'{module_name}.{owner.__name__}.{name}'

def__get__(self, obj, objtype=None):
return self.value

classLogin:
    USER_NAME = Prefix()
    PASSWORD = Prefix()
  • inspect.getmodule(owner) 拿到类所在的模块对象
  • Path(module.__file__).stem 提取文件名(不含 .py)

现在 Login.USER_NAME 输出 login.Login.USER_NAME。

还不够。你有时想自定义格式,比如用竖线分隔,或者不要模块名:

classPrefix:
def__init__(self, format_string=None):
        self.format_string = format_string

def__set_name__(self, owner, name):
        module = inspect.getmodule(owner)
        module_name = Path(module.__file__).stem
        class_name = owner.__name__

if self.format_string:
            self.value = self.format_string.format(
                module=module_name,
                class_name=class_name,
                attr_name=name
            )
else:
            self.value = f'{module_name}.{class_name}.{name}'

def__get__(self, obj, objtype=None):
return self.value
  • __init__ 接收格式字符串,可以不传(用默认格式)
  • __set_name__ 里用 format() 替换占位符

使用示例:

classLogin:
    USER_NAME = Prefix()                     # login.Login.USER_NAME
    PASSWORD = Prefix('{class_name}|{attr_name}')  # Login|PASSWORD
  • 不传参数时,用默认的 模块.类.属性
  • 传入自定义字符串时,可以自由组合 module、class_name、attr_name

核心要点

  • 普通类属性定义字符串常量,属性名和值语义重复
  • 枚举类的 auto() 只生成递增数字,不能加前缀
  • 描述符的 __set_name__ 在类定义时自动执行,适合生成依赖类名和属性名的值
  • __get__ 控制属性读取时的返回内容
  • 通过 __init__ 传入格式字符串,让描述符变得可配置

下次你需要给常量统一加前缀,试试用描述符,别手写重复的字符串。

最新文章

随机文章