Python、SQLAlchemy和Postgresql:理解继承
我最近开始接触Python、SQLAlchemy和Postgresql,正在努力理解继承的概念。
因为我接手了另一个程序员的代码,所以我需要搞清楚在继承的概念中,哪些是必要的,在哪里需要使用。
我有几个问题:
我可以只依靠SQLAlchemy来实现继承吗?换句话说,SQLAlchemy能否在没有指定INHERITS=?的情况下,对Postgresql数据库表应用继承?
使用声明性基础(declarative_base)这个技术是正确使用继承的必要条件吗?如果是这样的话,我们就得重写所有的代码,所以请不要让我失望。
假设我们可以使用表实例、空的实体类和mapper(),你能给我一个(非常简单的)例子,说明如何正确地进行这个过程吗?或者给我一个容易理解的教程链接——我还没有找到足够简单的教程。
我们正在处理的实际项目是房地产对象。所以我们基本上有: - 一个表 immobject(id, createtime) - 一个表 objectattribute(id, immoobject_id, oatype) - 几个属性表:oa_attributename(oa_id, attributevalue)
谢谢你的帮助。
Vincent
2 个回答
关于你的第三个问题,其实你不一定需要先声明空的实体类才能使用 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_class
和 named_orm_class
进行了些许改进,加入了一些装饰器等,提供了额外的功能,你也可以这样做。当然,底层其实还是在声明一个空的实体类。但你不需要这样做,除了这一次。
这样做可以让你暂时应付过去,直到你觉得自己不想再手动处理所有的连接查询,想要一个对象属性,能够在每次使用时自动对相关类进行懒加载查询。那时候你就可以考虑转向声明式编程(或者 Elixir)。
欢迎来到Stack Overflow:将来如果你有多个问题,建议每个问题单独发帖。如果有需要,可以把它们链接在一起,帮助提供更多背景信息。
在Postgres中,表的继承和Python中的类继承是完全不同的概念,解决的问题也不一样,SQLAlchemy并没有尝试把它们结合起来。
当你在Postgres中使用表继承时,其实是在数据库的结构层面做了一些技巧,这样可以强制执行一些更复杂的约束,这些约束用其他方式可能不容易表达。一旦你设计好了数据库结构,应用程序通常不会意识到这种继承关系。如果它们插入一行数据,这行数据就会神奇地出现在父表中(就像一个视图)。这在某些情况下是很有用的,比如让一些批量操作更高效(你可以直接删除一月份的表)。
这个概念和面向对象编程(无论是Python还是其他语言)中的继承是根本不同的。在面向对象编程中,应用程序是知道两个类型是相关的,并且子类型可以替代父类型的。“一个持有的东西是一个地址,一个联系人有一个地址,因此一个联系人可以有一个持有的东西。”
你需要使用哪种工具(这些工具大多是独立的)取决于你的应用程序。你可能不需要任何一种,也可能需要两种。
SQLAlchemy处理对象继承的机制灵活且强大,如果它符合你的特定需求,建议使用它,而不是自己搭建解决方案(这对几乎所有应用程序都适用)。
声明式扩展是一种便利;它允许你在一个“东西”中描述映射的表、Python类和两者之间的映射,而不是分开写三次。这让你的代码更“简洁”;不过,它只是建立在“经典SQLAlchemy”之上的一种便利,并不是必需的。
如果你发现需要在SQLAlchemy中可见的表继承,你映射的类和不使用这些特性时并没有什么不同;带有继承的表仍然是正常的关系(像表或视图),可以在Python代码中映射,而不需要了解继承关系。