#python #sqlalchemy
#python #sqlalchemy
Вопрос:
Я хотел бы сохранить сущность, используемую в моем коде, и избежать многократных вхождений. Таким образом, моя идея состояла в том, чтобы использовать __init__
метод для сбора основных данных для моего класса, а затем использовать своего рода __post_init__
метод для вычисления идентификатора из объекта моего класса. Вот код:
class Worker(Base):
__tablename__='worker'
id = Column(Integer,primary_key=True)
profile=Column(String(100),nullable=False)
useragent=Column(String(100),nullable=False)
def __init__(self,useragent,profile):
""" specify the main information"""
print('init')
self.profile= profile
self.useragent=useragent
def __post_init__(self):
""" compute an id based on self, the worker"""
self.id=id(self)
print('dans post init')
В этом примере можно использовать __init__
метод, но он не запускает __post_init__
метод, как мы могли бы ожидать, например, с dataclass .
Как я мог бы запустить этот метод сразу после выполнения __init__
метода?
Ответ №1:
__post_init__
Метод специфичен для dataclasses
библиотеки, потому что __init__
метод в dataclass
классах генерируется, и его переопределение полностью перечеркнуло бы цель его создания в первую очередь.
SQLAlchemy, с другой стороны, предоставляет __init__
реализацию в базовом классе модели (сгенерированном для вас с помощью declarative_base()
). Вы можете безопасно повторно использовать этот метод самостоятельно после настройки значений по умолчанию через super().__init__()
. Примите во внимание, что SQLAlchemy
предоставляемый __init__
метод принимает только аргументы ключевого слова:
def __init__(self, useragent, profile):
"""specify the main information"""
id = generate_new_id(self)
super().__init__(id=id, useragent=useragent, profile=profile)
Если вам нужно дождаться, пока другим столбцам сначала будут присвоены обновленные значения (потому что, возможно, они определяют функции Python как default
), тогда вы также можете запускать функции после вызова super().__init__()
и просто присваивать self
:
def __init__(self, useragent, profile):
"""specify the main information"""
super().__init__(useragent=useragent, profile=profile)
self.id = generate_new_id(self)
Примечание: вы не хотите использовать встроенную id()
функцию для генерации идентификаторов для вставленных в SQL данных, значения, которые возвращает функция, не гарантированно уникальны. Они уникальны только для набора всех активных объектов Python и только в текущем процессе. При следующем запуске Python или при удалении объектов из памяти значения могут и будут использоваться повторно, и вы не можете контролировать, какие значения он сгенерирует в следующий раз или в совершенно другом процессе.
Если вы хотите создавать только строки с уникальными комбинациями useragent
и profile
столбцов, тогда вам нужно определить a UniqueConstraint
в аргументах таблицы. Не пытайтесь определить уникальность на уровне Python, поскольку вы не можете гарантировать, что другой процесс не выполнит ту же проверку в то же время. База данных находится в гораздо лучшем положении, чтобы определить, есть ли у вас повторяющиеся значения, без риска возникновения условий гонки:
class Worker(Base):
__tablename__='worker'
id = Column(Integer, primary_key=True, autoincrement=True)
profile = Column(String(100), nullable=False)
useragent = Column(String(100), nullable=False)
__table_args__ = (
UniqueConstraint("profile", "useragent"),
)
или вы могли бы использовать составной первичный ключ, основанный на двух столбцах; первичные ключи (составные или иные) всегда должны быть уникальными:
class Worker(Base):
__tablename__='worker'
profile = Column(String(100), primary_key=True, nullable=False)
useragent = Column(String(100), primary_key=True, nullable=False)
Комментарии:
1. Дорогой Мартини, моей целью было бы использовать этот неуникальный аспект. Фактически, работники с одинаковым профилем и useragent будут иметь одинаковый идентификатор. Я бы просто проверил, есть ли они уже в базе данных, если это не так, я вставляю их, в другом случае я этого не делаю. Таким образом, интерес post_init состоял бы в том, чтобы получить представление о том, уникален worker или нет, и перейти к хранению в базе данных, если это необходимо, основываясь на этом идентификаторе.
2. @HappyCloudNinja: нет, у них не было бы того же
id
, потому чтоid()
это полностью зависит от текущего расположения памяти текущего процесса.3. @HappyCloudNinja: если
profile
иuseragent
вместе должны быть уникальными, то определите ограничение для этих двух столбцов вместе.
Ответ №2:
Я реализовал аналогичное поведение, используя __init_subclass__
метод:
class Parent:
def __init_subclass__(cls, **kwargs):
def init_decorator(previous_init):
def new_init(self, *args, **kwargs):
previous_init(self, *args, **kwargs)
if type(self) == cls:
self.__post_init__()
return new_init
cls.__init__ = init_decorator(cls.__init__)
def __post_init__(self):
pass
class Child(Parent):
def __init__(self):
print('Child __init__')
def __post_init__(self):
print('Child __post_init__')
class GrandChild(Child):
def __init__(self):
print('Before calling Child __init__')
Child.__init__(self)
print('After calling Child __init__')
def __post_init__(self):
print('GrandChild __post_init__')
child = Child()
# output:
# Child __init__
# Child __post_init__
grand_child = GrandChild()
# output:
# Before calling Child __init__
# Child __init__
# After calling Child __init__
# GrandChild __post_init__
Комментарии:
1. В общем, также было бы полезно вызывать
super().__post_init__()
всякий раз, когда переопределяете его. Это позволило бы выполнять здесь методыChild
‘s иGrandChild
‘s.