用于ORM的python枚举类 [英] python enumeration class for ORM purposes

查看:85
本文介绍了用于ORM的python枚举类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑后的问题



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


  1. 从允许值
    列表中初始化类(即,它是
    自动生成的!)。

  2. Class为每个允许的值创建一个本身
    的实例。

  3. Class不允许一次创建
    的任何其他实例
    的上述步骤已完成(任何尝试
    的操作都会导致异常)。

  4. 类实例提供了一种方法
    ,该方法具有给定的值,返回对相应
    实例的
    引用。

  5. 类实例只有两个
    属性:id和value。每个新实例

    属性ID自动增加;属性
    的值是实例
    表示的值。

  6. 类是可迭代的。我希望
    使用来实现接受的另一个SO问题的
    答案

    (具体而言,通过使用
    类注册表并在以下方法的元类中定义 iter
    方法我的
    枚举类实例化了。)

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



更新后的答案



我做了对aaronasterling的非常有用的答案稍作修改。我认为我要在这里展示它,以便其他人可以受益,并且如果我做错了事,这样我会收到更多评论:)



我所做的修改是:



(0)移植到p3k(项目->项目,元类->'元类=',无需将对象指定为基class)



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



(2)每次构造一个新元素时,我都不会对其进行一次性填充,而是一次更新_registry。这意味着我可以使用它的长度来设置id,因此我摆脱了_next_id属性。



(3)从enum()中删除类名参数,对于我计划的扩展,它也将更好地工作。毕竟,该类名将是一个本地名。无论如何,必须分别设置全局名称。因此,我使用了一个虚拟的 XXX作为本地类名。我有点担心第二次调用该函数时会发生什么,但它似乎可以正常工作。如果有人知道为什么,请告诉我。如果不是一个好主意,我当然可以在每次调用时自动生成一个新的本地类名。可以由用户添加。具体来说,如果用不存在的值调用instance(),则会创建相应的对象,然后由该方法返回。

  def枚举(值):
类EnumType(元类= IterRegistry):
_registry = {}
def __init __(self,value):
self.value =值
self.id = len(type( self)._ registry)
type(self)._ registry [value] = self

def __repr __(self):
返回self.value

@classmethod
def实例(cls,值):
返回cls._registry [value]

cls = type('XXX',(EnumType,),{})
表示值的值:
cls(值)

def __new __(cls,值):
如果cls._registry中的值:
返回cls。 _registry [value]
否则:
如果cls.frozen:
引发TypeError('不允许更多实例')
否则:
返回对象。__new __(cls)

cls .__ new__ = staticmethod(__ new__)
return cls

原始文本



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



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



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



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

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

将创建一个允许这些特定值的类,并且四处创建我需要的对象类型:Genre('scifi'),Genre('romance')等。



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



此外,我不确定我的方法是否适合



任何建议都会受到赞赏。

解决方案

新基于更新的答案



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

  def enum(classname,values):
class EnumMeta(类型):
def __iter __(cls):
返回cls._instances.itervalues()

class EnumType(object):
__metaclass__ = EnumMeta
_instances = {}
_next_id = 0
def __init __(self,value):
self.value =值
self.id = type(self)._ next_id
type(self)._ next_id + = 1

def实例(self,value):
返回type(self)._ instances [value]

cls = type (类名,(EnumType,),{})
实例= dict((value,cls(value))表示值的值)
cls._instances =实例

def __new __(cls,value):
引发TypeError('不允许更多实例')

cls .__ new__ = staticmethod(__ new__)
return cls


类型=枚举('类型',['科幻','漫画','科学'])


类别中的项目:
打印item,item.value,item.id
assert(item是Genre(item.value))
assert(item是item.instance(item.value))

类型('romance')



旧答案



回应您对Noctis Skytower答案的评论,您说您想要类型。 comic = Genre('comic')(unested):

  class Genre(GenreBase):
体裁= ['漫画','科幻',...]
def __getattr __(self,attr):
(如果attr为type(self)。体裁:
self)。 __dict __ [attr] = type(self)(attr)
return self .__ dict __ [attr]

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

  self .__ dict __ [attr] == type(self)( attr)

  type(self).__ dict __ [attr] = type(self)(attr)

这也使所有子类也响应请求创建子类的实例。如果希望子类创建类型的实例,请将 type(self)(attr)替换为类型(attr)


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 (i.e., 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. The attribute id auto-increments for each new instance; 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).

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

(1) Changed instance method into @classmethod (now I don't need the object to call it, just the class)

(2) Instead of populating _registry in one swoop, I update it every time a new element is constructed. This means I can use its length to set id, and so I got rid of _next_id attribute. It will also work better for the extension I plan (see below).

(3) Removed classname parameter from enum(). 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. 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. Specifically, if instance() is called with a non-existent value, the corresponding object is created and then returned by the method. 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. It allows me to map classes into tables in a SQL database.

I have several classes. One class (Book) is your typical class with some instance data. The others (Genre, Type, Cover, etc.) are all essentially enumeration type; e.g., 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.

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; in other words, it wouldn't work to simply define Genre.scifi = 0, Genre.romance = 1, etc.

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; on the other hand, by the time ORM knows about Genre, we're no longer in the class constructor.

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

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)

这篇关于用于ORM的python枚举类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆