Управление реализацией объекта Python - PullRequest
2 голосов
/ 18 августа 2010

Мой вопрос на самом деле не имеет ничего общего с sqlalchemy, а скорее с чистым python.

Я хотел бы проконтролировать создание экземпляров модели sqlalchemy.Это фрагмент из моего кода:

class Tag(db.Model):

    __tablename__ = 'tags'
    query_class = TagQuery
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(), unique=True, nullable=False)

    def __init__(self, name):
        self.name = name

Я хочу добиться, чтобы при создании экземпляра записи (Tag('django')) новый экземпляр создавался только в том случае, если еще нет другого тега с именемdjango внутри базы данных.В противном случае, вместо инициализации нового объекта, ссылка на уже существующую строку в базе данных должна быть возвращена (Tag('django')).

На данный момент я обеспечиваю уникальность тегов внутри PostМодель:

class Post(db.Model):

        # ...
        # code code code
        # ...

        def _set_tags(self, taglist):
            """Associate tags with this entry. The taglist is expected to be already
            normalized without duplicates."""
            # Remove all previous tags
            self._tags = []
            for tag_name in taglist:
                exists = Tag.query.filter(Tag.name==tag_name).first()
                # Only add tags to the database that don't exist yet
                # TODO: Put this in the init method of Tag (if possible)
                if not exists:
                    self._tags.append(Tag(tag_name))
                else:
                    self._tags.append(exists)

Это делает свою работу, но все же я хотел бы знать, как обеспечить уникальность тегов внутри самого класса Tag, чтобы я мог написать метод _set_tags следующим образом:

def _set_tags(self, taglist):
    # Remove all previous tags
    self._tags = []
    for tag_name in taglist:
        self._tags.append(Tag(tag_name))


Во время написания этого вопроса и тестирования я узнал, что мне нужно использовать метод __new__.Вот что я придумал (он даже проходит модульные тесты, и я не забыл изменить _set_tags метод):

class Tag(db.Model):

    __tablename__ = 'tags'
    query_class = TagQuery
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(), unique=True, nullable=False)

    def __new__(cls, *args, **kwargs):
        """Only add tags to the database that don't exist yet. If tag already
        exists return a reference to the tag otherwise a new instance"""
        exists = Tag.query.filter(Tag.name==args[0]).first() if args else None
        if exists:
            return exists
        else:
            return super(Tag, cls).__new__(cls, *args, **kwargs)

Меня беспокоит две вещи:

Во-первых: я получаю предупреждение:

DeprecationWarning: object.__new__() takes no parameters

Во-вторых: Когда я пишу это так, я получаю ошибки (я также пытался переименовать параметр name в n, но он не изменилсячто угодно):

def __new__(cls, name):
    """Only add tags to the database that don't exist yet. If tag already
    exists return a reference to the tag otherwise a new instance"""
    exists = Tag.query.filter(Tag.name==name).first()
    if exists:
        return exists
    else:
        return super(Tag, cls).__new__(cls, name)

Ошибки (или аналогичные):

TypeError: __new__() takes exactly 2 arguments (1 given)

Надеюсь, вы мне поможете!

Ответы [ 3 ]

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

Я использую метод класса для этого.

class Tag(Declarative):
    ...
    @classmethod
    def get(cls, tag_name):
        tag = cls.query.filter(cls.name == tag_name).first()
        if not tag:
            tag = cls(tag_name)
        return tag

А потом

def _set_tags(self, taglist):
    self._tags = []
    for tag_name in taglist:
        self._tags.append(Tag.get(tag_name))

Что касается __new__, не стоит путать его с __init__. Ожидается, что он будет вызываться без аргументов, поэтому даже если ваш собственный конструктор запрашивает некоторые из них, вы не должны передавать их супер / объекту, если вы не знаете , что ваш супер нуждается в них. Типичный вызов будет:

def __new__(cls, name=None): 
    tag = cls.query.filter(cls.name == tag_name).first()
    if not tag:
        tag = object.__new__(cls)
    return tag

Однако в вашем случае это не будет работать должным образом, поскольку он автоматически вызывает __init__, если __new__ возвращает экземпляр cls. Вам нужно будет использовать метакласс или добавить некоторые проверки в __init__.

2 голосов
/ 18 августа 2010

Не встраивайте это в сам класс.

Вариант 1. Создайте фабрику с уже существующим пулом объектов.

tag_pool = {}
def makeTag( name ):
    if name not in tag_pool:
        tag_pool[name]= Tag(name)
    return tag_pool[name]

Жизнь намного проще.

tag= makeTag( 'django' )

При необходимости создаст предмет.

Вариант 2. Определите "get_or_create" версию функции makeTag. Это запросит базу данных. Если элемент найден, верните объект. Если элемент не найден, создайте его, вставьте и верните.

1 голос
/ 18 августа 2010

Учитывая последнюю ошибку сообщения OP:

TypeError: __new__() takes exactly 2 arguments (1 given)

, создается впечатление, что где-то создается экземпляр класса без параметра name, то есть просто Tag().Трассировка для этого исключения должна сообщить вам , где , что "где-то" равно (но мы не показали его, так что мы так далеко, как можно -;)

При этом я согласен с другими ответами о том, что фабричная функция (возможно, красиво одетая как classmethod - создание фабрик - одно из лучших применений classmethod, в конце концов ;-) - это способиди, избегая сложностей, которые влечет за собой __new__ (например, вынуждая __init__ выяснить, был ли объект уже инициализирован, чтобы избежать его повторной инициализации! -).

...