将 __slots__ 与 SQLAlchemy 模型一起使用
Using __slots__ with SQLAlchemy model
我正在我的新项目中使用 SQLAlchemy,并希望将 __slots__
与模型一起使用(在没有 Alchemy 的测试版中,__slots__
是必需的,因为创建了大量对象)。但我无法将它们与 SQLAlchemy 声明结合起来,并得到以下代码错误:
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']
错误
ValueError: 'name' in __slots__ conflicts with class variable
预期 __slots__
修改 class 为其中定义的字段创建描述符,因此一种解决方法是创建隐藏字段 (_name) 并使模型字段充当属性,如此处所示:
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
但是这段代码写起来有点乏味,你需要为每个新字段添加相应的属性。我想知道,有没有更好的方法可以不用属性,同时仍然使用声明性基础(不使用经典映射)。
您可以在 class body 上使用简单的 Python 语句来自动创建基于 __slots__
属性本身的简单属性:
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
请注意,虽然在函数或方法中返回 y locals
的字典有些特殊,对它的修改不会影响实际变量,但它在 class body本身。
如果你有多个模型,或者觉得这太老套了,它也可以放在元class或__init_subclass__
方法中(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):
...
对于 metaclass 情况 (python < 3.6),只需将相同的三行放在 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):
...
我正在我的新项目中使用 SQLAlchemy,并希望将 __slots__
与模型一起使用(在没有 Alchemy 的测试版中,__slots__
是必需的,因为创建了大量对象)。但我无法将它们与 SQLAlchemy 声明结合起来,并得到以下代码错误:
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']
错误
ValueError: 'name' in __slots__ conflicts with class variable
预期 __slots__
修改 class 为其中定义的字段创建描述符,因此一种解决方法是创建隐藏字段 (_name) 并使模型字段充当属性,如此处所示:
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
但是这段代码写起来有点乏味,你需要为每个新字段添加相应的属性。我想知道,有没有更好的方法可以不用属性,同时仍然使用声明性基础(不使用经典映射)。
您可以在 class body 上使用简单的 Python 语句来自动创建基于 __slots__
属性本身的简单属性:
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
请注意,虽然在函数或方法中返回 y locals
的字典有些特殊,对它的修改不会影响实际变量,但它在 class body本身。
如果你有多个模型,或者觉得这太老套了,它也可以放在元class或__init_subclass__
方法中(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):
...
对于 metaclass 情况 (python < 3.6),只需将相同的三行放在 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):
...