如何将SQLAlchemy与类属性(和属性)一起使用?

Mar*_*nen 8 python sqlalchemy properties class-attributes python-3.x

假设我正在制作带有物品的游戏(想想Minecraft,CS:GO武器,LoL和Dota物品等).游戏中可能存在大量相同的项目,但细节差异很小,例如条件/耐久性或项目中剩余的弹药数量:

player1.give_item(Sword(name='Sword', durability=50))
player2.give_item(Sword(name='Sword', durability=80))
player2.give_item(Pistol(name='Pistol', ammo=12))
Run Code Online (Sandbox Code Playgroud)

但是因为我不想每次都给我的剑和手枪命名(由于名字总是相同的),我希望它能够非常容易地创建新的项目类,我想我会name上课属性:

class Item:
    name = 'unnamed item'
Run Code Online (Sandbox Code Playgroud)

现在我只是将其子类化:

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
Run Code Online (Sandbox Code Playgroud)

我们有工作班:

>>> sword = Sword(30)
>>> print(sword.name, sword.durability, sep=', ') 
Sword, 30
Run Code Online (Sandbox Code Playgroud)

但有没有办法以这种或那种方式使用SQLAlchemy 这些类属性(有时甚至是classproperties)?比如,我想将项目的持久性(实例属性)和名称(类属性)与其class_id(类属性)作为主键存储:

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
Run Code Online (Sandbox Code Playgroud)

耐用性可以通过以下方式轻松完成:

class Sword(Item):
    durability = Column(Integer)
Run Code Online (Sandbox Code Playgroud)

但是nameclass属性和class_id类属性怎么样?

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

更新:我在帖子中不清楚这些表格.我只想为这些项目设置一个表,其中class_id它用作主键.这就是我用元数据构建表的方法:

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
)
Run Code Online (Sandbox Code Playgroud)

Jul*_*ian 5

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

\n\n

该问题包含一个示例,其中Item子类具有自己的特定实例属性。例如,Pistol是继承层次结构中唯一具有属性的类ammo。当在数据库中表示这一点时,您可以通过为父类创建一个表(其中包含每个公共属性的列)并将特定于子类的属性存储在每个子类的单独表中来节省空间。SQLAlchemy 开箱即用地支持此功能,并将其称为连接表继承(因为您需要连接表才能收集公共属性和子类特有的属性)。Ilja Everil\xc3\xa4 的答案和我之前的答案都假设连接表继承是可行的方法。

\n\n

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

\n\n

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

\n\n

这是我之前的答案(最初复制自Ilja 的答案)的解决方案 1 的稍微修改版本。此版本(“解决方案 1B”)使用单表继承,因此所有项目都存储在同一个表中。

\n\n
class Item(Base):\n    name = \'unnamed item\'\n\n    @classproperty\n    def class_id(cls):\n        return \'.\'.join((cls.__module__, cls.__qualname__))\n\n    __tablename__ = \'item\'\n    id = Column(Integer, primary_key=True)\n    type = Column(String(50))\n    durability = Column(Integer, default=100)\n    ammo = Column(Integer, default=10)\n\n    __mapper_args__ = {\n        \'polymorphic_identity\': \'item\',\n        \'polymorphic_on\': type\n    }\n\n\nclass Sword(Item):\n    name = \'Sword\'\n\n    __mapper_args__ = {\n        \'polymorphic_identity\': \'sword\',\n    }\n\n\nclass Pistol(Item):\n    name = \'Pistol\'\n\n    __mapper_args__ = {\n        \'polymorphic_identity\': \'pistol\',\n    }\n
Run Code Online (Sandbox Code Playgroud)\n\n

当我们将其与原始解决方案 1 进行比较时,有一些事情很突出。和属性已移至基类,因此它的每个实例durability或其子类之一现在都具有 a和 an 。和子类已丢失其s 以及所有列属性。这告诉 SQLAlchemy和没有自己的关联表;换句话说,我们要使用单表继承。栏目属性和业务还在;这些为 SQLAlchemy 提供信息来确定表中的任何给定行是否属于,或类。这就是我说专栏是消歧器的意思。ammoItemItemdurabilityammoSwordPistol__tablename__SwordPistolItem.type__mapper_args__itemItemSwordPistoltype

\n\n

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

\n\n

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

\n\n
class Item(Base):\n    name = \'unnamed item\'\n\n    @classproperty\n    def class_id(cls):\n        return \'.\'.join((cls.__module__, cls.__qualname__))\n\n    __tablename__ = \'item\'\n    id = Column(Integer, primary_key=True)\n    type = Column(String(50))\n    durability = Column(Integer, default=100)\n    ammo = Column(Integer, default=10)\n\n    @declared_attr\n    def __mapper_args__(cls):\n        if cls == Item:\n            return {\n                \'polymorphic_identity\': cls.__name__,\n                \'polymorphic_on\': type,\n            }\n        else:\n            return {\n                \'polymorphic_identity\': cls.__name__,\n            }\n\n\nclass Sword(Item):\n    name = \'Sword\'\n\n\nclass Pistol(Item):\n    name = \'Pistol\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n

  • 不,除了 `Item` 类中的代码之外,您不需要执行任何操作。SQLAlchemy 会为您处理“type”列,事实上您最好永远不要碰它。所以你可以使用`mysword = Sword()`创建一把剑。我很高兴这对你来说似乎好得令人难以置信。;-) (2认同)