How to use an equivalent to __post_init__ method with normal class?
Question:
I would like to store entity used in my code and avoid multiple occurrences. Thus, my idea was to use an __init__
method for collecting the main data for my class, and then use a kind of __post_init__
method for computing an id from my class object. Here is the code:
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')
With this example, the __init__
method can be used, but it doesn’t run the __post_init__
method like we could expect with dataclass, for example.
How could I run this method just after the execution of the __init__
method ?
Answers:
The __post_init__
method is specific to the dataclasses
library, because the __init__
method on dataclass
classes is generated and overriding it would entirely defeat the purpose of generating it in the first place.
SQLAlchemy, on the other hand, provides an __init__
implementation on the base model class (generated for you with declarative_base()
). You can safely re-use that method yourself after setting up default values, via super().__init__()
. Take into account that the SQLAlchemy
-provided __init__
method only takes keyword arguments:
def __init__(self, useragent, profile):
"""specify the main information"""
id = generate_new_id(self)
super().__init__(id=id, useragent=useragent, profile=profile)
If you need to wait for the other columns to be given updated values first (because perhaps they define Python functions as a default
), then you can also run functions after calling super().__init__()
, and just assign to self
:
def __init__(self, useragent, profile):
"""specify the main information"""
super().__init__(useragent=useragent, profile=profile)
self.id = generate_new_id(self)
Note: you do not want to use the built-in id()
function to generate ids for SQL-inserted data, the values that the function returns are not guaranteed to be unique. They are only unique for the set of all active Python objects only, and only in the current process. The next time you run Python, or when objects are deleted from memory, values can and will be reused, and you can’t control what values it’ll generate next time, or in a different process altogether.
If you were looking to only ever create rows with unique combinations of the useragent
and profile
columns, then you need to define a UniqueConstraint
in the table arguments. Don’t try to detect uniqueness at the Python level, as you can’t guarantee that another process will not make the same check at the same time. The database is in a much better position to determine if you have duplicate values, without risking race conditions:
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"),
)
or you could use a composite primary key based on the two columns; primary keys (composite or otherwise) must always be unique:
class Worker(Base):
__tablename__='worker'
profile = Column(String(100), primary_key=True, nullable=False)
useragent = Column(String(100), primary_key=True, nullable=False)
I implemented the similar behavior using the __init_subclass__
method:
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__
I would like to store entity used in my code and avoid multiple occurrences. Thus, my idea was to use an __init__
method for collecting the main data for my class, and then use a kind of __post_init__
method for computing an id from my class object. Here is the code:
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')
With this example, the __init__
method can be used, but it doesn’t run the __post_init__
method like we could expect with dataclass, for example.
How could I run this method just after the execution of the __init__
method ?
The __post_init__
method is specific to the dataclasses
library, because the __init__
method on dataclass
classes is generated and overriding it would entirely defeat the purpose of generating it in the first place.
SQLAlchemy, on the other hand, provides an __init__
implementation on the base model class (generated for you with declarative_base()
). You can safely re-use that method yourself after setting up default values, via super().__init__()
. Take into account that the SQLAlchemy
-provided __init__
method only takes keyword arguments:
def __init__(self, useragent, profile):
"""specify the main information"""
id = generate_new_id(self)
super().__init__(id=id, useragent=useragent, profile=profile)
If you need to wait for the other columns to be given updated values first (because perhaps they define Python functions as a default
), then you can also run functions after calling super().__init__()
, and just assign to self
:
def __init__(self, useragent, profile):
"""specify the main information"""
super().__init__(useragent=useragent, profile=profile)
self.id = generate_new_id(self)
Note: you do not want to use the built-in id()
function to generate ids for SQL-inserted data, the values that the function returns are not guaranteed to be unique. They are only unique for the set of all active Python objects only, and only in the current process. The next time you run Python, or when objects are deleted from memory, values can and will be reused, and you can’t control what values it’ll generate next time, or in a different process altogether.
If you were looking to only ever create rows with unique combinations of the useragent
and profile
columns, then you need to define a UniqueConstraint
in the table arguments. Don’t try to detect uniqueness at the Python level, as you can’t guarantee that another process will not make the same check at the same time. The database is in a much better position to determine if you have duplicate values, without risking race conditions:
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"),
)
or you could use a composite primary key based on the two columns; primary keys (composite or otherwise) must always be unique:
class Worker(Base):
__tablename__='worker'
profile = Column(String(100), primary_key=True, nullable=False)
useragent = Column(String(100), primary_key=True, nullable=False)
I implemented the similar behavior using the __init_subclass__
method:
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__