简体   繁体   English

用于ORM目的的python枚举类

[英]python enumeration class for ORM purposes

EDITED QUESTION 编辑问题

I'm trying to create a class factory that can generate enumeration-like classes with the following properties: 我正在尝试创建一个类工厂,它可以生成具有以下属性的枚举类:

  1. Class is initialized from the list of allowed values (ie, it's automatically generated!). 类从允许值列表中初始化(即,它是自动生成的!)。
  2. Class creates one instance of itself for each of the allowed value. 类为每个允许值创建一个自身实例。
  3. Class does not allow the creation of any additional instances once the above step is complete (any attempt to do so results in an exception). 上述步骤完成后,类不允许创建任何其他实例(任何尝试都会导致异常)。
  4. Class instances provide a method that, given a value, returns a reference to the corresponding instance. 类实例提供了一种方法,该方法在给定值的情况下返回对相应实例的引用。
  5. Class instances have just two attributes: id and value. 类实例只有两个属性:id和value。 The attribute id auto-increments for each new instance; 每个新实例的属性id自动递增; the attribute value is the value the instance represents. 属性值是实例表示的值。
  6. Class is iterable. 类是可迭代的。 I'd prefer to implement this using the accepted answer to another SO question (specifically, by utilizing class registry and defining an iter method in the metaclass from which my enumeration classes are instanced). 我更喜欢使用另一个SO问题的接受答案来实现它(具体地说,通过利用类注册表并在我的枚举类实例化的元类中定义一个iter方法)。

This is all I'm looking for. 这就是我正在寻找的。 Please consider the original text (below) just a background to the question. 请将原始文本(下方)视为问题的背景。 Sorry for not being clear from the start. 很抱歉从一开始就不清楚。

UPDATED ANSWER 更新的答案

I made slight modifications to the very helpful answer by aaronasterling. 我对aaronasterling的非常有用的答案做了一些修改。 I thought I'd show it here so that others can benefit, and so that I receive more comments if I did something wrong :) 我以为我会在这里展示,以便其他人可以受益,所以如果我做错了,我会收到更多评论:)

The modifications I made are: 我做的修改是:

(0) Ported to p3k (iteritems --> items, metaclass --> 'metaclass =', no need to specify object as base class) (0)移植到p3k(iteritems - > items, 元类 - >'metaclass =',不需要指定对象作为基类)

(1) Changed instance method into @classmethod (now I don't need the object to call it, just the class) (1)将实例方法更改为@classmethod(现在我不需要对象来调用它,只需要类)

(2) Instead of populating _registry in one swoop, I update it every time a new element is constructed. (2)我不是一举填充_registry,而是每次构造一个新元素时都更新它。 This means I can use its length to set id, and so I got rid of _next_id attribute. 这意味着我可以使用它的长度来设置id,所以我摆脱了_next_id属性。 It will also work better for the extension I plan (see below). 它也适用于我计划的扩展(见下文)。

(3) Removed classname parameter from enum(). (3)从enum()中删除了classname参数。 After all, that classname is going to be a local name; 毕竟,该类名将是一个本地名称; the global name would have to be set separately anyway. 无论如何,全局名称必须单独设置。 So I used a dummy 'XXX' as the local classname. 所以我使用了一个虚拟'XXX'作为本地类名。 I'm a bit worried about what happens when I call the function for the second time, but it seems to work. 我有点担心第二次调用该函数时会发生什么,但它似乎有效。 If anyone knows why, let me know. 如果有人知道原因,请告诉我。 If it's a bad idea, I can of course auto-generate a new local classname at every invocation. 如果这是一个坏主意,我当然可以在每次调用时自动生成一个新的本地类名。

(4) Extended this class to allow an option whereby new enum elements can be added by the user. (4)扩展此类以允许用户添加新的枚举元素的选项。 Specifically, if instance() is called with a non-existent value, the corresponding object is created and then returned by the method. 具体来说,如果使用不存在的值调用instance(),则会创建相应的对象,然后由该方法返回。 This is useful if I grab a large number of enum values from parsing a file. 如果我从解析文件中获取大量枚举值,这将非常有用。

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

ORIGINAL TEXT 原文

I am using SQLAlchemy as the object-relational mapping tool. 我使用SQLAlchemy作为对象关系映射工具。 It allows me to map classes into tables in a SQL database. 它允许我将类映射到SQL数据库中的表。

I have several classes. 我有几节课。 One class (Book) is your typical class with some instance data. 一个类(Book)是具有一些实例数据的典型类。 The others (Genre, Type, Cover, etc.) are all essentially enumeration type; 其他(流派,类型,封面等)都是基本的枚举类型; eg, Genre can only be 'scifi', 'romance', 'comic', 'science'; 例如,流派只能是'科幻','浪漫','漫画','科学'; Cover can only be 'hard', 'soft'; 封面只能是“硬”,“软”; and so on. 等等。 There is many-to-one relationship between Book and each of the other classes. Book与其他每个类之间存在多对一的关系。

I would like to semi-automatically generate each of the enumeration-style classes. 我想半自动生成每个枚举样式的类。 Note that SQLAlchemy requires that 'scifi' is represented as an instance of class Genre; 请注意,SQLAlchemy要求'scifi'表示为类Genre的实例; in other words, it wouldn't work to simply define Genre.scifi = 0, Genre.romance = 1, etc. 换句话说,简单地定义Genre.scifi = 0,Genre.romance = 1等是行不通的。

I tried to write a metaclass enum that accepts as arguments the name of the class and the list of allowed values. 我尝试编写一个元类枚举,它接受类的名称和允许值列表作为参数。 I was hoping that 我希望如此

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

would create a class that allows these particular values, and also goes around and creates each of the objects that I need: Genre('scifi'), Genre('romance'), etc. 会创建一个允许这些特定值的类,并且还可以创建我需要的每个对象:类型('科幻'),流派('浪漫')等。

But I am stuck. 但我被卡住了。 One particular problem is that I can't create Genre('scifi') until ORM is aware of this class; 一个特别的问题是,在ORM意识到这个类之前我无法创建Genre('scifi'); on the other hand, by the time ORM knows about Genre, we're no longer in the class constructor. 另一方面,当ORM知道Genre时,我们不再是类构造函数了。

Also, I'm not sure my approach is good to begin with. 另外,我不确定我的方法是好的开始。

Any advice would be appreciated. 任何意见,将不胜感激。

new answer based on updates 基于更新的新答案

I think that this satisfies all of your specified requirements. 我认为这满足了您所有指定的要求。 If not, we can probably add whatever you need. 如果没有,我们可以添加你需要的任何东西。

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

old answer 老答案

In response to your comment on Noctis Skytower's answer wherein you say that you want Genre.comic = Genre('comic') (untested): 回应你对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]

This creates an instance of genre in response to an attempt to access it and attaches it to the instance on which it is requested. 这将创建一个类型实例,以响应尝试访问它并将其附加到请求它的实例。 If you want it attached to the entire class, replace the line 如果您希望将它附加到整个类,请替换该行

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

with

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

this has all subclasses create instances of the subclass in response to requests as well. 这使得所有子类都创建子类的实例以响应请求。 If you want subclasses to create instances of Genre , replace type(self)(attr) with Genre(attr) 如果您希望子类创建Genre实例,请将type(self)(attr)替换为Genre(attr)

You can create new classes on-the-fly with the type builtin: 您可以使用内置type即时创建新类:

type(name, bases, dict) 类型(名称,基数,字典)

Return a new type object. 返回一个新类型的对象。 This is essentially a dynamic form of the class statement. 这实际上是类语句的动态形式。 The name string is the class name and becomes the __name__ attribute; 名称字符串是类名,并成为__name__属性; the bases tuple itemizes the base classes and becomes the __bases__ attribute; 基元元组列出基类并成为__bases__属性; and the dict dictionary is the namespace containing definitions for class body and becomes the __dict__ attribute. dict字典是包含类体定义的命名空间,并成为__dict__属性。 For example, the following two statements create identical type objects: 例如,以下两个语句创建相同的类型对象:

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

In this case: 在这种情况下:

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

or in Python 2.7+: 或者在Python 2.7+中:

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

If you are doing this a lot, you can abstract away the pattern. 如果你这么做,你可以抽象出模式。

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

EDIT: Have I missed the point? 编辑:我错过了这一点吗? (I've not used SQLAlchemy before.) Are you asking how to create new subclasses of Genre , or how to create new instances ? (我之前没有使用SQLAlchemy。)您是否在询问如何创建Genre子类 ,或者如何创建新实例 The former seems intuitively right, but the latter is what you've asked for. 前者看似直观正确,但后者是你所要求的。 It's easy: 这很简单:

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

will make you a list of: 会列出你的清单:

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

也许Verse Quiz程序中的枚举功能可能对你有用: Verse Quiz

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM