I am using SQLAlchemy on my new project and would like to use __slots__ with models (in beta version without alchemy, __slots__ were necessary because a large number of objects was created). But I am unable to combine them with SQLAlchemy declarations and get the following error for code:
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class NotWorking(Base):
__tablename__ = 'table1'
pk = Column(Integer, primary_key=True)
name = Text(length=64, convert_unicode=True)
__slots__ = ['name', 'pk']
Error
ValueError: 'name' in __slots__ conflicts with class variable
Which is expected as __slots__ modify class to create descriptors for fields defined in them, so one workaround is to make hidden fields (_name) and make model fields act as properties as shown here:
class Working(Base):
__tablename__ = 'table2'
pk = Column(Integer, primary_key=True)
name = Text(length=64, convert_unicode=True)
__slots__ = ['_name', '_pk']
# Workaround
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
But this code can be a bit tedious to write and you need to add corresponding property for each new field. I am wondering, is there a better way to do without properties, while still using declarative base (without using Classical Mappings).
You can use simple Python statements on the class body to automate the creation of simple properties, based on the __slots__ attribute itself:
class Working(Base):
...
__slots__ = ['_name', '_pk']
for _tmp_name in __slots__:
locals()[_tmp_name[1:]] = property(lambda self, name=_tmp_name: getattr(self, name))
del _tmp_name
Note that while the dictionary returned y locals inside a function or method is somewhat special, in which modifications to it do not affect the actual variables, it works as a plain dictionary in the class body itself.
If you have several models,or find this too hacky, it can be put in a metaclass or __init_subclass__ method as well (Python 3.6+):
class MyBase(Base):
def __init_subclass__(cls, *args, **kwargs):
for name in cls.__slots__:
setattr(cls, name[1:], property(lambda self, name=name: getattr(self, name)))
class Working(MyBase):
...
For the metaclass case (python < 3.6), just put the same three lines in the metaclass __init__:
class MetaBase(type):
def __init__(cls, name, bases, namespace):
super().__init__(name, bases, namespace)
for name in cls.__slots__:
setattr(cls, name[1:], property(lambda self, name=name: getattr(self, name)))
class Working(Base, metaclass=MetaBase):
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With