首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在类属性(和属性)中使用SQLAlchemy?

如何在类属性(和属性)中使用SQLAlchemy?
EN

Stack Overflow用户
提问于 2016-07-22 06:12:41
回答 3查看 8.6K关注 0票数 11

假设我正在制作一个游戏,里面有一些物品(想想我的经验之谈,CS:GO武器,LoL和Dota项目等等)。游戏中可以有大量相同的物品,但细节差别很小,比如物品的状态/耐用性或剩余弹药的数量:

代码语言:javascript
复制
player1.give_item(Sword(name='Sword', durability=50))
player2.give_item(Sword(name='Sword', durability=80))
player2.give_item(Pistol(name='Pistol', ammo=12))

但是,由于我不想每次都给我的剑和手枪命名(因为名称总是相同的),而且我希望创建新的项目类非常容易,所以我想让name成为一个类属性:

代码语言:javascript
复制
class Item:
    name = 'unnamed item'

现在,我只需子类如下:

代码语言:javascript
复制
class Sword(Item):
    name = 'Sword'

    def __init__(self, durability=100):
        self.durability = durability

class Pistol(Item):
    name = 'Pistol'

    def __init__(self, ammo=10):
        self.ammo = ammo

我们有工人阶级:

代码语言:javascript
复制
>>> sword = Sword(30)
>>> print(sword.name, sword.durability, sep=', ') 
Sword, 30

但是,是否有一种方法可以以某种方式在类属性中使用这些类属性(有时甚至是SQLAlchemy )?比方说,我想存储一个项目的持久性(实例属性)和名称(类属性),并将其class_id (类属性)作为主键:

代码语言:javascript
复制
class Item:
    name = 'unnamed item'

    @ClassProperty  # see the classproperty link above
    def class_id(cls):
        return cls.__module__ + '.' + cls.__qualname__

class Sword(Item):
    name = 'Sword'

    def __init__(self, durability=100):
        self.durability = durability

耐用性可以很容易地做到以下几点:

代码语言:javascript
复制
class Sword(Item):
    durability = Column(Integer)

但是name类属性和class_id类属性如何?

实际上,我有更大的继承树,每个类都有多个属性/属性以及更多的实例属性。

更新:--我在我的帖子中不清楚这些表。我只希望有一个表用于项目,其中使用class_id作为主键。这就是我如何用元数据构造表的方法:

代码语言:javascript
复制
items = Table('items', metadata,
    Column('steamid', String(21), ForeignKey('players.steamid'), primary_key=True),
    Column('class_id', String(50), primary_key=True),
    Column('name', String(50)),
    Column('other_data', String(100)),  # This is __RARELY__ used for something like durability, so I don't need separate table for everything
)
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-07-29 23:21:34

这是基于单表继承的第二个答案。

这个问题包含一个示例,其中Item子类有它们自己的特定实例属性。例如,Pistol是继承层次结构中唯一具有ammo属性的类。在数据库中表示此值时,可以为父类创建包含每个公共属性的列的表,并将特定于子类的属性存储在每个子类的单独表中,从而节省空间。SQLAlchemy支持这一点,并将其称为联合表继承 (因为您需要连接表来收集公共属性和子类特有的属性)。Ilja Everil的答复我先前的回答都认为联合表继承是可行的。

事实证明,Markus Meskanen的实际代码有点不同。子类没有特定的实例属性,它们都有一个共同的level属性。还有,Markus评论说,他希望所有的子类都存储在同一个表中。。使用单个表的一个可能的优点是,您可以添加和删除子类,而无需每次对数据库模式进行重大更改。

SQLAlchemy也提供了对此的支持,它被称为单表继承。如果子类确实有特定的属性,它甚至可以工作。它的效率有点低,因为每行都必须存储每个可能的属性,即使它属于不同子类的一个项。

下面是与我以前的答案(最初是从Ilja的回答复制的)的解决方案1的一个稍微不同的版本。此版本(“解决方案1B")使用单个表继承,因此所有项都存储在同一个表中。

代码语言:javascript
复制
class Item(Base):
    name = 'unnamed item'

    @classproperty
    def class_id(cls):
        return '.'.join((cls.__module__, cls.__qualname__))

    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    durability = Column(Integer, default=100)
    ammo = Column(Integer, default=10)

    __mapper_args__ = {
        'polymorphic_identity': 'item',
        'polymorphic_on': type
    }


class Sword(Item):
    name = 'Sword'

    __mapper_args__ = {
        'polymorphic_identity': 'sword',
    }


class Pistol(Item):
    name = 'Pistol'

    __mapper_args__ = {
        'polymorphic_identity': 'pistol',
    }

当我们将其与最初的解决方案1进行比较时,有几件事情是非常突出的。durabilityammo属性已经转移到Item基类,因此Item的每个实例或它的一个子类现在都有一个durability和一个ammoSwordPistol子类已经丢失了它们的__tablename__和它们的所有列属性。这告诉SQLAlchemy,SwordPistol没有自己的关联表;换句话说,我们希望使用单个表继承。Item.type列属性和__mapper_args__业务仍然存在;这些属性为SQLAlchemy提供了信息,以确定item表中的任何给定行是否属于ItemSwordPistol类。当我说type列是消歧器时,这就是我的意思。

现在,使用Markus还评论说他不想自定义子类。来创建具有单表继承的数据库映射。Markus希望从没有数据库映射的现有类层次结构开始,然后通过编辑基类立即创建整个单表继承数据库映射。这意味着将__mapper_args__添加到SwordPistol子类(如上面的解决方案1B中)是不可能的。实际上,如果可以“自动”计算消歧器,这就节省了大量样板,特别是在有许多子类的情况下。

这可以使用@declared_attr来完成。输入解决方案4:

代码语言:javascript
复制
class Item(Base):
    name = 'unnamed item'

    @classproperty
    def class_id(cls):
        return '.'.join((cls.__module__, cls.__qualname__))

    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    type = Column(String(50))
    durability = Column(Integer, default=100)
    ammo = Column(Integer, default=10)

    @declared_attr
    def __mapper_args__(cls):
        if cls == Item:
            return {
                'polymorphic_identity': cls.__name__,
                'polymorphic_on': type,
            }
        else:
            return {
                'polymorphic_identity': cls.__name__,
            }


class Sword(Item):
    name = 'Sword'


class Pistol(Item):
    name = 'Pistol'

这将产生与解决方案1B相同的结果,只不过消歧器(仍然是type列)的值是从类中计算出来的,而不是任意选择的字符串。这里,它只是类的名称(cls.__name__)。我们本来可以选择完全限定名(cls.class_id),甚至选择自定义name属性(cls.name),前提是可以保证每个子类都覆盖name。只要在值和类之间存在一对一的映射,您认为什么才是消歧器的值并不重要。

票数 5
EN

Stack Overflow用户

发布于 2016-07-22 12:31:34

引用官方文档的话

在构造类时,声明式用称为描述符的特殊Column访问器替换所有的Python对象; 除了映射过程对我们的类所做的事情之外,这个类基本上仍然是一个普通的Python类,我们可以对该类定义应用程序所需的任意数量的普通属性和方法。

由此可以清楚地看到,添加类属性、方法等是可能的。但是有一些保留的名称,即__tablename____table__metadata__mapper_args__ (并非详尽无遗的列表)。

至于继承,SQLAlchemy提供三种形式单表混凝土联合表继承

使用联合表继承实现简化的示例:

代码语言:javascript
复制
class Item(Base):
    name = 'unnamed item'

    @classproperty
    def class_id(cls):
        return '.'.join((cls.__module__, cls.__qualname__))

    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity': 'item',
        'polymorphic_on': type
    }


class Sword(Item):
    name = 'Sword'

    __tablename__ = 'sword'
    id = Column(Integer, ForeignKey('item.id'), primary_key=True)
    durability = Column(Integer, default=100)

    __mapper_args__ = {
        'polymorphic_identity': 'sword',
    }


class Pistol(Item):
    name = 'Pistol'

    __tablename__ = 'pistol'
    id = Column(Integer, ForeignKey('item.id'), primary_key=True)
    ammo = Column(Integer, default=10)

    __mapper_args__ = {
        'polymorphic_identity': 'pistol',
    }

添加项和查询:

代码语言:javascript
复制
In [11]: session.add(Pistol())

In [12]: session.add(Pistol())

In [13]: session.add(Sword())

In [14]: session.add(Sword())

In [15]: session.add(Sword(durability=50))

In [16]: session.commit()

In [17]: session.query(Item).all()
Out[17]: 
[<__main__.Pistol at 0x7fce3fd706d8>,
 <__main__.Pistol at 0x7fce3fd70748>,
 <__main__.Sword at 0x7fce3fd709b0>,
 <__main__.Sword at 0x7fce3fd70a20>,
 <__main__.Sword at 0x7fce3fd70a90>]

In [18]: _[-1].durability
Out[18]: 50

In [19]: item =session.query(Item).first()

In [20]: item.name
Out[20]: 'Pistol'

In [21]: item.class_id
Out[21]: '__main__.Pistol'
票数 2
EN

Stack Overflow用户

发布于 2016-07-29 00:09:41

Ilja Everil的答复已经是最好的可能。虽然它没有将class_id的值按字面顺序存储在表中,但请注意,同一个类的任何两个实例都具有相同的class_id值。因此,知道这个类就足以计算任何给定项的class_id。在Ilja提供的代码示例中,type列确保类总是已知的,class_id类属性负责其余部分。因此,如果是间接的,class_id仍然在表中。

我重复Ilja的例子,从他原来的答案这里,以防他决定改变在他自己的帖子。让我们称之为“解决方案1”。

代码语言:javascript
复制
class Item(Base):
    name = 'unnamed item'

    @classproperty
    def class_id(cls):
        return '.'.join((cls.__module__, cls.__qualname__))

    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity': 'item',
        'polymorphic_on': type
    }


class Sword(Item):
    name = 'Sword'

    __tablename__ = 'sword'
    id = Column(Integer, ForeignKey('item.id'), primary_key=True)
    durability = Column(Integer, default=100)

    __mapper_args__ = {
        'polymorphic_identity': 'sword',
    }


class Pistol(Item):
    name = 'Pistol'

    __tablename__ = 'pistol'
    id = Column(Integer, ForeignKey('item.id'), primary_key=True)
    ammo = Column(Integer, default=10)

    __mapper_args__ = {
        'polymorphic_identity': 'pistol',
    }

Ilja在他对这个问题的最后评论中暗示了一个解决方案,使用@declared_attr,它可以将class_id存储在表中,但我认为它不那么优雅。它为您提供的只是以一种稍微不同的方式表示完全相同的信息,而代价是使代码变得更加复杂。自己看(“解决方案2"):

代码语言:javascript
复制
class Item(Base):
    name = 'unnamed item'

    @classproperty
    def class_id_(cls):  # note the trailing underscore!
        return '.'.join((cls.__module__, cls.__qualname__))

    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    class_id = Column(String(50))  # note: NO trailing underscore!

    @declared_attr  # the trick
    def __mapper_args__(cls):
        return {
            'polymorphic_identity': cls.class_id_,
            'polymorphic_on': class_id
        }


class Sword(Item):
    name = 'Sword'

    __tablename__ = 'sword'
    id = Column(Integer, ForeignKey('item.id'), primary_key=True)
    durability = Column(Integer, default=100)

    @declared_attr
    def __mapper_args__(cls):
        return {
            'polymorphic_identity': cls.class_id_,
        }


class Pistol(Item):
    name = 'Pistol'

    __tablename__ = 'pistol'
    id = Column(Integer, ForeignKey('item.id'), primary_key=True)
    ammo = Column(Integer, default=10)

    @declared_attr
    def __mapper_args__(cls):
        return {
            'polymorphic_identity': cls.class_id_,
        }

这种方法还有一个额外的危险,我将在后面讨论。

在我看来,使代码更简单会更优雅。这可以从解决方案1开始,然后合并nametype属性,因为它们是冗余的(“解决方案3"):

代码语言:javascript
复制
class Item(Base):
    @classproperty
    def class_id(cls):
        return '.'.join((cls.__module__, cls.__qualname__))

    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))  # formerly known as type

    __mapper_args__ = {
        'polymorphic_identity': 'unnamed item',
        'polymorphic_on': name,
    }


class Sword(Item):
    __tablename__ = 'sword'
    id = Column(Integer, ForeignKey('item.id'), primary_key=True)
    durability = Column(Integer, default=100)

    __mapper_args__ = {
        'polymorphic_identity': 'Sword',
    }


class Pistol(Item):
    __tablename__ = 'pistol'
    id = Column(Integer, ForeignKey('item.id'), primary_key=True)
    ammo = Column(Integer, default=10)

    __mapper_args__ = {
        'polymorphic_identity': 'Pistol',
    }

到目前为止讨论的所有三种解决方案都在Python端给出了完全相同的请求行为(假设您将忽略type属性)。例如,Pistol的一个实例将在每个解决方案中返回'yourmodule.Pistol'作为其class_id'Pistol'作为其name。在每个解决方案中,如果您向层次结构(比如Key )添加了一个新的item类,那么它的所有实例都将自动将其class_id报告为'yourmodule.Key',并且您将能够在类级别设置它们的公共name一次。

在SQL端,对于消除项类之间歧义的列的名称和值,有一些细微的差异。在解决方案1中,该列被称为type,它的值是任意为每个类选择的。在解决方案2中,列名是class_id,其值等于类属性,这取决于类名。在解决方案3中,名称是name,其值等于类的name属性,可以独立于类名进行更改。但是,由于所有这些消除item类歧义的不同方法都可以一对一地映射到彼此,所以它们包含相同的信息。

我在前面提到过,解决方案2消除item类歧义的方式存在一个问题。假设您决定将Pistol类重命名为GunGun.class_id_ (带有尾随下划线)和Gun.__mapper_args__['polymorphic_identity']将自动更改为'yourmodule.Gun'。但是,数据库中的class_id列(映射到Gun.class_id而不带尾下划线)仍将包含'yourmodule.Pistol'。您的数据库迁移工具可能不够聪明,无法确定需要更新这些值。如果您不小心,您的class_id将被损坏,SQLAlchemy可能会因为无法为您的项找到匹配类而向您抛出异常。

您可以通过使用任意值作为消歧器(如在解决方案1中)和使用class_id魔术(或类似的间接路由)将@declared_attr存储在单独的列中(或类似的间接路由)来避免这一问题,就像在解决方案2中一样。然而,在这一点上,您确实需要问自己为什么class_id需要在数据库表中。这真的有理由让你的代码变得如此复杂吗?

带回家消息:您可以使用SQLAlchemy映射普通的类属性以及计算的类属性,甚至在继承的情况下也是如此,如解决方案所示。但这并不一定意味着你真的应该这么做。从你的最终目标开始,找出最简单的方法来实现这些目标。只有这样做才能解决真正的问题,才能使你的解决方案更加复杂。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38519349

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档