【问题标题】:Representing complex objects in a Python enum在 Python 枚举中表示复杂对象
【发布时间】:2018-05-18 05:56:25
【问题描述】:

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

我对 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

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

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

【问题讨论】:

  • 如果不完全了解您要完成的工作,很难说。我从未见过 Enums 用于跟踪任何比简单的簿记更复杂的关系集。在 Python3.4 之前,枚举甚至不是一种语言特性,因此该语言显然没有将它们设计为必要的结构,而是为了方便而添加了更多。您想准确捕捉哪些关系?
  • 我认为这是一个有效的表示,但是,如果框架 API 没有启用它,您可能需要问自己为什么需要访问类型的元属性......也许你没有按预期使用框架,或者,也许你不应该使用一个?
  • 我正在尝试捕获我的图形框架的语义。例如,将节点视为单词,将图形视为句子。对于我的每一句话,我都想知道它的定义,例如不管是动词还是名词。由于框架的语义不应该随着时间而改变,我认为将其建模为枚举常量很有意义。编辑:我所说的框架并不是一个 python 库,而是一组关于如何使用节点和关系类型的规则(如果你愿意的话,一个语法)
  • @thijsfranck:你能用动词/名词/语法风格而不是 a/b/foo/bar 风格重做你的例子吗?很难看出你实际上想用通用名称来完成什么。
  • 这很好。我改变了我的例子!

标签: python enums spyder


【解决方案1】:

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

在玩了一会儿之后,我发现了两种解决我问题的方法。不幸的是,使用 Enums 的解决方案不适用于我的项目所需的 Python 2.7。因此,我将解释两者。

Python 3.4:

我将值的数据类型添加为我的 Enum 类的 mixin,并按照建议 here 注释我的枚举值的分配。特别是与数据框一起使用时,我还需要使我的枚举对象具有可比性,我通过定义比较方法来做到这一点。最后将枚举值传递给我的复杂对象的构造函数。这消除了通过 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

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

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

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

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

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

为冗长的回答道歉。希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多