【问题标题】:Controlling the instantiation of python object控制python对象的实例化
【发布时间】:2010-08-17 20:07:08
【问题描述】:

我的问题与 sqlalchemy 并没有太大关系,而是与纯 python 相关。

我想控制 sqlalchemy 模型实例的实例化。这是我的代码中的一个 sn-p:

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)

希望你能帮帮我!

【问题讨论】:

    标签: python sqlalchemy metaprogramming


    【解决方案1】:

    我为此使用类方法。

    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__ 混淆。预计会在不带参数的情况下调用它,因此即使您自己的构造函数需要一些参数,您也不应该将它们传递给 super/object,除非您知道您的 super 需要它们。典型的调用是:

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

    但是,这在您的情况下不会按预期工作,因为如果__new__ 返回cls 的实例,它会自动调用__init__。您需要使用元类或在__init__ 中添加一些检查。

    【讨论】:

    • 你们所有人都给出了朝着同一个方向(工厂)方向发展的有用答案,但你的答案是最具体的。再加上你解释了__new__ 的特质,所以我检查了你的答案是正确的。
    【解决方案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”版本。这将查询数据库。如果找到该项目,则返回该对象。如果没有找到项目,则创建、插入并返回。

    【讨论】:

      【解决方案3】:

      鉴于 OP 的最新错误消息:

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

      似乎在某个地方实例化了类没有name 参数,即只是Tag()。该异常的回溯应该告诉您where“某处”(但我们没有显示它,所以这就是我们能走多远;-)。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多