Python、SQLAlchemy和Postgresql:理解继承

1 投票
2 回答
1565 浏览
提问于 2025-04-17 03:47

我最近开始接触Python、SQLAlchemy和Postgresql,正在努力理解继承的概念。

因为我接手了另一个程序员的代码,所以我需要搞清楚在继承的概念中,哪些是必要的,在哪里需要使用。

我有几个问题:

  1. 我可以只依靠SQLAlchemy来实现继承吗?换句话说,SQLAlchemy能否在没有指定INHERITS=?的情况下,对Postgresql数据库表应用继承?

  2. 使用声明性基础(declarative_base)这个技术是正确使用继承的必要条件吗?如果是这样的话,我们就得重写所有的代码,所以请不要让我失望。

  3. 假设我们可以使用表实例、空的实体类和mapper(),你能给我一个(非常简单的)例子,说明如何正确地进行这个过程吗?或者给我一个容易理解的教程链接——我还没有找到足够简单的教程。

我们正在处理的实际项目是房地产对象。所以我们基本上有: - 一个表 immobject(id, createtime) - 一个表 objectattribute(id, immoobject_id, oatype) - 几个属性表:oa_attributename(oa_id, attributevalue)

谢谢你的帮助。

Vincent

2 个回答

0

关于你的第三个问题,其实你不一定需要先声明空的实体类才能使用 mapper。如果你的应用不需要复杂的属性,你可以直接用反射和 metaclasses 来建模现有的数据库表,而不需要先定义它们。下面是我做的示例:

mymetadata = sqlalchemy.MetaData()
myengine = sqlalchemy.create_engine(...)

def named_table(tablename):
  u"return a sqlalchemy.Table object given a SQL table name"
  return sqlalchemy.Table(tablename, mymetadata, autoload=True, autoload_with=myengine)

def new_bound_class(engine, table):
  u"returns a new ORM class (processed by sqlalchemy.orm.mapper) given a sqlalchemy.Table object"
  fieldnames = table.c.__dict__['_data']
  def format_attributes(obj, transform):
    attributes = [u'%s=%s' % (x, transform(x)) for x in fieldnames]
    return u', '.join(attributes)
  class DynamicORMClass(object):
    def __init__(self, **kw):
      u"Keyword arguments may be used to initialize fields/columns"
      for key in kw:
        if key in fieldnames: setattr(self, key, kw[key])
        else: raise KeyError, '%s is not a valid field/column' % (key,)
    def __repr__(self):
      return u'%s(%s)' % (self.__class__.__name__, format_attributes(self, repr))
    def __str__(self):
      return u'%s(%s)' % (str(self.__class__), format_attributes(self, str))
  DynamicORMClass.__doc__ = u"This is a dynamic class created using SQLAlchemy based on table %s" % (table,)
  return sqlalchemy.orm.mapper(DynamicORMClass, table)

def named_orm_class(table):
  u"returns a new ORM class (processed by sqlalchemy.orm.mapper) given a table name or object"
  if not isinstance(table, Table):
    table = named_table(table)
  return new_bound_class(table)

使用示例:

>>> myclass = named_orm_class('mytable')
>>> session = Session()
>>> obj = myclass(name='Fred', age=25, ...)
>>> session.add(obj)
>>> session.commit()
>>> print str(obj)  # will print all column=value pairs

我对 new_bound_classnamed_orm_class 进行了些许改进,加入了一些装饰器等,提供了额外的功能,你也可以这样做。当然,底层其实还是在声明一个空的实体类。但不需要这样做,除了这一次。

这样做可以让你暂时应付过去,直到你觉得自己不想再手动处理所有的连接查询,想要一个对象属性,能够在每次使用时自动对相关类进行懒加载查询。那时候你就可以考虑转向声明式编程(或者 Elixir)。

4

欢迎来到Stack Overflow:将来如果你有多个问题,建议每个问题单独发帖。如果有需要,可以把它们链接在一起,帮助提供更多背景信息。

在Postgres中,表的继承和Python中的类继承是完全不同的概念,解决的问题也不一样,SQLAlchemy并没有尝试把它们结合起来。

当你在Postgres中使用表继承时,其实是在数据库的结构层面做了一些技巧,这样可以强制执行一些更复杂的约束,这些约束用其他方式可能不容易表达。一旦你设计好了数据库结构,应用程序通常不会意识到这种继承关系。如果它们插入一行数据,这行数据就会神奇地出现在父表中(就像一个视图)。这在某些情况下是很有用的,比如让一些批量操作更高效(你可以直接删除一月份的表)。

这个概念和面向对象编程(无论是Python还是其他语言)中的继承是根本不同的。在面向对象编程中,应用程序是知道两个类型是相关的,并且子类型可以替代父类型的。“一个持有的东西是一个地址,一个联系人有一个地址,因此一个联系人可以有一个持有的东西。”

你需要使用哪种工具(这些工具大多是独立的)取决于你的应用程序。你可能不需要任何一种,也可能需要两种。


  1. SQLAlchemy处理对象继承的机制灵活且强大,如果它符合你的特定需求,建议使用它,而不是自己搭建解决方案(这对几乎所有应用程序都适用)。

  2. 声明式扩展是一种便利;它允许你在一个“东西”中描述映射的表、Python类和两者之间的映射,而不是分开写三次。这让你的代码更“简洁”;不过,它只是建立在“经典SQLAlchemy”之上的一种便利,并不是必需的。

  3. 如果你发现需要在SQLAlchemy中可见的表继承,你映射的类和不使用这些特性时并没有什么不同;带有继承的表仍然是正常的关系(像表或视图),可以在Python代码中映射,而不需要了解继承关系。

撰写回答