以下代码是一个带有一个简单表的SqlAlchemy ORM的非常简单的实现。 Mytable类尝试从BaseAbstract继承。
代码抛出以下异常:
消息:元类冲突:派生类的元类必须是 所有基础的元类的(非严格的)子类
from abc import ABC
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
class BaseAbstract(ABC):
"""description of class"""
SQLALCHEMY_DATABASE_URI =\
'mssql+pyodbc://(local)/TestDB?driver=SQL+Server+Native+Client+11.0'
SQLALCHEMY_TRACK_MODIFICATIONS = False
engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
metadata = Base.metadata
class Mytable(Base, BaseAbstract):
__tablename__ = 'myTable'
id = Column(Integer, primary_key=True)
firstNum = Column(Integer, nullable=False)
secondNum = Column(Integer, nullable=False)
如果您将类声明行更改为
class Mytable(Base):
代码可以正常工作。此外,如果您将class BaseAbstract(ABC):
更改为class BaseAbstract(object):
,代码将再次正常运行。
如何从SQLAlchemy中的抽象类继承?
答案 0 :(得分:4)
Mixing metaclasses is not easy你应该避免它。 SQLAlchemy提供了一种处理abstract base classes或augmenting the base的方法,另一方面,您尝试做的事情看起来很像mixin。
您可以指示SQLAlchemy使用__abstract__
跳过为类创建表和映射器:
Base = declarative_base()
class BaseAbstract(Base):
"""description of class"""
__abstract__ = True
class Mytable(BaseAbstract):
...
class BaseAbstract:
"""description of class"""
Base = declarative_base(cls=BaseAbstract)
class Mytable(Base):
...
但在我看来,最简单的解决方案是放弃使用"抽象基础"总而言之,并将其视为mixin,正如您已经在某种程度上所做的那样:
class CommonMixin:
"""description of class"""
Base = declarative_base()
class Mytable(CommonMixin, Base):
...
但是如果你坚持使用实际的abc.ABC
抽象基类,register你的模型类作为虚拟子类:
class BaseAbstract(ABC):
"""description of class"""
Base = declarative_base()
@BaseAbstract.register
class Mytable(Base):
...
缺点是在实例化虚拟子类时不会检查@abc.abstractmethod
修饰的方法。
如果上述内容无法满足您的需求,并且您希望使用ABC
来检查是否已实现所需的方法,则可以尝试按照指示的异常进行操作,并创建一个新的元类,它是{{的组合1}}和ABCMeta
:
DeclarativeMeta
但是,我无法保证这一点。它可能包含不止一些惊喜。
答案 1 :(得分:1)
我遇到了同样的问题。除了 the very good answer 的 Ilja 并且仅仅因为在评论他们的答案时您正在询问
<块引用>我试图从继承自 ABC 的抽象类继承的原因是在函数中使用 isinstance,该函数将对象作为参数,如果返回 true,则通过调用抽象类中定义的方法进行额外处理。如果我不从 ABC 继承,Python 不会强制我需要确保继承的类实现抽象类的所有属性,如果我在实现中遗漏了一些,那么我将得到运行时异常。我宁愿得到一个构建异常。这就是 ABC 给我的。这不是使用 ABC 的好理由吗?
我想分享我来这里之前创建的解决方法。除了 ABC
之外,Python 还提供了另一种在类实例化期间检查某个方法是否存在的方法,即在 __init_subclass__
魔术方法中。我做了以下事情:
class BaseAbstract:
ABSTRACT_METHODS = [
"some_method", "another_method"
]
def __init_subclass__(cls):
for method_name in BaseAbstract.ABSTRACT_METHODS:
method = getattr(cls, method_name, None)
if not method or not callable(method):
raise TypeError(
f"Can't instantiate abstract class {cls.__name__} with "
f"abstract methods {method_name}."
)
使用 inspect
,您还可以检查对“抽象”方法签名的约束。
这是否比使用 Ilja 的想法更好,这是一个争论的问题。我只是想分享我的方法,而不是声称它优越。
我发现我的解决方法有两个简洁的特征:
我只有一个 SQLalchemy Base。所以我不必担心多个 Bases,例如在使用 alembic
的功能自动生成迁移或调用 Base.metadata.create_all()
我实际上不必了解问题所在。如果您从未处理过元数据冲突,那么使用我的方法更容易理解为什么它可以解决问题。
但是,它仍然可以被认为是某种hacky。它还具有在类初始化期间而不是在实例化期间检查方法存在的特征。这是一个限制,以防您有级联继承,其中所有方法都出现在最后一个子级上,但不是在继承链中的所有中间类上。
答案 2 :(得分:0)
您应该能够通过为Mytable
引入中间基类来解决问题。例如:
Base = declarative_base()
metadata = Base.metadata
class IntermediateBase(type(Base), BaseAbstract):
pass
class Mytable(IntermediateBase):
__tablename__ = 'myTable'
id = Column(Integer, primary_key=True)
firstNum = Column(Integer, nullable=False)
secondNum = Column(Integer, nullable=False)