【问题标题】:How do I detect and invoke a function when a python enum member is accessed访问python枚举成员时如何检测和调用函数
【发布时间】:2020-09-29 15:24:42
【问题描述】:

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

from enum import Enum

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

它如何得到以下行为:

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

我尝试过但失败的事情:

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

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

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

【问题讨论】:

    标签: python enums deprecation-warning


    【解决方案1】:

    这似乎是 subclassing 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
    

    新的基础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,
                    )
    

    我们的示例 Enum 带有已弃用的成员:

    class Foo(DeprecatedEnum):
        BAR = "bar"
        BAZ = "baz", "use something else"
    

    以及警告(来自测试脚本):

    # 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']
    

    以及Foo 中所有已弃用的成员:

    >>> print([m.name for m in Foo if m._args])
    ['BAZ']
    

    披露:我是 Python stdlib Enumenum34 backportAdvanced Enumeration (aenum) 库的作者。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多