你会学到
- 描述符的
__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()
但现在值变成了数字 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__ 在类定义时自动执行,适合生成依赖类名和属性名的值 - 通过
__init__ 传入格式字符串,让描述符变得可配置
下次你需要给常量统一加前缀,试试用描述符,别手写重复的字符串。