answer by Ilja Everilä уже наилучшим.Хотя он не сохраняет значение class_id
внутри таблицы буквально, обратите внимание, что любые два экземпляра одного и того же класса всегда имеют одинаковое значение class_id
. Поэтому, зная класс, достаточно, чтобы вычислилclass_id
для любого предмета. В примере кода, который предоставил Илья, столбец type
гарантирует, что класс всегда известен, а свойство класса class_id
заботится обо всем остальном. Таким образом, class_id
по-прежнему представлен в таблице, если косвенно.
Повторяю пример Илии из его первоначального ответа здесь, если он решает изменить его в своем посте. Назовем это «решение 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 намекнул на решение в своем последнем комментарии к этому вопросу, используя @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
и type
свойства, так как они являются избыточными («раствор 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
, и его значение произвольно выбирается для каждого класса. В решении 2 имя столбца равно class_id
, и его значение равно свойству класса, которое зависит от имени класса. В решении 3 имя равно name
, и его значение равно свойству 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 скорее всего бросит вам исключения из-за невозможности найти подходящие классы для ваших товаров.
Вы могли бы избежать этой проблемы, используя произвольное значение как disambiguator, так как в растворе 1, и хранение class_id
в отдельной колонке, используя @declared_attr
магии (или подобный косвенный маршрут), так как в растворе 2. Однако, в этот момент вам действительно нужно спросить себя, почему class_id
должен быть в таблице базы данных. Действительно ли это оправдывает сложность вашего кода?
Возьмите домой сообщение: вы можете карта равнину атрибуты класса, а также вычисляемые свойства класса, используя SQLAlchemy, даже перед лицом наследования, как это видно из решений. Это не обязательно означает, что вы должны это сделать. Начните с ваших конечных целей, и найдите самый простой способ достижения этих целей. Сделайте свое решение более сложным, если это решает настоящую проблему.
Вы спрашиваете, можно ли смешивать простые атрибуты python и декларативные, или я полностью не понял? –
@ IljaEverilä Я спрашиваю, есть ли способ хранения атрибутов класса и свойств класса в базе данных по атрибутам экземпляра. В моем примере кода мне нужно хранить базы боеприпасов/долговечности элемента на 'class_id' элемента. Боеприпасы и долговечность являются атрибутами экземпляра, поэтому я могу просто выполнить 'ammo = Column (Integer)', но как бы я мог использовать свойство класса (или даже атрибут класса)? –
Я обновил вопрос, чтобы лучше объяснить проблему. –