Как использовать эквивалент метода __post_init__ с обычным классом?

#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.