Ilja Everilä的答案已经是最好的了。虽然它没有按字面意思class_id
将 的值存储在表中,但请注意同一类的任何两个实例始终具有相同的 值。因此,知道类足以计算任何给定项目的。在 Ilja 提供的代码示例中,该列确保始终可以知道类,而类属性负责其余部分。因此,如果是间接的,则仍然在表中表示。class_id
class_id
type
class_id
class_id
我在这里从他的原始答案中重复 Ilja 的示例,以防他决定在自己的帖子中更改它。让我们称之为“解决方案 1”。
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 在他对这个问题的最后评论中暗示了一个解决方案, using @declared_attr
,它实际上会存储class_id
表格内部,但我认为它会不那么优雅。它给你带来的只是以稍微不同的方式表示完全相同的信息,代价是让你的代码更加复杂。自己看看(“解决方案2”):
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 开始然后合并name
andtype
属性来实现,因为它们是多余的(“解决方案 3”):
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
,它的所有实例将自动报告它们class_id
的存在'yourmodule.Key'
,并且您将能够name
在类级别设置它们的共同点。
SQL 方面有一些细微的差异,关于在项目类之间消除歧义的列的名称和值。在解决方案 1 中,调用该列type
并为每个类任意选择其值。方案二中,列名是class_id
,其值等于类属性,这取决于类名。在解决方案 3 中,名称 isname
并且其值等于name
类的属性,该属性可以独立于类名而变化。然而,由于所有这些消除项目类别歧义的不同方法都可以一对一地相互映射,因此它们包含相同的信息。
我之前提到过,解决方案 2 消除项目类歧义的方式有一个问题。假设您决定将Pistol
类重命名为Gun
. Gun.class_id_
(带有尾随下划线)Gun.__mapper_args__['polymorphic_identity']
并将自动更改为'yourmodule.Gun'
. 但是,class_id
数据库中的列(映射到Gun.class_id
没有尾随下划线)仍将包含'yourmodule.Pistol'
. 您的数据库迁移工具可能不够智能,无法确定需要更新这些值。如果您不小心,您class_id
的 s 将被损坏,并且 SQLAlchemy 可能会因无法为您的项目找到匹配的类而向您抛出异常。
您可以通过使用任意值作为消歧器来避免此问题,如在解决方案 1 中,并class_id
使用魔法(或类似的间接路由)将其存储在单独的列中@declared_attr
,如在解决方案 2 中。但是,此时您确实需要问问自己为什么class_id
需要在数据库表中。它真的有理由让你的代码如此复杂吗?
带回家的信息:您可以使用 SQLAlchemy 映射普通类属性以及计算类属性,即使面对继承,如解决方案所示。这并不一定意味着你真的应该这样做。从您的最终目标开始,并找到实现这些目标的最简单方法。只有在解决实际问题的情况下才能使您的解决方案更加复杂。