控制python对象的实例化

时间:2010-08-17 20:07:09

标签: python sqlalchemy metaprogramming

我的问题与sqlalchemy没什么关系,而是与纯python有关。

我想控制sqlalchemy Model实例的实例化。这是我的代码片段:

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 个答案:

答案 0 :(得分:3)

我使用类方法。

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__混淆。它应该被称为w / out args,所以即使你自己的构造函数要求一些,你也不应该将它们传递给super / object,除非你知道你的超级需要它们。典型的调用是:

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__中添加一些检查。

答案 1 :(得分:2)

不要将其嵌入课堂本身。

选项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.定义makeTag函数的“get_or_create”版本。这将查询数据库。如果找到该项,则返回该对象。如果没有找到任何项目,请创建它,插入并返回。

答案 2 :(得分:1)

鉴于OP的最新错误信息:

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

似乎某个地方正在实例化而没有 name参数,即仅Tag()。该异常的追溯应该告诉你哪里“某处” (但我们没有显示它,所以我们可以走得多远; - )。 / p>

话虽如此,我同意其他答案,毕竟工厂功能(可能很好地装扮成classmethod - 制造工厂是classmethod的最佳用途之一;-)是要走的路,避免__new__所带来的复杂性(例如强制__init__找出对象是否已经初始化以避免重新初始化它! - 。)。