继承

缘由

想象一下,您有一份人员名单,每个人都扮演着某个角色。有些人是学生,有些人是教授,有些人是员工。每个角色都有不同的属性。学生按其系别和年级而闻名。教授有一个系别(某些属性对所有或某些角色来说都是通用的)、时间表和其他属性。

如何在 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>
Get SQLObject at SourceForge.net. Fast, secure and Free Open Source software downloads