перечислимый класс Python для целей ORM - PullRequest
4 голосов
/ 28 августа 2010

РЕДАКТИРОВАНИЕ ВОПРОСА

Я пытаюсь создать фабрику классов, которая может генерировать перечислимые классы со следующими свойствами:

  1. Класс инициализированиз списка разрешенных значений (т. е. он автоматически генерируется!).
  2. Класс создает один экземпляр для каждого из разрешенных значений.
  3. Класс не позволяет создавать дополнительные экземпляры.после завершения вышеуказанного шага (любая попытка сделать это приводит к исключению).
  4. Экземпляры класса предоставляют метод, который при заданном значении возвращает ссылку на соответствующий экземпляр.
  5. Классэкземпляры имеют только два атрибута: идентификатор и значение.Атрибут id автоматически увеличивается для каждого нового экземпляра;значение атрибута - это значение, которое представляет экземпляр.
  6. Класс является итеративным.Я предпочел бы реализовать это, используя принятый ответ на другой вопрос SO (в частности, используя реестр классов и определяя метод iter в метаклассе, из которого создаются мои классы перечисления).

Это все, что я ищу.Пожалуйста, рассмотрите исходный текст (ниже) просто как фон для вопроса.Извините за непонятность с самого начала.

ОБНОВЛЕННЫЙ ОТВЕТ

Я внес небольшие изменения в очень полезный ответ от aaronasterling.Я думал, что покажу это здесь, чтобы другие могли извлечь выгоду, и чтобы я получил больше комментариев, если я сделал что-то не так:)

Изменения, которые я сделал:

(0) Портированыв p3k (iteritems -> items, metaclass -> 'metaclass =', нет необходимости указывать объект в качестве базового класса)

(1) Изменен метод экземпляра на @classmethod (теперьМне не нужен объект для его вызова, просто класс)

(2) Вместо заполнения _registry одним махом, я обновляю его каждый раз, когда создается новый элемент.Это означает, что я могу использовать его длину для установки идентификатора, и поэтому я избавился от атрибута _next_id.Это также будет работать лучше для расширения, которое я планирую (см. Ниже).

(3) Удален параметр имени класса из enum ().В конце концов, это имя класса будет локальным именем;в любом случае глобальное имя должно быть установлено отдельно.Поэтому я использовал фиктивный «XXX» в качестве локального имени класса.Я немного беспокоюсь о том, что происходит, когда я вызываю функцию во второй раз, но, похоже, она работает.Если кто-нибудь знает почему, дайте мне знать.Если это плохая идея, я, конечно, могу автоматически генерировать новое локальное имя класса при каждом вызове.

(4) Расширил этот класс, чтобы предоставить возможность, с помощью которой новые элементы enum могут добавляться пользователем.В частности, если 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

ORIGINAL TEXT

Я использую SQLAlchemy в качестве объектно-реляционного отображенияинструмент.Это позволяет мне отображать классы в таблицы в базе данных SQL.

У меня есть несколько классов.Один класс (Книга) - это ваш типичный класс с некоторыми данными экземпляра.Остальные (Жанр, Тип, Обложка и т. Д.) Являются по существу типом перечисления;например, жанр может быть только «фантастика», «романтика», «комикс», «наука»;Покрытие может быть только «твердым», «мягким»;и так далее.Между Книгой и каждым другим классом существует отношение многие-к-одному.

Я хотел бы полуавтоматически генерировать каждый из классов стиля перечисления.Обратите внимание, что SQLAlchemy требует, чтобы scifi был представлен как экземпляр класса Genre;Другими словами, было бы не просто определить Genre.scifi = 0, Genre.romance = 1 и т. д.

Я попытался написать перечисление метакласса, которое принимает в качестве аргументов имя класса исписок допустимых значений.Я надеялся, что

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

создаст класс, который позволяет эти конкретные значения, а также обходит и создает каждый из объектов, которые мне нужны: Genre ('scifi'), Genre ('romance')и т. д.

Но я застрял.Одна конкретная проблема заключается в том, что я не могу создать Genre ('scifi'), пока ORM не узнает об этом классе;с другой стороны, к тому времени, когда ORM узнает о Genre, мы уже не в конструкторе классов.

Кроме того, я не уверен, что мой подход хорош для начала.

Любой совет будет оценен.

Ответы [ 3 ]

3 голосов
/ 28 августа 2010

новый ответ на основе обновлений

Я думаю, что это удовлетворяет всем вашим указанным требованиям. Если нет, то мы можем добавить все, что вам нужно.

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's, в котором вы говорите, что хотите 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)

0 голосов
/ 28 августа 2010

Может быть, эта функция перечисления из программы Verse Quiz вам может пригодиться: Verse Quiz

0 голосов
/ 28 августа 2010

Вы можете создавать новые классы "на лету" с помощью встроенного type типа:

(имя, базы, дикт)

Returnобъект нового типа.По сути, это динамическая форма выражения класса.Строка имени является именем класса и становится атрибутом __name__;кортеж base перечисляет базовые классы и становится атрибутом __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'>}

РЕДАКТИРОВАТЬ: я пропустил пункт?(Я ранее не использовал SQLAlchemy.) Вы спрашиваете, как создать новые подклассы из Genre или как создать новые экземпляры ?Первое кажется интуитивно правильным, но последнее - то, о чем вы просили.Это просто:

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

сделает вам список:

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