从抛出的抽象类异常继承SQLAlchemy类时:元类冲突:派生类的元类必须是

时间:2018-03-30 21:54:19

标签: python python-3.x sqlalchemy abstract-class

以下代码是一个带有一个简单表的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中的抽象类继承?

3 个答案:

答案 0 :(得分:4)

Mixing metaclasses is not easy你应该避免它。 SQLAlchemy提供了一种处理abstract base classesaugmenting the base的方法,另一方面,您尝试做的事情看起来很像mixin

您可以指示SQLAlchemy使用__abstract__跳过为类创建表和映射器:

Base = declarative_base()

class BaseAbstract(Base):
    """description of class"""
    __abstract__ = True

class Mytable(BaseAbstract):
    ...

你也可以augment the Base class

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 answerIlja 并且仅仅因为在评论他们的答案时您正在询问

<块引用>

我试图从继承自 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 的想法更好,这是一个争论的问题。我只是想分享我的方法,而不是声称它优越。

我发现我的解决方法有两个简洁的特征:

  1. 我只有一个 SQLalchemy Base。所以我不必担心多个 Bases,例如在使用 alembic 的功能自动生成迁移或调用 Base.metadata.create_all()

  2. 我实际上不必了解问题所在。如果您从未处理过元数据冲突,那么使用我的方法更容易理解为什么它可以解决问题。

但是,它仍然可以被认为是某种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)