4

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

jOOsko
  • 608
  • 6
  • 9
  • 1
    doesn't the statement `name = Text(length=64, convert_unicode=True)` create the correct descriptor already? So you'd have `__slots__ = ()` to indicate there are no extra instance variables that need to be stored? – Tadhg McDonald-Jensen Mar 27 '17 at 16:16
  • `__slots__` were intended to get decrease memory consumption, and maybe made object a bit more static, but (I found that after checking your comment) as Base does not define them, they are not exactly useful. `__dict__` still remains, and you can still add new fields, so they could only be used at most as a guideline. – jOOsko Mar 28 '17 at 19:46

1 Answers1

5

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):
    ...
jsbueno
  • 99,910
  • 10
  • 151
  • 209