代码之家  ›  专栏  ›  技术社区  ›  max

用于ORM的python枚举类

  •  4
  • max  · 技术社区  · 14 年前

    编辑问题

    我正在尝试创建一个类工厂,它可以生成具有以下属性的枚举类:

    1. 类是从列表初始化的 允许值(即 自动生成!).
    2. 类创建自身的一个实例 对于每个允许值。
    3. 类不允许创建 一旦 以上步骤已完成(任何尝试 这样做会导致异常)。
    4. 类实例提供了一个方法 如果给定一个值,则返回 引用相应的 实例。
    5. 类实例只有两个 属性:ID和值。这个 属性ID自动递增 每个新实例;属性 value是实例的值 代表。
    6. 类是不可更改的。我更愿意 使用 the accepted answer to another SO question (具体来说,通过利用课堂 注册和定义 伊特尔 元类中的方法, 枚举类已实例化)。

    这就是我要找的。请考虑原文(以下)只是问题的背景。很抱歉一开始就不清楚。

    更新答案

    我对阿罗纳斯特林非常有用的回答做了细微的修改。我想我会在这里展示它,这样别人就可以受益,如果我做错了,我会收到更多的评论:)

    我所做的修改是:

    (0)移植到p3k(ITeritems-->项, 元类 -->“metaclass=”,无需将对象指定为基类)

    (1)把实例方法改为@class method(现在我不需要对象来调用它,只需要类)

    (2)每次构建一个新元素时,我都会更新它,而不是一次性填充注册表。这意味着我可以使用它的长度来设置ID,所以我去掉了下一个ID属性。对于扩展I计划(见下文),它也会更好地工作。

    (3)从枚举()中删除了classname参数。毕竟,该类名将是一个本地名称;无论如何,全局名称都必须单独设置。所以我用了一个虚拟的'xxx'作为本地类名。当我第二次调用函数时,我有点担心会发生什么,但它似乎可以工作。如果有人知道原因,请告诉我。如果这是个坏主意,我当然可以在每次调用时自动生成一个新的本地类名。

    (4)扩展这个类以允许用户添加新的枚举元素。具体来说,如果使用不存在的值调用instance(),则会创建相应的对象,然后由方法返回。如果我从分析文件中获取大量枚举值,这将非常有用。

    def enum(values):
        class EnumType(metaclass = IterRegistry):
            _registry = {}
            def __init__(self, value):
                self.value = value
                self.id = len(type(self)._registry)
                type(self)._registry[value] = self
    
            def __repr__(self):
                return self.value
    
            @classmethod
            def instance(cls, value):
                return cls._registry[value]
    
        cls = type('XXX', (EnumType, ), {})
        for value in values:
            cls(value)
    
        def __new__(cls, value):
            if value in cls._registry:
                return cls._registry[value]
            else:
                if cls.frozen:
                    raise TypeError('No more instances allowed')
                else:
                    return object.__new__(cls)
    
        cls.__new__ = staticmethod(__new__)
        return cls
    

    原文

    我使用SQLAlchemy作为对象关系映射工具。它允许我将类映射到SQL数据库中的表中。

    我有几节课。一个类(书)是具有一些实例数据的典型类。其他类型(体裁、类型、封面等)基本上都是枚举型;例如体裁只能是“科幻”、“浪漫”、“漫画”、“科学”;封面只能是“硬”、“软”等。书和其他每门课之间有多对一的关系。

    我想半自动地生成每个枚举样式类。请注意,sqlacalchemy要求“scfi”表示为类流派的一个实例;换句话说,它不能简单地定义流派。scfi=0、流派。romance=1等。

    我试图编写一个元类枚举,它接受类的名称和允许值的列表作为参数。我希望

    Genre = enum('Genre', ['scifi', 'romance', 'comic', 'science'])
    

    将创建一个类,允许这些特定的值,并四处走动,创建我需要的每个对象:流派(“scfi”)、流派(“romance”)等。

    但我被困住了。一个特别的问题是,在ORM意识到这个类之前,我不能创建流派(“scfi”);另一方面,当ORM知道流派时,我们已经不在类构造函数中了。

    另外,我不确定我的方法是否适合开始。

    任何建议都将不胜感激。

    3 回复  |  直到 14 年前
        1
  •  3
  •   aaronasterling    14 年前

    基于更新的新答案

    我认为这满足了你所有的特定要求。如果没有,我们可能可以添加您需要的任何内容。

    def enum(classname, values):
        class EnumMeta(type):
            def __iter__(cls):
                return cls._instances.itervalues()
    
        class EnumType(object):
            __metaclass__ = EnumMeta
            _instances = {}
            _next_id = 0
            def __init__(self, value):
                self.value = value
                self.id = type(self)._next_id
                type(self)._next_id += 1
    
            def instance(self, value):
                return type(self)._instances[value]
    
        cls = type(classname, (EnumType, ), {})
        instances = dict((value, cls(value)) for value in values)
        cls._instances = instances
    
        def __new__(cls, value):
            raise TypeError('No more instances allowed')
    
        cls.__new__ = staticmethod(__new__)
        return cls
    
    
    Genre = enum('Genre', ['scifi', 'comic', 'science'])
    
    
    for item in Genre:
        print item, item.value, item.id
        assert(item is Genre(item.value))
        assert(item is item.instance(item.value))
    
    Genre('romance')
    

    旧答案

    为了回应你对诺蒂斯天空塔的回答,你说你想 Genre.comic = Genre('comic') (未经测试):

    class Genre(GenreBase):
        genres = ['comic', 'scifi', ... ]
        def __getattr__(self, attr):
            if attr in type(self).genres:
                self.__dict__[attr] = type(self)(attr)
            return self.__dict__[attr]
    

    这将创建一个流派实例以响应对其进行访问的尝试,并将其附加到请求它的实例。如果要将它附加到整个类,请替换该行

    self.__dict__[attr] == type(self)(attr) 
    

    具有

    type(self).__dict__[attr] = type(self)(attr)
    

    它还具有所有子类创建子类的实例以响应请求。如果希望子类创建 Genre 替换 type(self)(attr) 具有 Genre(attr)

        2
  •  0
  •   Katriel    14 年前

    您可以使用 type 建筑:

    类型(名称、基、dict)

    返回新类型对象。这是 本质上是 类语句。名称字符串是 类名称并成为 __name__ 属性;基元组 将基类逐项化并成为 这个 __bases__ 属性;以及dict 字典是包含 类体的定义并成为 这个 __dict__ 属性。例如, 以下两个语句创建 相同类型的对象:

    >>> class X(object):
    ...     a = 1
    ...
    >>> X = type('X', (object,), dict(a=1))
    
    New in version 2.2.
    

    在这种情况下:

    genre_mapping = { }
    for genre in { 'scifi', 'romance', 'comic', 'science' }:
        genre_mapping[ 'genre' ] = type( genre, ( Genre, ), { } )
    

    或者在python 2.7+中:

    genre_mapping = { genre: type( genre, ( Genre, ), { } ) for genre in genres }
    

    如果你经常这样做,你可以把模式抽象掉。

    >>> def enum( cls, subs ):
    ...     return { sub: type( sub, ( cls, ), { } ) for sub in subs }
    ...
    >>> enum( Genre, [ 'scifi', 'romance', 'comic', 'science' ] )
    {'romance': <class '__main__.romance'>, 'science': <class '__main__.science'>,
    'comic': <class '__main__.comic'>, 'scifi': <class '__main__.scifi'>}
    

    编辑:我错过要点了吗?(我以前没有用过sqlacalchemy。)你在问如何创建新的 子类 属于 Genre 或如何创建新的 实例 ?前者在直觉上似乎是正确的,但后者正是你所要求的。这很容易:

    list( map( Genre, [ 'scifi', ... ] ) )
    

    将为您列出:

    [ Genre( 'scifi' ), ... ]
    
        3
  •  0
  •   Noctis Skytower    14 年前

    也许这个来自Verse Quiz程序的枚举函数对您有一定的用处: Verse Quiz