继承¶
缘由¶
想象一下,您有一份人员名单,每个人都扮演着某个角色。有些人是学生,有些人是教授,有些人是员工。每个角色都有不同的属性。学生按其系别和年级而闻名。教授有一个系别(某些属性对所有或某些角色来说都是通用的)、时间表和其他属性。
如何在 SQLObject 中实现这一点?嗯,显而易见的方法是创建一个带有描述或命名角色的列的 Person 表,以及一个用于每个角色的表。然后必须编写解释和取消引用角色列的代码。
嗯,下面描述的继承机制完全可以做到这一点!它只是自动完成的,并且对用户来说基本上是透明的。
首先,您创建一个 Person 表。这里没有什么神奇的
class Person(SQLObject):
name = StringCol()
age = FloatCol()
现在您需要一个角色层次结构
class Role(InheritableSQLObject):
department = StringCol()
神奇之处始于此!您从特殊根类 InheritableSQLObject
继承类,并提供一组对所有角色都通用的属性。其他角色必须从 Role 继承
class Student(Role):
year = IntCol()
class Professor(Role):
timetable = StringCol()
现在您希望在 Person 中有一个可以解释为角色的列。很简单
class Person(SQLObject):
name = StringCol()
age = FloatCol()
role = ForeignKey("Role")
真的,就这些!当询问其角色时,Person 会返回其 .role 属性取消引用并解释后的值。它不会返回 Role 类的实例,而是返回相应子类的实例 - Student 或 Professor。
这是一个基于人们最常遇到的任务的简要说明,但当然它可以用于远远超出人员/角色任务的范围。我也省略了说明中的所有细节。现在看看真正的运行程序
from sqlobject import *
from sqlobject.inheritance import InheritableSQLObject
__connection__ = "sqlite:/:memory:"
class Role(InheritableSQLObject):
department = StringCol()
class Student(Role):
year = IntCol()
class Professor(Role):
timetable = StringCol(default=None)
class Person(SQLObject):
name = StringCol()
age = FloatCol()
role = ForeignKey("Role", default=None)
Role.createTable()
Student.createTable()
Professor.createTable()
Person.createTable()
first_year = Student(department="CS", year=1)
lecturer = Professor(department="Mathematics")
student = Person(name="A student", age=21, role=first_year)
professor = Person(name="A professor", age=42, role=lecturer)
print student.role
print professor.role
它打印
<Student 1 year=1 department='CS'>
<Professor 2 timetable=None department='Mathematics'>
您可以获取所有可用角色的列表
print list(Role.select())
它打印
[<Student 1 year=1 department='CS'>, <Professor 2 timetable=None department='Mathematics'>]
瞧 - 您已经获得了一个 Role 子类的列表!
如果您向 Role 添加一个 MultipleJoin 列,则可以列出给定角色的所有人员
class Role(InheritableSQLObject):
department = StringCol()
persons = MultipleJoin("Person")
for role in Role.select():
print role.persons
它打印
[<Person 1 name='A student' age=21.0 roleID=1>]
[<Person 2 name='A professor' age=42.0 roleID=2>]
如果您希望您的角色拥有多个角色,请使用 RelatedJoin
class Role(InheritableSQLObject):
department = StringCol()
persons = RelatedJoin("Person")
class Student(Role):
year = IntCol()
class Professor(Role):
timetable = StringCol(default=None)
class Person(SQLObject):
name = StringCol()
age = FloatCol()
roles = RelatedJoin("Role")
Role.createTable()
Student.createTable()
Professor.createTable()
Person.createTable()
first_year = Student(department="CS", year=1)
lecturer = Professor(department="Mathematics")
student = Person(name="A student", age=21)
student.addRole(first_year)
professor = Person(name="A professor", age=42)
professor.addRole(lecturer)
print student.roles
print professor.roles
for role in Role.select():
print role.persons
它打印
[<Student 1 year=1 department='CS'>]
[<Professor 2 timetable=None department='Mathematics'>]
[<Person 1 name='A student' age=21.0>]
[<Person 2 name='A professor' age=42.0>]
谁、什么和如何¶
Daniel Savard 已为 SQLObject 实施了继承。在 ORM 术语 中,这是一种垂直继承。唯一的区别是对象引用其叶节点,而不是父节点。使用 Python 类的层次结构在运行时重建到父节点的链接。
- 正如 Ian Bicking 所建议的,每个子类现在都具有与父类相同的 ID。不再需要 childID 列和 parent 外键(以及一个小速度提升)。
- 不再需要调用 getSubClass,因为在创建类的实例时,总是会返回“最新”的子类。
- 此版本现在似乎可以与 addColumn、delColumn、addJoin 和 delJoin 正确配合使用。
以下代码
from sqlobject.inheritance import InheritableSQLObject
class Person(InheritableSQLObject):
firstName = StringCol()
lastName = StringCol()
class Employee(Person):
_inheritable = False
position = StringCol()
将生成以下表格
CREATE TABLE person (
id INT PRIMARY KEY,
child_name TEXT,
first_name TEXT,
last_name TEXT
);
CREATE TABLE employee (
id INT PRIMARY KEY,
position TEXT
)
添加了一个新的类属性 _inheritable
。当此新属性设置为 1 时,该类将标记为“可继承”,并且将自动添加新列:childName(TEXT)。
从父类继承的每个类都将获得与父类相同的 ID。因此,无需跟踪父 ID 和子 ID,因为它们是相同的。
childName 列将包含子类的名称(例如“Employee”)。这将允许一个类始终返回其子类(如果可用)(同时也是员工的人员将始终返回员工类的实例)。
例如,以下代码
p = Person(firstName='John', lastName='Doe')
e = Employee(firstName='Jane', lastName='Doe', position='Chief')
p2 = Person.get(1)
将在数据库中创建以下数据
*Person*
id
child_name
first_name
last_name
0
Null
John
Doe
1
Employee
Jane
Doe
*Employee*
id
position
1
Chief
您仍然可以正常询问属性:e.firstName 将返回 Jane,并且设置它将在 person 表中写入新值。
如果您使用 p2,因为 p2 是一个 person 对象,您将获得一个 employee 对象。person(0) 将返回一个 Person 实例,并将具有以下属性:firstName 和 lastName。person(1) 或 employee(1) 都将返回相同的 Employee 实例,并将具有以下属性:firstName、lastName 和 position。
此外,删除链接的人员或员工将按预期销毁这两个条目。
SQLObject q 魔术也起作用。使用这些选择是有效的
Employee.select(AND(Employee.q.firstName == 'Jane', Employee.q.position == 'Chief')) will return Jane Doe
Employee.select(AND(Person.q.firstName == 'Jane', Employee.q.position == 'Chief')) will return Jane Doe
Employee.select(Employee.q.lastName == 'Doe') will only return Jane Doe (as Joe isn't an employee)
Person.select(Person.q.lastName == 'Doe') will return both entries.
当与“继承”类一起使用时,SQL“where”子句将包含附加子句。这些子句是 id 和父 id 之间的链接。这将类似于以下请求
SELECT employee.id, person.first_name, person.last_name
FROM person, employee WHERE person.first_name = 'Jane'
AND employee.position = 'Chief' AND person.id = employee.id
限制和注意事项¶
- 仅单继承有效。无法从多个 SQLObject 类继承。
- 可以从继承的类继承,并且这将很好地工作。在上述示例中,您可以拥有一个从 Employee 继承的 Chief 类,并且所有父属性都将通过 Chief 类获得。
- 您不得在继承的类中重新定义列(这将引发异常)。
- 如果您不希望在最后一个类(永远不会被继承的类)中出现“childName”列,则必须在此类中将“_inheritable”设置为 False。
- 继承实现与延迟更新不兼容。不要将 lazyUpdate 设置为 True。如果您需要此功能,则必须修补 SQLObject 并覆盖许多方法 - 至少是 _SO_setValue()、sync()、syncUpdate()。将乐于接受修补程序。
- 您最好将自己限制在简单的用例中。继承实现很容易在更复杂的情况下窒息。
- 从同一父类继承的表之间的联接会产生不正确的结果,因为联接到了相同的父表(它们必须使用不同的别名)。
- 继承分两个阶段进行 - 首先,它从父表中提取 ID,然后从子表中提取行。如果您尝试做复杂的事情,第一阶段可能会失败。例如,Children.select(orderBy=Children.q.column, distinct=True) 可能会失败,因为在第一阶段,继承会为父表生成一个 SELECT 查询,其中 ORDER BY 是子表中的列。
- 我制作它是因为我需要能够在链接表中进行自动继承。
- 此版本对我有效;它可能对您不起作用。我尽我所能,但有可能我破坏了一些东西……因此,无法保证此版本能够正常工作。
- 感谢 Ian Bicking 提供 SQLObject;这是一个很棒的 Python 模块。
- 尽管所有属性都已继承,但与 sqlmeta 数据不适用。不要尝试通过继承 InheritableSQLObject 的 sqlmeta.columns 字典获取父列:它会引发 KeyError。连接也适用:如果在父类中定义了连接,则在继承 InheritableSQLObject 中,sqlmeta.joins 列表将为空,即使连接方法将正常工作。
- 如果您对此补丁有任何建议、错误或补丁,可以联系 SQLObject 团队:<sqlobject-discuss at lists.sourceforge.net>