访问python枚举成员时如何检测和调用函数

Run*_*ith 6 python enums deprecation-warning

我有一个枚举,其中一些成员已被弃用:

from enum import Enum

class Foo(Enum):
    BAR = "bar"
    BAZ = "baz"  # deprecated

Run Code Online (Sandbox Code Playgroud)

它如何获得以下行为:

  • 当有人写作时Foo.BAR,一切都正常
  • 当有人写入时Foo.BAZDeprecationWarning使用warnings.warn("BAZ is deprecated", DeprecationWarning). 之后一切正常。
  • 当以其他方式访问成员时,应该应用相同的行为,例如Foo("baz")Foo["BAZ"]应该引发DeprecationWarning.

我尝试过但失败的事情:

  • 覆盖_missing_而不定义BAZ. 不起作用,因为最后我仍然需要返回现有成员一段时间(直到我们的数据库清除了已弃用的值)。但我不能动态地将成员添加到枚举中。如果我定义它,_missing_则不调用。
  • 覆盖任何__getattr__, __getattribute__. 这些在访问成员的属性时被调用,例如Foo.BAZ.boo,在访问时不调用Foo.BAZ。我想这可能是工作,如果我能改写__getattr__EnumMeta,然后进行Enum使用子元类。但是,我也不知道如何做到这一点
  • overwrite __class_getitem__:保留用于静态类型,无论如何都不会调用。
  • 滥用_generate_next_value_。此函数仅在创建类时调用,因此当类被调用一次时,无论是否调用已弃用的成员,我都会收到弃用警告。但这不是我想要的。
  • 看看这个问题。它不能解决我的问题,因为目标是在迭代过程中过滤已弃用的成员。

TLDR:访问枚举成员时如何检测和调用函数?

我正在使用 python 3.8,所以新功能很好。

Eth*_*man 6

这似乎是子类化EnumMeta是正确做法的时候之一。

_on_access每当访问成员时,新元类将运行一个方法(如果存在):

class OnAccess(EnumMeta):
    """
    runs a user-specified function whenever member is accessed
    """
    #
    def __getattribute__(cls, name):
        obj = super().__getattribute__(name)
        if isinstance(obj, Enum) and obj._on_access:
            obj._on_access()
        return obj
    #
    def __getitem__(cls, name):
        member = super().__getitem__(name)
        if member._on_access:
            member._on_access()
        return member
    #
    def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1):
        obj = super().__call__(value, names, module=module, qualname=qualname, type=type, start=start)
        if isinstance(obj, Enum) and obj._on_access:
            obj._on_access()
        return obj
Run Code Online (Sandbox Code Playgroud)

新基Enum将成员创建时的任何额外参数视为函数的参数deprecate,并_on_access仅在给出额外参数时才将属性设置为该函数:

class DeprecatedEnum(Enum, metaclass=OnAccess):
    #
    def __new__(cls, value, *args):
        member = object.__new__(cls)
        member._value_ = value
        member._args = args
        member._on_access = member.deprecate if args else None
        return member
    #
    def deprecate(self):
        args = (self.name, ) + self._args
        import warnings
        warnings.warn(
                "member %r is deprecated; %s" % args,
                DeprecationWarning,
                stacklevel=3,
                )
Run Code Online (Sandbox Code Playgroud)

以及我们Enum弃用成员的示例:

class Foo(DeprecatedEnum):
    BAR = "bar"
    BAZ = "baz", "use something else"
Run Code Online (Sandbox Code Playgroud)

和警告(来自测试脚本):

# no warning here
list(Foo)

# nor for non-deprecated members
Foo.BAR

# but direct use of deprecated members does generate warnings
Foo.BAZ
/home/ethan/test:74: DeprecationWarning: member 'BAZ' is deprecated; use something else
  Foo.BAZ

Foo('baz')
/home/ethan/test:75: DeprecationWarning: member 'BAZ' is deprecated; use something else
  Foo('baz')

Foo['BAZ']
/home/ethan/test:76: DeprecationWarning: member 'BAZ' is deprecated; use something else
  Foo['BAZ']
Run Code Online (Sandbox Code Playgroud)

以及所有已弃用的成员Foo

>>> print([m.name for m in Foo if m._args])
['BAZ']
Run Code Online (Sandbox Code Playgroud)

披露:我是Python stdlibEnumenum34backportAdvanced Enumeration ( aenum) 库的作者。