用于ORM目的的python枚举类

时间:2010-08-28 00:51:50

标签: python sqlalchemy enumeration

已编辑的问题

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

  1. 从列表中初始化类 允许值(即,它) 自动生成!)。
  2. Class创建自己的一个实例 对于每个允许的值。
  3. 类不允许创建 任何其他实例一旦 上述步骤已完成(任何尝试 这样做会导致异常)。
  4. 类实例提供了一种方法 这个,给定一个值,返回一个 参考相应的 实例
  5. 类实例只有两个 属性:id和value。该 属性id自动递增 每个新实例;属性 value是实例的值 表示。
  6. 类是可迭代的。我更喜欢 使用the accepted answer to another SO question实现此目的 (具体来说,通过利用课程 注册表并定义 iter 我的元类中的方法 枚举类是实例)。
  7. 这就是我正在寻找的。请将原始文本(下方)视为问题的背景。很抱歉从一开始就不清楚。

    更新的答案

    我对aaronasterling非常有用的答案做了一些修改。我以为我会在这里展示,以便其他人可以受益,如果我做错了,我会收到更多评论:)

    我所做的修改是:

    (0)移植到p3k(iteritems - > items,元类 - >'metaclass =',无需将对象指定为基类)

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

    (2)我不是一举填充_registry,而是每次构造一个新元素时都更新它。这意味着我可以使用它的长度来设置id,所以我摆脱了_next_id属性。它也适用于我计划的扩展(见下文)。

    (3)从enum()中删除了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数据库中的表。

    我有几节课。一个类(Book)是具有一些实例数据的典型类。其他(流派,类型,封面等)都是基本的枚举类型;例如,流派只能是'科幻','浪漫','漫画','科学';封面只能是“硬”,“软”;等等。 Book与其他每个类之间存在多对一的关系。

    我想半自动生成每个枚举样式的类。请注意,SQLAlchemy要求'scifi'表示为类Genre的实例;换句话说,简单地定义Genre.scifi = 0,Genre.romance = 1等是行不通的。

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

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

    会创建一个允许这些特定值的类,并且还可以创建我需要的每个对象:类型('科幻'),流派('浪漫')等。

    但我被困住了。一个特别的问题是,在ORM意识到这个类之前我无法创建Genre('scifi');另一方面,当ORM知道Genre时,我们不再是类构造函数了。

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

    任何建议都将受到赞赏。

3 个答案:

答案 0 :(得分:3)

基于更新的新答案

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

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')

旧答案

回应你对Noctis Skytower答案的评论,其中你说你想要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)

答案 1 :(得分:0)

您可以使用type内置功能即时创建新类:

  

类型(名称,基数,字典)

     

返回一个新类型对象。这是   本质上是一种动态的形式   课堂陈述。名称字符串是   类名并成为   __name__属性;基地元组   逐项列出基类并成为   __bases__属性;和dict   dictionary是包含的命名空间   类体的定义和变成   __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'>}

编辑:我错过了这一点吗? (我之前没有使用过SQLAlchemy。)您是否在询问如何创建Genre的新子类,或者如何创建新的实例?前者看似直观正确,但后者是你所要求的。这很简单:

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

会列出以下内容:

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

答案 2 :(得分:0)

也许Verse Quiz程序中的枚举函数对您有用:Verse Quiz