用 Python 枚举表示复杂对象

thi*_*nck 5 python enums spyder

我正在使用一个基于图形的框架,该框架由多种类型的节点和关系组成。每种类型都有一些我希望能够轻松访问的元属性。为了表示这些类型,我认为枚举是最好的选择。

我对 Python 还比较陌生,但我知道 Java 等其他语言的枚举。我想这个问题实际上可以归结为Python 中枚举的使用是否合适。我这样代表我的类型:

class Grammar(Enum):
    VERB = 'verb'
    NOUN = 'noun'
# END Grammar

class _Word():
    def __init__(self, name, meta):
        self.name = name
        self.meta = meta
    # END __init__
# END _Word

class Word(Enum):
    TYPE_A = _Word('foo', Grammar.VERB)
    TYPE_B = _Word('bar', Grammar.NOUN)
# END Word
Run Code Online (Sandbox Code Playgroud)

因此,我的每个 Word 值都分配了一个 _Word 对象,这是一个复杂类型。这在大多数使用 Enum 的情况下都可以正常工作。然而,我的同事指出,Spyder 在检查存在枚举实例的对象时抛出异常:(ValueError(): is not a valid Word注意冒号后面的空格)。

这让我认为我使用枚举的方式不是最佳实践。我错过了什么吗?

thi*_*nck 1

问题似乎是 pandas 和 json 等几个库很难序列化枚举对象。我在网上找到了几个解释如何编写自定义序列化器的解决方案。但是,由于我无法告诉 Spyder 或 Pandas 使用该序列化器,因此我需要一些不同的东西。

经过一番尝试后,我发现两种方法可以解决我的问题。不幸的是,使用 Enums 的解决方案不适用于 Python 2.7,这是我的项目的要求。因此,我将解释两者。

Python 3.4:

我将值的数据类型添加为枚举类的混合,并按照此处的建议注释枚举值的分配。特别是为了与数据帧一起使用,我还需要使我的枚举对象具有可比性,这是通过定义比较方法来实现的。最后将枚举值传递到复杂对象的构造函数中。这消除了通过 value 属性访问属性的需要。

class Grammar(str, Enum):
    VERB: str = 'verb'
    NOUN: str = 'noun'

    def __eq__(self, other):
        return not self < other and not other < self
    # END __eq__

    def __ne__(self, other):
        return self < other or other < self
    # END __ne__

    def __gt__(self, other):
        return other < self
    # END __gt__

    def __ge__(self, other):
        return not self < other
    # END __ge__

    def __le__(self, other):
        return not other < self
    # END __le__

    def __lt__(self, other):
        return self.value < other.value
    # END __lt__

    def __hash__(self):
        return hash(self.value)
    # END __hash__
# END Grammar

class _Word(dict):
    def __init__(self, name, meta):
        self['name'] = name
        self['meta'] = meta
    # END __init__
# END _Word

class Word(dict, Enum):
    TYPE_A: dict = _Word('foo', Grammar.VERB)
    TYPE_B: dict = _Word('bar', Grammar.NOUN)

    def __init__(self, _word):
        self['name'] = _word['name']
        self['meta'] = _['meta']
    # END __init__

    def __eq__(self, other):
        return not self < other and not other < self
    # END __eq__

    def __ne__(self, other):
        return self < other or other < self
    # END __ne__

    def __gt__(self, other):
        return other < self
    # END __gt__

    def __ge__(self, other):
        return not self < other
    # END __ge__

    def __le__(self, other):
        return not other < self
    # END __le__

    def __lt__(self, other):
        return self['name'] < other['name']
    # END __lt__

    def __hash__(self):
        return hash(self['name'])
    # END __hash__
# END Word
Run Code Online (Sandbox Code Playgroud)

这使得 Spyder 能够毫无问题地处理我的枚举。作为奖励,当将对象与数据帧一起使用或序列化为 json 时,通用对象表示现在将替换为实际值。这让生活变得非常轻松!

Python 2.7: 不幸的是,Python 2.7 的 Enum 实现并没有继承使我的第一个解决方案工作所需的所有语法。具体来说,VERB: str = 'verb'不允许带注释的赋值。我最终删除了枚举的使用并改用类属性。这仍然允许以明确您正在处理常量的方式进行访问(例如Grammar.VERB,但这确实意味着您失去了很好的 Enum 功能。

对我来说最重要的是能够迭代我的枚举值,因此我创建了一个函数,允许我从伪枚举中检索所有值:

class PseudoEnum():

    @classmethod
    def getAll(cls):

        """
        Returns a list of tuples representing all entries for this PseudoEnum along the dimensions key x value. This is useful when you need to iterate over the enum.

        :returns: A list of all PseudoEnum entries
        :rtype: list of tuple
        """

        return [(i, getattr(cls, i)) for i in dir(cls) if not i.startswith('__') and not callable((getattr(cls, i)))]
    # END getAll        
# END PseudoEnum
Run Code Online (Sandbox Code Playgroud)

Grammar现在Word继承自PseudoEnum. 此外,比较方法已从Word变为_WordGrammar不再需要比较方法,因为它处理常规字符串。

对于冗长的答案表示歉意。希望这可以帮助!