一、简介
在过去的十年中,形式本体已经广泛应用于计算机科学中来构建数据和知识。与此同时,Python 编程语言在教学、商业和研究中越来越广泛。然而,直到最近,只有很少的工具和资源致力于使用 Python 中的本体。事实上,大多数关于本体的书籍或教程都是相当理论化的,并没有涉及编程,或者它们局限于像 Java 这样更复杂的语言。
这个问题在生物医学领域尤其重要,在这个领域,本体和 Python 被广泛使用。在我作为索邦巴黎大学医学信息学教师和研究人员的日常生活中,我经常看到学生和工程师构建后来没有被使用的本体。这些文件保存在一个 u 盘上,因为不容易将本体与现有的软件整合。
这本书的存在就是为了填补这个空白。它展示了如何使用 Python 轻松访问本体并将其发布为动态网站,以构建新的本体,执行自动推理,将实体链接到医学术语,或在 DBpedia 中进行一些研究…使用 Owlready,这是我从 2013 年开始为“面向本体的编程”开发的 Python 模块。并且,在这本书里,我们不会害怕实现基于本体的程序:你会看到比数学公式更多的源代码!
1.1 这本书是给谁的?
这本书是为任何想用 Python 操作和构建本体,或者想从实用的角度发现本体世界的人准备的,尤其是为计算机科学家和语义网应用开发人员、生物信息学家、人工智能领域的科学家、这些学科的学生…或者只是为好奇的人准备的!
要读这本书,建议了解一下面向对象编程,用 Python 或者另一种面向对象语言(Java,C++等)。).另一方面,不需要了解 Python 语言或掌握正式的本体,章节 2 和 3 包含提醒。
1.2 为什么是本体论?
本体论的概念来源于柏拉图的哲学和著作。在计算机科学中,本体是“一个领域中所有实体以及这些实体之间存在的关系的正式描述”。这个定义可能看起来很复杂!事实上,它是以一种机器可以利用的方式来描述知识,并关注完整性和“普遍性”。本体是所谓的“符号”人工智能的一部分,它由结构化知识组成,使其可以被计算机访问,这与机器学习(如神经网络、深度学习等)相反。).
下图展示了一个非常简单的生态学领域的本体论的例子,用图解法表示(注意:“狗鱼”和“蟑螂”是两种鱼类):
这里,我们有八个实体,用矩形表示,以及这些实体之间的关系。存在几类关系:
-
层级“是-a”关系:它们将一个实体链接到一个更一般的实体。例如,人是动物,狗鱼是动物,多氯联苯是污染物,等等。在编程中,术语“继承”也用于命名这些关系。
-
地理关系(“生活”、“存在于”):它们指示实体的位置,将实体链接到一个地方。例如,狗鱼生活在湖里。
-
各种横向关系(“吃”,“集中”):例如,人类吃梭鱼。
通过查阅这个图表,你会很容易地推断出一个人很可能是被多氯联苯所陶醉。本体的优势是使这种推理不仅可以被人类使用,也可以被机器使用:在一种名为 reasoner 的软件的帮助下,计算机将能够重现这种推理,并推断出人类有被 PCB 毒害的风险。
为此,本体依赖于描述逻辑(参见附录 A)。OWL 语言(万维网联盟 W3C 标准化的网络本体语言)是最常用于形式化本体的语言之一。OWL 支持大量不同的描述逻辑。OWL 语言可以翻译成 RDF(资源描述框架),RDF 本身通常用 XML(可扩展标记语言)表示。
本体有两个主要目的:
-
自动推理:由于概念、关系及其属性的集合是以一种正式的方式描述的,因此自动执行逻辑推理成为可能。
-
知识的重用:所有的本体共享相同的名称空间,并且可以链接在一起,导致了语义网。
此外,还有许多为本体设计的工具,如 Protégé editor 或 HermiT and Pellet reasoners。使用本体论允许你使用所有这些工具,尽管对于一个给定的项目,你可能不需要本体论的全部潜力。
1.3 为什么选择 Python?
最常用于处理本体的编程语言是 Java。然而,Java 是一种复杂的语言,而且在某些领域很少使用,例如生物医学领域。
相反,今天兴起的语言是 Python,尤其是在生物医学领域(的确,这本书的几个例子将来自生物学或医学)。与其他编程语言相比,Python 的主要优势在于它优化了程序员的时间:与大多数其他语言相比,Python 允许程序员更快地开发他/她的程序。15 年前,当我意识到用 Python 只需要一天就可以完成用 Java 需要三天的任务时,这就是我选择 Python 的原因!
如今,Python 经常被用作链接其他组件的粘合剂,如数据库、网站、文本文件…或本体,正如我们将在本书中看到的。
1.4 为什么 Owlready?
Owlready 允许“面向本体的编程”,即对象和类是一个本体的实体的面向对象编程。面向本体的编程是一种比通常的 Java 应用编程接口(API)更简单和更强大的方法,如由 OWLAPI 和 JENA 提出的,其中本体的实体的行为不像编程语言的对象和类。
Owlready 提供了三个世界中最好的:
-
形式本体的表达能力,也就是说,详细描述复杂知识、将它们联系在一起以及对这些知识进行推理的能力
-
关系数据库的访问速度,具有快速存储和搜索能力
-
Python 等面向对象编程语言的灵活性,能够执行“命令性”的代码行,向计算机发出“命令”,这是本体或数据库本身无法做到的
Owlready 包含了一个带有 OWL 语义层的图形数据库。这个数据库被称为 quadstore ,因为它以 RDF 格式存储四元组,也就是说,RDF 三元组的形式(subject,property,object)添加了一个本体标识符(参见第十一章,了解 RDF 和 Owlready 的 quadstore 结构的更详细的解释)。
这个 quadstore 以一种紧凑的格式存储来自已加载的本体的所有信息。它可以以 SQLite3 数据库文件的形式放在 RAM 或磁盘上。然后,Owlready 在需要时将本体实体加载到 Python 中,并在不再需要时自动从 RAM 中删除它们。此外,如果在 Python 中修改了这些实体,Owlready 会自动更新 quadstore。
下图显示了 Owlready 的一般架构:
这种架构使得加载大量本体(几十或几百千兆字节)同时非常快速地访问特定实体成为可能,例如,通过文本搜索。它还允许对应于 OWL 本体的语义级别(不像许多图形数据库局限于 RDF 级别)。然而,Owlready 也可以用作简单的对象数据库、图形数据库或对象关系映射器(ORM ),而没有利用本体的表达能力所带来的好处。
Owlready 是作为自由软件发布的(GNU LGPL 许可证)。本书涵盖了 owl ready 2-0.25 版本(owlready2
模块)。关于其安装,您可以参考第 2.11 节。如果您在学术环境中使用 Owlready,请引用以下文章:
- 拉米 JB。owl ready:Python 中面向本体的编程,具有生物医学本体的自动分类和高级构造 。医学中的人工智能 2017;80:11-28
http://www.lesfleursdunormal.fr/_downloads/article_owlready_aim_2017.pdf
1.5 图书大纲
前两章包含提醒:第二章介绍 Python,第三章介绍 OWL 本体。如果你已经掌握了这些概念,你可以快速阅读这些章节。
然后第 4 、 5 和 6 章解释了如何用 Owlready 在 Python 中操作和创建本体。这些章节介绍了 Owlready 的基本特性。
以下章节描述了更具体的功能。第章 7 涉及自动推理,第章 8 涉及注释和文本搜索,第章 9 涉及医学术语的管理。
最后,最后两章描述了高级特性。第十章展示了如何将 Python 方法集成到 OWL 本体的类中,第十一章展示了如何直接访问 Owlready 的 RDF quadstore。
这本书的源代码可以在 GitHub 上通过这本书的产品页面获得,位于 www.apress.com/978-1-4842-6551-2
。
1.6 摘要
在这一介绍性章节中,我们介绍了形式本体论、Python 和 Owlready,并且我们画出了本书内容的大纲。
二、Python 语言:玩转一条蛇!
Python 是一种通用且易学的编程语言。它已经存在了近 30 年,但多年来一直保密,现在取得了巨大的成功——成为当今教授范围最广的编程语言之一。Python 的主要优势在于它的简单性和为用户节省时间:使用 Python,我可以在一天内完成用 Java 三天、用 c 语言一周才能完成的工作。Python 极大地提高了生产率。
Python 是一个开源软件,而且是免费的。它可以在几乎所有现有的操作系统上运行(Linux PC、Windows PC、Mac、Android 等)。).Python 历史上有两个版本:版本 2.x(不再支持,但仍被旧程序使用)和版本 3.x(目前支持和推荐)。Owlready 需要版本 3.x,所以我们将在本书中使用这个版本。然而,这两个版本之间的差异微乎其微。
在本章中,我们将快速介绍 Python 语言及其语法的基础知识。然而,如果你还没有编程技能,我们建议你首先查阅一本专门学习 Python 的书。相反,如果你已经知道 Python 语言,你可以直接进入 2.11 节安装 Owlready。
2.1 安装 Python
在 Linux 下,几乎所有的发行版都提供了 Python 的包(通常这些包已经安装好了)。您可以检查它们是否存在于您的发行版的包管理器中,并在必要时安装包 python3。此外,如果您的发行版将 python3-pip 和 python3-idle 包与主 python3 包区分开来,请安装它们。
在 Windows 上,需要安装 Python。您可以从以下地址下载它:
在 Mac OS 上,很可能已经安装了 Python 您可以通过在终端中运行命令“python3 -v”来验证它。否则,请从前面的网站安装。
2.2 启动 Python
要用 Python 编程,可以使用集成开发环境(IDE)或者使用文本编辑器和终端。如果您是 Python 的新手,第一个选项可能是最简单的;我们建议使用通常随 Python 3 一起安装的空闲环境。
Python 是一种解释型语言,因此它可以在两种不同的模式下使用:
- “shell”模式,在这种模式下,当程序员输入代码时,计算机会一行一行地解释它们。这种模式便于执行快速测试。由 IDLE 打开的默认“Shell”窗口对应于这种模式(参见下面的示例)。行首的“
>>>
”符号是 Python 的命令提示符:解释器提示你输入新的一行代码。
注意,在“shell”模式下,输入的代码行不保存,在关闭终端或空闲时会丢失!
- “程序”模式,用户编写一个多行程序,然后计算机执行整个程序。这种模式允许你执行复杂的程序。与闲置,你可以创建一个新的程序与文件➤新文件菜单。将出现一个新窗口,您将在其中编写程序(参见下面的示例)。然后文件将被保存(扩展名为。py)并可通过运行➤运行模块菜单(或按 F5 键)执行。
在 Linux 上,您可能更喜欢使用文本编辑器输入程序(例如 Emacs、Vi)和终端来执行它们:
- 要拥有“shell”模式,在终端中执行命令“
python3
”:
[Bash prompt]# python3
Python 3.7.1 (default, Oct 22 2018, 10:41:28)
[GCC 8.2.1 20180831] on linux
Type "help", "copyright", credits or "license" for more information.
>>>
要退出 Python,请按 Ctrl+D。
- 要运行一个程序,在终端中运行命令“
python3 file_name.py
”(很明显,用保存程序的文件名替换file_name.py
,如果需要,用路径替换)。
按照惯例,在本书中,我们将以“shell”模式的方式编写 Python 代码的简短示例:Python 代码的前面是命令提示符“>>>
”,而这些行显示的最终输出不带此前缀,例如:
>>> print("Hello again!")
Hello again!
要执行这个示例,永远不要输入“>>>
”提示符(无论是在“shell”模式下还是在“program”模式下)。只需输入提示后面的代码。当命令占用多行时,Python 会在“shell”模式中添加“...
”,如下例所示:
>>> print(
... "Still here ?")
Still here ?
这是一个“命令结束”提示。和以前一样,不应输入“...
”。
较长的代码示例将以程序的形式呈现,如下所示:
# File file_name.py
print("It's me again!")
print("See you soon.")
第一行只表示文件名;它不必输入到程序中。
最后,在代码行中,由于本书页面的宽度有限,↲字符将被用在一行的末尾来表示换行符。在这种情况下,您不必在编程时回到该行,例如:
>>> x = "This is a very long text here, isn't it?"↲
+ "Indeed, it is."
2.3 语法
评论
在 Python 中,哈希字符“#”后面的任何内容都是注释,Python 解释器不会考虑这些内容。注释是用来给将要阅读程序的程序员提供指导的,但是被机器忽略了。这里有一个例子:
>>> # This text is a comment, and thus it is ignored by Python!
屏幕上的书写
print()
函数用于在屏幕上书写(在 shell 中,或在“程序”模式下的标准输出上);我们以前已经遇到过。可以显示几个用逗号分隔的值:
>>> print("My age is", 40)
My age is 40
print()
功能在“shell”模式下可以省略,但在“program”模式下是必须的。
>>> print(2 + 2)
4
>>> 2 + 2
4
帮助
Python 有大量预定义的函数。在“shell”模式中,help()
函数用于获取某个函数的帮助,例如,print()
函数:
>>> help(print)
然后,在“shell”模式下,您可以通过按键盘上的“Q”键退出手册页。
变量
变量是与值相关联的名称。通常,该值只有在程序被执行时才是已知的(例如,当它是计算的结果时)。
变量名必须以字母或下划线“_”开头,可以包含字母、数字和下划线。Python 3 接受变量名中的重音字符,但禁止使用空格。
在 Python 中,变量不需要声明,也不需要类型化。因此,同一个变量可以包含任何类型的数据,并且其值的类型可以在程序中改变。运算符“=”用于定义(或重新定义)变量的值;可以读作“取值”(注意,这不是数学中“=”的通常含义)。
>>> age = 39
>>> print(age)
39
在计算中,变量的名称由它们的值代替:
>>> age + 1
40
“=”运算符也可用于重新定义变量的值。例如,要将变量 age 的值增加 1,我们将:
>>> age = age + 1
>>> age
40
缩进
缩进对应于代码行左边的空格。不像大多数其他编程语言,缩进只是一种约定,在 Python 中缩进是有意义的。因此,一个坏的缩进在 Python 中是一个语法错误!特别是,我们不应该在条件和循环的左边添加空格(我们将在后面看到)。以下示例显示了一个缩进错误:
>>> age
File "<stdin>", line 1
age
^
IndentationError: unexpected indent
此外,建议您在缩进 Python 程序时不要混用空格和制表符。
2.4 主要数据类型
Python 可以操作各种数据类型:整数(缩写为 int )、实数(通常称为 float )、Unicode 字符串(缩写为 str )、布尔值(true 或 false value,缩写为 bool )。变量的数据类型不必声明,在程序执行过程中可能会改变。以下是各种数据类型的示例:
>>> age = 31 # Integer
>>> weight = 64.5 # Floating-point number
>>> name = "Jean-Baptiste Lamy" # Character string
>>> teacher = True # Boolean
>>> student = False # Boolean
2.4.1 整数(int
)和浮点数(float
)
整数是没有小数部分的数字。Python 中对整数值没有限制。
在计算机科学中,实数通常由浮点数表示(它们被称为“float ”,因为逗号的位置据说是浮动的:小数部分之前可以有很多位,之后很少,反之亦然)。点用于表示小数部分,如下例所示:
>>> poids = 64.4
在 Python 中,floats 的精度实际上相当于许多其他编程语言(包括 C、C++和 Java)中的“double”数。
注意,10.0 是浮点数,而 10 是整数。
下表总结了数字的主要代数运算:
|代数运算
|
例子
|
| — | — |
| 添加 | >>> 2 + 2
four |
| 减法 | >>> 4 - 2
Two |
| 增加 | >>> 3 * 4
Twelve |
| 分开 | >>> 10 / 3``3.3333333333333
|
| 整数除法 | >>> 10 // 3``3
|
| 力量 | >>> 3 ** 2``9
|
布尔函数(bool
)
布尔值可以接受两个值,在 Python 中分别写成 True(真,整数值 1)和 False(假,整数值 0)。
2.4.3 字符串(str
)
字符串是文本或文本的一部分。字符数没有限制(零个、一个或多个)。字符串总是用引号括起来(单引号或双引号;最好使用双引号,因为单引号与撇号是同一个字符)。在 Python 3 中,所有字符串都是 Unicode 的,因此可以包含任何语言的任何字符。
>>> name = "Jean-Baptiste Lamy"
>>> empty_string = ""
要在字符串中插入特殊字符,请使用以反斜杠开头的转义码。以下是最常见的:
|特殊字符
|
转义码
|
| — | — |
| 换行符 | \n
|
| 标签 | \t
|
| 反斜线符号 | \\
|
| 简单引用 | \'
|
| 双引号 | \
|
特别是,在 Windows 上,文件名和路径中的反斜杠必须是双重的,例如,“C:\\directory\\file.py
”。
Python 还允许长字符串,它可以跨越多行并包含引号。一个长字符串以三个引号开始,也以三个引号结束,如下例所示:
>>> long_string = """This character string is long
... and may contain line breaks and
... quotation marks " without problems.
... Backslashs \\ must still be doubled, though."""
单引号也可以用于长字符串。
在 Python 中,一切都是对象,包括字符串。因此它们有方法,我们可以用指向符号“object.method (parameters,…)".下表总结了对字符串的主要操作和方法。
|字符串操作
|
例子
|
| — | — |
| 获取字符串的长度 | >>> s =``Goodbye``>>> len(s)``7
|
| 获取字符串中的一个字符(注意,第一个字符是零而不是一;负数从末尾算起) | >>> s[0]``G``# First character``>>> s[-1]``"e" # Last character
|
| 得到绳子的一部分 | >>> s[0:4]``"Good"
|
| 查找一个字符串是否包含在另一个中 | >>> s.find("bye")``4 # Found in position 4``# (return -1 if not found)
|
| 从字符串末尾开始搜索(R 代表右) | >>> s.rfind("o")``2 # Found in position 2``# (return -1 if not found)
|
| 根据分隔符拆分字符串 | >>> "alpha;beta;gamma".↲ split(";")``["alpha", "beta", "gamma"]
|
| 根据空格(空格、换行符和制表符)剪切字符串 | >>> "alpha beta gamma".↲split()``["alpha", "beta", "gamma"]
|
| 用另一个字符串替换一个字符串的一部分 | >>> "Come here!".↲replace("here", "there")``"Come there!"
|
| 把两根绳子连接起来。注意,如果你想要一个空格,你必须添加一个 | >>> "JB" + "LAMY"``"JBLAMY"
|
| 用值格式化字符串 | >>> last_name = "LAMY"``>>> first_name = "JB"``>>> "Hello %s!" %↲ first_name``"Hello JB!"``>>> "Hello %s %s!" %↲ (first_name, last_name)``"Hello JB LAMY!"``>>> rate = 90``>>> "Success rate: %s %%"↲% rate``"Success rate: 90 %"
|
2.4.4 清单(list
列表包含零个、一个或多个元素(它们类似于其他编程语言中的数组,但是它们的大小可以不同)。元素可以是不同的类型(整数、字符串等。).列表是用方括号创建的;元素放在方括号中,用逗号分隔,例如:
>>> my_list = [0, "Lamy", True]
>>> empty_list = [ ]
在一个由 n 个元素组成的列表中,这些元素从 0 到n1 进行编号。按照惯例,列表通常会收到一个复数变量名,例如,动物列表的“animals”。
Python 列表也是对象。下表总结了列表上可用的主要操作和方法。
|列表操作
|
例子
|
| — | — |
| 创建列表 | >>> animals = ["elephant",``... "giraffe",``... "rhinoceros",``... "gazelle"]
|
| 获取列表的长度 | >>> len(animals)``4
|
| 从列表中获取一个元素(注意,列表是从零而不是一开始编号的) | >>> animals[0]``"elephant" # First``>>> animals[-1]``"gazelle" # Last
|
| 获得列表的一部分 | >>> animals[0:2]``["elephant", "giraffe"]
|
| 在末尾添加一个元素 | >>> animals.append("lion")
|
| 向给定位置添加一个元素(0:第一个位置,依此类推。) | >>> animals.insert(0, "lynx")
|
| 连接两个列表 | >>> [1, 2] + [3, 4, 5]``[1, 2, 3, 4, 5]
|
| 移除给定元素 | >>> animals.↲remove("gazelle")
|
| 移除给定位置的元素 | >>> del animals[-2]
|
| 查找列表中是否存在某个元素 | >>> "lion" in animals``True
|
| 对列表排序(默认为升序/字母顺序) | >>> animals.sort()
|
| 从列表中获取最高的元素,或者最低的元素 | >>> max([2, 1, 4, 3])``4``>>> min([2, 1, 4, 3])``1
|
2.4.5 元组(" ?? ")
元组非常类似于列表,区别在于它们是不可修改的。元组写在圆括号中,而不是方括号中:
>>> triple = (1, 2, 3)
>>> pair = (1, 2)
>>> single_element_tuple = (1,) # Do not forget the comma here!
2.4.6 字典(dict
和defaultdict
)
字典(或关联数组、哈希表或哈希表)将键映射到值。例如,字典可以将一个单词与其定义相匹配(因此有了字典名)。使用大括号创建字典,在大括号中放置零个、一个或多个“键:值”对,用“,”分隔。例如(请记住,行首的“...
”是 Python 提示的一部分,不应由程序员输入):
>>> my_dict = {
... "fruit" : "a plant food with a sweet taste",
... "apple" : "a fleshy fruit with a red or green skin",
... "orange" : "a juicy fruit with an orange skin",
... }
在前面的例子中,关键字是“水果”、“苹果”和“橘子”,值是定义。每个键有且只有一个值。
字典的键必须是不可变的(即不可修改的)。因此,我们不能使用列表作为键(通常使用元组代替)。
Python 字典也是对象。下表总结了对字典的主要操作和方法。
|字典操作
|
例子
|
| — | — |
| 获取字典中键(或值)的数量 | >>> len(my_dict)``3
|
| 获取与键关联的值 | >>> my_dict["apple"]``"a fleshy fruit with a red or green skin"
|
| 添加或修改给定键的值 | >>> my_dict["clef"] = "value"
|
| 删除一个键(及其相关值) | >>> del my_dict["clef"]
|
| 搜索字典中是否有关键字 | >>> "apple" in my_dict``True
|
| 找回所有的钥匙 | >>> for key in my_dict: ...``or``>>> keys = list(my_dict.↲keys())
|
| 恢复所有值 | >>> for value in my_dict.↲values(): ...``or``>>> values = list(my_dict.↲values())
|
| 收集所有(键,值)对(作为元组) | >>> for key, value in↲my_dict.items(): ...``or``>>> pairs = list(my_dict.↲items())
|
Python 还提供了一个默认的字典,叫做defaultdict
,这通常很有用。它是在collections
模块中定义的(我们将在后面看到模块;在下面的例子中,第一行对应于模块的导入;参见 2.10.1)。当您从默认字典中获取一个值,而字典中没有该键时,它会自动添加一个默认值。当它被创建时,defaultdict
接受一个默认数据类型的参数(它可以是一个数据类型、一个函数或者一个类,我们将在后面看到)。
以下示例创建一个 int 类型的defaultdict
。默认值是整数 0。
>>> from collections import defaultdict
>>> d = defaultdict(int)
>>> d["new_key"]
0
>>> d["new_key"] = d["new_key"] + 1
>>> d["new_key"]
1
>>> d["new_key"] = d["new_key"] + 1
>>> d["new_key"]
2
下面是第二个创建列表类型的defaultdict
的例子。因此,默认值是一个空列表。当每个键可能被映射到几个值(即一个值列表)时,通常使用列表的defaultdict
。
>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> d["new_key"]
[]
>>> d["new_key"].append("a")
>>> d["new_key"]
['a']
>>> d["new_key"].append("b")
>>> d["new_key"]
['a', 'b']
2.4.7 设置(set
)
从功能的角度来看,集合非常接近列表,从实现的角度来看,集合非常接近字典。与列表不同,元素是没有顺序的,并且不能重复。集合用大括号写,像字典一样,但是用元素代替“键:值”对:
>>> my_set = {1, 2, 1, 3}
>>> len(my_set)
3
请注意,副本(第二个 1)已被删除。
必须使用set()
函数创建空集,以避免与空字典(记为{}
)混淆:
>>> empty_set = set()
add()
方法允许你添加一个元素到集合中(它代替了列表的append()
方法)和remove()
方法来删除一个元素。
经典集合运算(并集、交集等。)可通过方法和操作符获得(交集用“&”,并集用“|”)。不可变集合(frozenset
)被用作字典中的键,而不是集合。它们对于集合就像元组对于列表一样。
2.4.8 文件(open
)
使用open()
功能打开文件:
>>> my_file = open("path/filename.ext", "r")
第二个参数是“模式”;它可以是下列值之一:
-
“r”表示读取文本文件(默认值)
-
“w”写文本文件
-
“rb”来读取二进制文件
-
“wb”写二进制文件
如果文件不存在,打开文件进行写入会自动创建该文件,否则会将其覆盖。Python 处理换行符的转换(Unix/Windows/Mac)和文本文件的编码(默认为 UTF 8)。
|文件操作
|
例子
|
| — | — |
| 以字符串形式读取文件的全部内容 | >>> content = my_file.read()
|
| 写入文件 | >>> my_file.write("content")
|
| 关闭文件(当文件对象被 Python 销毁时自动调用) | >>> my_file.close()
|
数据类型之间的转换
有时需要将一种数据类型转换成另一种数据类型。int()
、float()
、str()
、list()
、tuple()
、set()
和frozenset()
函数分别允许将值转换为整数、浮点数、字符串、列表、元组、集合或不可变集合。
转换为
|
句法
|
| — | — |
| 整数 | int(x)
|
| 浮动 | float(x)
|
| 布尔代数学体系的 | bool(x)
|
| 目录 | list(x)
|
| 元组 | tuple(x)
|
| 一组 | set(x)
|
| 不可变集合 | frozenset(x)
|
| 词典 | dict(x)``# x is of the form [(key1, value1), (key2, value2)...]
|
| 线 | str(x) # String for displaying to the user``repr(x) # String for displaying to the programmer
|
以下示例将整数 8 转换为字符串:
>>> str(8)
'8'
2.5 条件(if
)
这些条件只允许在某些情况下执行命令,这将在程序执行时确定。条件的一般 Python 语法如下:
if condition1:
instruction executed if condition1 is true
instruction executed if condition1 is true...
elif condition2:
instruction executed if condition1 is false
and condition2 is true...
else:
instruction executed if condition1
and condition2 are false...
continuation of the program
(executed whether the conditions are true or false)
elif
是else
和if
的缩写。“elif”和“else”部分是可选的,并且可能存在几个“elif”部分。**缩进(即行首的空格)很重要,因为它指示条件的结束位置。**空格的数量是程序员的选择,但必须保持不变,建议避免混合空格字符和制表符。
条件可以使用标准的比较运算符:
-
<
(小于) -
>
(大于) -
<=
(小于或等于) -
>=
(大于或等于) -
==
(等于,不要与用于定义变量的简单“=”混淆) -
!=
(不同于) -
is
(测试两个对象之间的同一性)
逻辑运算符“and”、“or”和“not”可用于将多个条件组合在一起,如下例所示:
>>> if (age > 18) and (age < 65):
... print("You are an adult.")
当只有一条指令要执行时,可以将所有内容放在一行中:
>>> if age >= 65: print("You are an elderly person.")
条件可以嵌套,使用多级缩进:
>>> if age == 0:
... print("You are a newborn.")
... if weight > 10.0:
... print("I think there is an error in the weight!")
2.6 循环(for
)
循环使得多次执行相同的命令成为可能。在 Python 中,循环遍历列表并为列表中的每个元素执行一系列指令(这是一种在其他编程语言中通常称为“for each”的循环)。当前元素放在您选择的变量中。for
循环的一般语法如下:
for variable in my_list:
if conditions1: continue # Move to the next item in the list
if conditions2: break # Stop the loop
repeated instructions...
else:
instructions executed only if the loop went all the way
(i.e. no break was encountered)
continuation of the program (executed once)
continue
指令中断当前迭代,并立即移动到下一个元素。break
指令中断循环并立即从循环中退出。最后,else
部分仅在循环结束时执行(即,未被break
中断)。当然,在给定的循环中,continue
、break
和else
的存在不是强制性的。
迭代列表可以是包含列表的变量,也可以是字符串(循环遍历字符串的每个字符)、集合(循环遍历集合的元素,以任意顺序)、字典(循环遍历字典的键)等等。也可以是用range()
函数生成的索引列表:
>>> range(4)
注意,Python 的range()
函数与一个 OWL 属性的“范围”无关,这个我们后面会看到!
这是一个循环的例子。它考虑动物名称列表,每行显示一种动物:
>>> animals = ["elephant", "zebra", "rhinoceros", "dolphin"]
>>> for animal in animals:
... print(animal)
elephant
zebra
rhinoceros
dolphin
如果你想同时显示列表中每种动物的数量,我们可以使用range()
:
>>> for i in range(len(animals)):
... print(i, animals[i])
0 elephant
1 zebra
2 rhinoceros
3 dolphin
循环也可以集成到列表的定义中:它们是理解列表。这里有一个例子:
>>> integers = [1, 2, 3]
>>> even_integers = [2 * i for i in integers]
>>> even_integers
[2, 4, 6]
该理解列表与通过以下循环创建的列表相同:
>>> even_integers2 = []
>>> for i in integers:
... even_integers2.append(2 * i)
>>> even_integers2
[2, 4, 6]
类似地,Python 提出了理解集和字典,例如:
>>> twofold = { i: 2 * i for i in integers }
>>> twofold
{1: 2, 2: 4, 3: 6}
>>> twofold[2]
4
当希望在多个列表上循环时,可能会出现两种情况:
-
这些列表没有配对。在这种情况下,我们将使用嵌套循环,如下例所示:
-
列表是成对的,两个两个(或三个三个,等等。也就是说,列表 1 的第一元素与列表 2 的第一元素相关联,列表 1 的第二元素与列表 2 中的第二元素相关联,等等。).
zip()
函数允许你在两个(或更多)配对列表上循环。在以下示例中,我们有一个配对的动物列表和一个环境列表,即动物#1 与环境#1 匹配,动物#2 与环境#2 匹配,依此类推:
>>> animals = ["elephant", "zebra", "rhinoceros",↲ "dolphin"]
>>> environments = ["savanna", "forest", "river"]
>>> for animal in animals:
... for environment in environments:
... print("a", animal, "in the", environment)
a elephant in the savanna
a elephant in the forest
a elephant in the river
a zebra in the savanna
a zebra in the forest
a zebra in the river
a rhinoceros in the savanna
a rhinoceros in the forest
a rhinoceros in the river
a dolphin in the savanna
a dolphin in the forest
a dolphin in the river
>>> animals = ["elephant", "zebra", "rhinoceros",↲ "dolphin"]
>>> environments = ["savanna", "forest", "savanna",↲ "river"]
>>> for animal, environment in zip(animals,↲ environments):
... print("a", animal, "live in the", environment)
a elephant live in the savanna
a zebra live in the forest
a rhinoceros live in the savanna
a dolphin live in the river
2.7 发电机
生成器使得浏览一系列元素成为可能(以列表的方式);然而,它不像列表那样在存储器中存储所有的元素:生成器一个接一个地产生元素,并且这些元素必须被立即处理(例如,使用循环)。因此,生成器可以提高性能,尤其是在处理大量数据时。这就是为什么许多 Owlready 方法返回生成器而不是列表。
也可以使用list()
功能将发生器转换成列表,例如,用于显示,如下所示:
>>> print(list(my_generator))
相反,要在发电机上循环,最好不要使用list()
来提高性能,如下所示:
>>> for x in my_generator: print(x)
2.8 功能(def
)
函数用于定义一组指令(或“子例程”),以便在主程序的不同位置执行几次。这组指令可以接收参数:这些参数将被传递给函数的调用,并将作为局部变量在函数内部可用。这些函数是用def
语句创建的,其一般语法是:
def function_name(parameter1, parameter2 = default_value,...):
function body
return return_value
函数可以接收多个参数,每个参数都有一个默认值。
return
语句指示函数的返回值,并中断它。
然后,可以用括号调用函数(括号是强制的,即使没有参数):
returned_value = function_name(parameter1_value, parameter2_value)
returned_value = my_function_with_no_parameter()
下面是一个简单的函数示例:
>>> def twofold(x):
... return x * 2
>>> twofold(3)
6
>>> twofold("bla")
'blabla'
请注意,函数参数不是类型化的。这就是为什么在前面的例子中,我们能够对整数和字符串都使用我们的twofold()
函数。
调用函数时,可以命名参数,这允许以任何顺序传递参数:
returned_value = function_name(parameter2 = parameter2_value,
parameter1 = parameter1_value)
函数也可以有可变数量的参数:
def my_function(*args, **kargs):
function body
return returned_value
args
( arguments )将接收一个带有非命名参数值的元组,kargs
(关键字 arguments)一个带有命名参数的字典。这里有一个例子:
>>> def function_with_variable_parameters(*args, **kargs):
... print(args, kargs)
>>> function_with_variable_parameters(1, 2, 3, extra_param = 4)
(1, 2, 3) { "extra_param": 4 }
调用函数时也可以使用以下语法:
>>> kargs = { "parameter1": 1, "parameter2": 2 }
>>> function(**kargs)
# equivalent
to function(parameter1 = 1, parameter2 = 2)
2.9 类(class
)
类别和实例
类是面向对象编程的基础。一个类代表一个创建对象的模板,例如,我们可能有一个创建动物或书籍的类。一个类也可以被看作是一个对象的一般类别,例如,一本书是一个一般的类别,许多不同的书以不同的标题、作者等等存在。
按照惯例,类名总是以大写字母开头(例如,“Book”)。该类定义了该类的每个对象的可用属性(例如,对于类 Book: title、author 和 price)以及可以应用于每个对象的方法(例如,对于类 Book: format a book citation)。
然后,该类将创建该类的对象,称为“实例”,例如,“指环王”和“琥珀中的九个王子”将是同一个“书”类的两个实例。因此,该类使得“分解”实例共有的部分成为可能:属性定义和方法,而属性值是特定于每个实例的。
在 Python 中,类是用class
语句创建的。方法是用def
语句在类内部创建的(对于函数);第一个参数表示应用该方法的对象(按照惯例它被称为self
;它相当于 Java 或 C++中的关键字this
,但在方法参数中显式出现)。属性不是类型化的,就像变量一样。它们是通过给它们一个值来定义的,语法为“self.attribute_name = value
”。
class
语句的一般语法是:
class my_class(parent_class1, parent_class2,...):
class_attribute_name = value
def __init__(self, parameters...): # constructor
self.object_attribute_name = value
def method1(self, parameters...):
method_body
return returned_value
def method2(self, parameters...):
method_body
return returned_value
当一个类为空时(它不包含任何方法),有必要添加一个pass
语句,以向 Python 指示它:
class my_empty_class(parent_class1, parent_class2,...):
pass
在方法体中,当想要获取或修改其属性(self.attribute
)或调用其方法(self.method(parameters...)
)时,必须始终指定“self
”活动对象。
__init__()
是一种叫做“构造函数”的特殊方法。如果存在,则在创建新实例时会自动调用构造函数。构造函数可以接收参数,这些参数的值将在创建实例时给出。
以下是“Book”类的定义示例:
>>> class Book(object):
... def __init__(self, title, author, price):
... self.title = title
... self.author = author
... self.price = price
... def format_citation(self):
... return '%s' by %s (price: %s€) % (self.title,↲
self.author, self.price)
在前面的定义中,我们从object
类定义了 Book 类,这是 Python 中最通用的类。
然后,为了创建一个类的实例,以函数的方式调用该类。任何参数都将被传递给__init__()
构造函数。
my_object = my_class(constructor_parameters...)
点符号用于访问对象的属性和方法:
print(my_object.attribute)
my_object.attribute = value
my_object.method(parameters...)
在实例上调用方法时,self
参数是 never 给定的。前面的调用相当于
my_object_class.method(my_object, parameters...)
例如,我们可以创建 Book 类的一个或多个实例,获取或修改它们的属性值,并调用它们的方法:
>>> ldr = Book("The Lord of the Rings", "JRR Tolkien", 24)
>>> npa = Book("Nine Princes in Amber", "R Zelazny", 12)
>>> npa.author
'R Zelazny'
>>> npa.price = 10
>>> npa.format_citation()
"'Nine Princes in Amber' by R Zelazny (price: 10€)"
继承
继承是面向对象编程中的一个基本机制。这种机制允许您创建与给定类共享相似蓝图的新类,也就是说,在一个类中定义子类。例如,漫画是图书的一个特殊子类:漫画类是从图书继承而来的一个子类。一般的类(这里是书)称为“超类”或“父类”,更具体的类(这里是漫画)称为“子类”或“子类”。
子类继承其父类的所有属性和方法:就像 Book 类的实例一样,Comic 类的实例有标题、作者和价格(属性),并且可以格式化引文(方法)。然而,子类可能有额外的属性和方法。例如,一本漫画书由其作者(或编剧)来描述,但也由其插图画家来描述:因此我们可以为漫画类添加一个“插图画家”属性。通过避免重复父类及其子类共有的属性和方法,继承使得“分解”源代码和简化源代码成为可能。
此外,可以在子类中重新定义父类的方法。例如,可以重新定义 Comic 类的构造函数来接受一个额外的“illustrator
”参数,并且可以重新定义format_citation()
方法来显示 illustrator 名称。重新定义方法时,可以通过使用关键字super()
调用父类来委托给父类方法,如下例所示:
class my_child_class(my_parent_class1, my_parent_class2,...):
def my_method(self, parameters...):
parent_returned_value = super().my_method(parameters...)
additional child class method body
return child_return_value
下面的示例定义了从 Book 类继承而来的 Comic 类:
>>> class Comic(Book):
... def __init__(self, title, author, illustrator, price):
... super().__init__(title, author, price)
... self.illustrator = illustrator
... def format_citation(self):
... return "'%s' written by %s and illustrated by %s↲
(price: %s€)" % (self.title, self.author, self.illustrator,↲
self.price)
构造函数方法__init__()
和format_citation()
方法已经在漫画子类中重新定义。新的构造函数定义支持illustrator
属性,并委托给父类方法来管理标题、作者和价格属性。
以下示例创建了一个 Comic 实例:
>>> re = Comic("Return to the Earth", "Yves Ferry",↲
"Manu Larcenet", 10)
>>> re.format_citation()
"'Return to the Earth' written by Yves Ferry and illustrated by↲
Manu Larcenet (price: 10€)"
注意,我们可以调用format_citation()
方法,而不知道我们调用它的对象是一本书还是一部漫画。Python 会根据对象的类别自动选择正确的方法。这种机制被称为多态性。
下面的例子遍历了我们创建的三个实例,并显示了它们的引用。x
变量有时包含一本书,有时包含一部漫画,在不知道对象x
的确切类的情况下调用format_citation()
方法。
>>> for x in [ldr, npa, re]:
... print(x.format_citation())
"'The Lord of the Rings' by JRR Tolkien (price: 24€)"
"'Nine Princes in Amber' by R Zelazny (price: 10€)"
"'Return to the Earth' written by Yves Ferry and illustrated↲
by Manu Larcenet (price: 10€)"
Python 也允许多重继承:当定义一个子类时,可以给出几个父类,用逗号分隔。
特殊方法名称
在 Python 中,开头和结尾带有两个下划线的方法名是特殊的方法。以下是主要的几个:
-
__init__(self, parameters...)
:构造器 -
__del__(self)
:毁灭者 -
__repr__(self)
:返回显示给程序员的字符串 -
__str__(self)
:返回显示给最终用户的字符串
2.9.4 面向对象编程的函数和运算符
以下三个属性和函数可用于分析对象和/或类之间的关系:
-
object.__class__
返回对象的类或类型,例如: -
isinstance(object, Class)
测试给定对象是否属于给定类(包括子类、孙类、等)。),例如:
>>> ldr.__class__
<class 'Book'>
>>> "blabla".__class__
<class 'str'>
issubclass(Class, parent_class)
测试给定的类是否继承自parent_class
,例如:
>>> isinstance(ldr, Book)
True
>>> isinstance(ldr, Comic)
False
>>> isinstance(re, Book)
True
>>> isinstance(re, Comic)
True
>>> issubclass(Comic, Book)
True
>>> issubclass(Book, Comic)
False
is
运算符允许测试两个对象是否相同:
>>> ldr is ldr
True
>>> ldr is npa
False
最后,当属性的名称在编写程序时未知,但在变量执行期间可用(作为字符串)时,使用以下函数来操作对象的属性:
-
hasattr(object, attribute_name)
测试对象是否有名为attribute_name
的属性。 -
getattr(object, attribute_name)
返回对象名为attribute_name
的属性的值。 -
setattr(object, attribute_name, value)
为对象定义名为attribute_name
的属性值。 -
delattr(object, attribute_name)
从对象中删除名为attribute_name
的属性。
>>> attribute_name = "author"
>>> hasattr(ldr, attribute_name)
True
>>> getattr(ldr, attribute_name)
'JRR Tolkien'
这些方法对于自省特别有用,也就是说,在不知道对象的类或属性的情况下,以通用的方式操作对象。
2.10 Python 模块
Python 模块定义了特定领域(如数学、生物信息学、3D 图形等)的附加函数和类。).Owlready2 是 Python 模块的一个例子。这些模块中包含的函数和类在 Python 中默认不可用;在访问和使用模块之前,必须导入相应的模块。
导入模块
在 Python 中有两种导入模块的方法:
-
导入模块及其名称。使用这种方法,有必要在模块名后面加上一个“.”在模块的每个函数和类的前面。下面是一个关于
math
模块的例子:>>> import math >>> math.cos(0.0) 1.0
-
导入模块的内容。使用这种方法,可以直接使用模块的函数和类,而不必在每次调用时都提到模块的名称。另一方面,如果几个模块定义了同名的函数或类,这可能会有问题:在这种情况下,最后一次导入会覆盖前一次导入。这里是另一个关于
math
模块的例子:
>>> from math import *
>>> cos(0.0)
1.0
Python 语言包括大量的“标准”模块,这些模块随 Python 本身一起安装。官方 Python 文档描述了每个模块;可从以下地址在线获得:
https://docs.python.org/3/py-modindex.html
其他模块可以从 PyPI (Python 包索引)安装,可从以下网址获得
安装附加模块
“pip3”工具允许从 PyPI 通过互联网自动下载、安装和更新 Python 3 模块。该工具可以在 shell 命令行(在 Unix/Mac 下)或 MS-DOS 命令提示符(在 Windows 下)中使用。以下命令行安装 Python 模块(如果已经安装,则更新它):
pip3 install -U name_of_the_module_to_install
最好以“root”(或 Linux/Mac 下的超级用户)或“administrator”(Windows 下)的身份安装这些模块,这样所有用户都可以使用它们。但是,这不是必须的:如果您没有全局安装所需的权限,您可以只为当前用户安装模块,使用参数“--user
”。以下命令行为当前用户安装模块:
pip3 install -U --user name_of_the_module_to_install
2.11 安装 Owlready2
Owlready 版本 2 可以通过“pip3”工具从互联网上安装;对应的模块名为“owlready2”(注意不要忘记版本号 2)。
此外,Owlready 还提供了一个在 Cython 中优化的版本,cython 是一种从代码 c 中的 Python 编译派生的语言。为了从这个优化的版本中受益,必须预先安装“cyt hon”模块。然而,如果 Cython 的安装出错,或者如果您没有 C 编译器(特别是在 Windows 上),您可以安装没有 Cython 的 Owlready,代价是加载本体时性能(稍微)降低。
最后,以下 Python 模块也将用于本书的其余部分:“Flask”、“MyGene”和“RDFlib”。
2.11.1 从终端安装 Owlready2
以下命令可用于在终端(Linux/Mac 下的 Bash 终端,Windows 下的 DOS 命令行界面)中安装 Owlready2 和其他模块:
pip3 install -U cython
pip3 install -U owlready2 Flask mygene rdflib
如果您没有 root 或管理员权限,请使用以下命令为活动用户安装模块:
pip3 install -U --user cython
pip3 install -U --user owlready2 Flask mygene rdflib
2.11.2 从 IDLE 或 Spyder(或任何 Python shell)安装 Owlready2
您可以使用以下 Python 命令从任何 Python 3.7.x 控制台安装 Owlready2,包括在集成开发环境中找到的命令,包括 IDLE 或 Spyder3:
>>> import pip.__main__
>>> pip.__main__._main(["install", "-U", "--user", "cython")
>>> pip.__main__._main(["install", "-U", "--user", "owlready2", "rdflib")
>>> pip.__main__._main(["install", "-U", "--user", "Flask", "mygene")
2.11.3 手动安装 Owlready2
如果出现问题,Owlready2 也可以通过五个步骤手动安装:
-
从 PyPI 下载压缩源代码:
https://pypi.org/project/Owlready2/#files
.
-
解压缩压缩的源代码,例如,在 Windows 下的“C:\”下。
-
源目录被命名为“Owlready2-0.xx”,其中“xx”是版本号(例如,“Owlready2-0.25”)。将此目录重命名为“owlready2”,例如“C:\owlready2”。
-
在 PYTHONPATH 中添加包含源目录的目录(在我们的例子中是“C:\ ”);这可以用 Python 来实现,如下所示(注意:不要忘记将任何反斜杠加倍!):
>>> import sys >>> sys.path.append("C:\\")
-
现在可以导入 Owlready2 了!
>>> import owlready2
2.12 摘要
在本章中,我们已经了解了如何用 Python 进行基本编程,包括语言语法、控制结构(如条件和循环)以及面向对象的编程。我们还回顾了主要的 Python 数据类型,比如字符串或列表。最后,我们看到了如何安装 Python 模块,特别是 Owlready 和本书剩余部分中的示例所需的其他模块。
三、OWL 本体论
“本体论”一词来源于哲学,对应的是“存在的科学”。这个术语随后在计算机科学中被用于指定一个领域中所有对象的正式定义以及这些对象之间存在的关系。因此它是一种“形式本体论”。因此,本体旨在构造和形式化一个领域中的对象,尽可能独立于预期的应用:本体因此可以被同一领域中的其他应用重用。
具体地说,形式本体可以用来实现两个目标:
-
执行自动推理:形式本体允许使用推理器进行逻辑推理。比如一个动物的本体可以推导出一个黑白条纹的动物其实是斑马。自动推理将在本书第七章的主题上更加具体。
-
链接不同来源的知识:正式的本体使用互联网地址(称为 IRI,国际化资源标识符)来标识不同的实体(或对象)。因此,所有本体共享相同的名称空间:任何本体都可以引用任何其他本体。此外,本体允许等价关系的定义:因此,如果相同的事物已经由两个不同的人在两个不同的本体中声明为两个不同的实体,第三个人可以在这些实体之间添加等价关系,以便它们成为一个。
这两个目标是互补的,因为联系知识可以使新的推理成为可能。
在这一章中,我们将解释什么是形式本体论,而不涉及理论方面。我们将强调本体和编程中使用的对象模型之间的相似性和差异,我们将构建一个简单的本体示例,然后我们将再次使用它来说明后面章节中的示例。
3.1 一个本体……它是什么样子的?
从理论的角度来看,一个本体包含了公理。描述逻辑用于形式化实体的定义,并以逻辑公理的形式表示它们。附录 A 简要描述了这些逻辑。然而,如果你不理解描述逻辑和相关的公式也没关系——我自己早在知道或理解这些公式之前就开始使用形式本体论了!这不会妨碍你在本书的其余部分编写你的第一个本体,或者有效地使用这些本体。
从实用的角度来看,一个本体使得定义一个模型成为可能,就像 Python(见 2.9)这样的编程语言的类和实例的方式,但是具有更高的表达水平,也就是说,更详细。本体和面向对象编程因此共享许多共同的元素,但是经常使用不同的术语来指代相同或非常相似的事物。下表给出了面向对象编程领域和正式本体领域的词汇表之间的对应关系:
|面向对象编程
|
形式本体
|
| — | — |
| 目标 | 实体 |
| 组件 | 本体论 |
| 班级 | 班级 |
| 类继承 | 类继承,也称为“是-a”关系 |
| —(无同等物) | 财产继承 |
| 情况 | 个人 |
| 属性或特性 | 属性、角色或谓词 |
| 实例的属性值 | 关系 |
| 类别名 | 伊利 |
| 数据类型 | 数据类型 |
| 方法 | —(无同等物) |
| —(无同等物) | 逻辑构造函数限制解体 |
面向本体的编程,我们将在下一章看到,将把这两个世界结合在一起。
因此,本体是一组实体,可以是类、属性或个体。与 Python(或任何其他面向对象的编程语言)的对象模型相比,我们有三个主要区别:
-
属性是在类之外独立定义的。
-
个体可以属于一个类,但也可以属于几个类(这是多重实例化,类似于多重继承,只是针对实例)。
-
本体论是基于开放世界的假设:也就是说,任何没有被明确禁止的东西都被认为是可能的。例如,如果我们定义“指环王”这本书的作者是“JRR Tolkien”,那么开放世界假设为这本书留下了其他额外作者存在的可能性。由于 JRR 托尔金是唯一的作者,我们还必须指出“指环王”除了“JRR 托尔金”之外没有其他作者(通常使用 OWL 限制)。
本体有几种语言;OWL (Web 本体语言)是目前应用最广泛的语言。OWL 本体可以保存在 RDF/XML 格式(最常见的格式)的文件中,也可以保存在 OWL/XML、N-Triples、Turtle 和其他格式的文件中。
3.2 使用 Protégé编辑器手动创建本体
可以用本体编辑器手工创建一个本体。迄今为止,使用最多的编辑器是 Protégé。可在以下地址免费获取: https://protege.stanford.edu
。稍后我们将使用它来构建我们关于细菌的示例本体。
3.3 示例:细菌的本体
为了说明本体的构建和它所能提供的可能性,我们将以细菌本体为例。该本体旨在描述细菌及其物理和化学特性。然而,为了简洁起见,我们将把自己限制在几个简单的特征和少数物种上。我提前向我的生物学家读者道歉,有时我们不得不进行粗糙的简化——完整和精确的细菌本体论的概念本身就构成了一项真正的研究工作!
我们将只保留以下三个特征来描述细菌:
-
它们的形状:细菌可以是圆形或杆状(细长形)。
-
它们的分组:细菌可以彼此隔离,也可以成对、成簇或成链分组,链可以是小链或长链。
-
它们的革兰氏状态:革兰氏阳性细菌通过革兰氏试验染色,不像革兰氏阴性细菌。
图 3-1 显示了根据这些特征对细菌进行的分类。圆形细菌称为“球菌”,杆状细菌称为“杆菌”。
此外,我们将只保留以下三个致病细菌家族:
-
葡萄球菌:圆形,聚集成簇,革兰氏阳性
-
链球菌:圆形,聚集成小链,但从未分离,革兰氏阳性
-
假单胞菌:杆状,成对分组或分离,革兰氏阳性
此后,我们将考虑一个细菌可以有几个群体:事实上,观察从来没有涉及到一个单一的细菌,但对几个。因此,观察同一种细菌的几个群是很常见的:例如,成簇的葡萄球菌可能偶尔单独或成对出现。然而,链球菌从来不是孤立的,而是成组的(成对的,成簇的,当然,最好是成链的)。
图 3-2
细菌本体的 UML 类图
图 3-1
根据三个标准对细菌进行简单分类
图 3-2 给出了 UML(统一建模语言)中的类图。然而,请注意,本体允许表示比类图上显示的更多的信息。例如,(实际上)所有聚集成簇的圆形革兰氏阳性细菌都是葡萄球菌。因此,对于这种细菌,有可能推断出细菌的种类、形状、类群和革兰氏状态。相反,假单胞菌不是唯一的杆状、孤立或成对的细菌。这是一个重要的区别,因为它会影响自动推理;然而,一个“经典”的对象模型(比如 Python 见 2.9)不允许将其考虑在内。
在本章的开始,我们将本体定义为“尽可能独立于预期的应用”。例如,细菌的本体可以有多种应用,例如:
-
创建一个描述不同细菌特性的百科网站(见 4.12)
-
促进细菌信息的输入或提取(见 5.14)
-
帮助识别未知细菌(见 7.7)
-
用关于细菌的信息丰富已经存在的本体或资源,如 UMLS(见 9.10)
-
通过允许相似细菌的分组来促进医院中的统计研究(以回答诸如“上个月厌氧菌感染的数量增加了吗?”)
这些应用中的每一个都可以通过特定的知识库来实现。例如,可以使用由如下规则组成的知识库来识别细菌:
-
如果 shape = round,grouping = in cluster,gram = ‘+’
-
然后是葡萄球菌
然而,一个本体能够实现来自同一知识源的所有这些应用,这极大地方便了这些知识的维护和重用。
在接下来的几节中,我们将使用 Protégé editor 从这种细菌分类中构建一个(小的)形式本体。
3.4 创建新的本体
当您启动 Protégé editor 时,它会自动创建一个新的空本体。编辑器包括几个选项卡;默认情况下,会显示活动的本体选项卡。
在这个选项卡中,我们将定义我们的本体的 IRI。IRI 是本体的“名称”,这个名称采用互联网地址的形式。但是,请注意,IRI 必须是互联网地址的形式,但是本体不需要在互联网上的这个地址可用!因此,通常创建其 IRI 以“http://www.semanticweb.org/
或“http://www.test.org/
开头的本体,而不拥有这些互联网域名的权利。
我们将称我们的细菌为本体论:
(注意:这个互联网地址指向我的个人网站,在那里你实际上可以下载完整的本体)。您可以在 Protégé的“本体 IRI”字段中输入这个 IRI,如下图所示:
然后,您可以将本体以 RDF/XML 格式保存在一个名为“bacteria.owl”的文件中。此后,不要忘记在编辑期间定期保存本体。
3.4.1 类别
在 Protégé中,“Classes”选项卡允许您浏览现有的类并创建新的类。按钮和允许您分别创建所选类的新的子类或姐妹类。使用这些按钮,我们可以创建一个与我们先前的 UML 模型相对应的类层次结构,如下面的屏幕截图所示:
在本体论中,遗传也被称为“是一种关系”:例如,我们可以说一个假单胞菌是一个细菌。
分离
本体和对象模型的一个重要区别如下:在本体中,一个个体可以属于几个类。因此,一个给定的形状很可能是既圆又棒的*!开放世界假设允许这种类型的解释:任何没有被正式禁止的事情都被认为是可能的。*
在我们的细菌本体论中,我们想要禁止这一点:给定的形状要么是圆形,要么是杆状,但不能同时是两者。为此,我们必须将 Round 和 Rod 这两个类声明为不相交的。两个不相交的类不能有共同的个体。
不相交的类在“类”选项卡的“描述”面板中声明。我们将选择杆类,然后单击“Disjoint with”部分右侧的“+”按钮,并在对话框的“Class hierarchy”选项卡中选择圆形类。您应该会得到以下结果:
这两个类现在是不相交的。注意,没有必要声明第二个类(圆)与第一个类(圆)不相交:这是从前面的声明中自动推导出来的。
同样,必须声明 InSmallChain 类与 InLongChain 类不相交。
Isolated、InPair、InCluster 和 InChain 类必须声明为 pairwise disjoint :也就是说,由这个列表中的两个类组成的任何对都是不相交的。为此,只需选择其中一个类(例如,隔离),单击“Disjoint with”右侧的“+”按钮,并同时选择其他三个类(通过按下控制键,而不是单击三次“+”按钮!).结果应该如下所示:
注意,关于分组的亚类,不相交并不意味着给定的细菌不能用两个不同的分组来观察(例如,分离的或成对的,如假单胞菌)。分离仅仅意味着一个给定的类群不能既是孤立的又是成对的,但它并不禁止一个细菌有两个不同的类群,一个类群是孤立的,另一个类群是成对的。
同样,类细菌、形状和分组必须声明为不相交的:例如,一个几何形状不能与一个细菌是同一个东西!这对人类来说似乎是显而易见的,但请记住,对机器来说并非如此。本体寻求全面地形式化知识,包括最明显的知识。
3.4.3 分区
我们已经定义了两类形状,圆形和杆形,它们现在是不相交的。然而,我们没有排除其他形状的存在,例如三角形。同样,开放世界假设使这种解释成为可能。然而,细菌只有两种可能的形状:圆形或杆状。我们必须声明所有的形状不是圆的就是棒的:它是一个分区(我们将说圆和棒的类构成了类形状的一个分区)。
为此,我们选择 Shape 类,并在“描述”面板中,单击“子类”右侧的“+”。这个“+”按钮允许您向类中添加超类;这些可以是命名类,也可以是 OWL 逻辑构造函数,就像这里。在出现的对话框中,我们选择“类表达式编辑器”选项卡,并输入构造函数“Round 或 Rod”。您应该获得以下结果:
这个构造函数“or”允许两个类用一个逻辑“or”链接起来(当我们用集合逻辑思考时,也称为 union )。这意味着 Shape 类是 Rod 和 Round 类的并集的子类。因此,现在任何形状不是圆形就是杆状,因此没有其他可能的形状。
同样,我们必须划分 in chain(“in small chain 或 InLongChain”的子类)和 Grouping(“Isolated or in pair or InCluster or in chain”的子类)。
数据属性
我们现在将处理属性。在本体中,与面向对象编程不同,属性是独立于类定义的。OWL 考虑了三类属性:值为数据的数据属性(数字、文本、日期、布尔值等。)、其值是实体(即本体个体)的对象属性,以及不干预语义或推理并因此可以无限制地混合数据和实体的注释属性。
在 Protégé中,“数据属性”选项卡允许您创建数据属性。OWL 除了支持类之间的继承,还支持属性之间的继承;但是,我们不会在这里使用它。使用和按钮,其工作方式类似于类的按钮,我们将创建两个名为“gram_positive”和“nb_colonies”的新数据属性。最后一个属性对于描述细菌来说并不是很有用,但是它可以作为数值数据属性的一个例子。
您应该会得到以下结果:
可以通过指定以下内容来配置每个数据属性:
-
itsdomain(protégé中的“Domains (intersection)”):这是为其定义属性的类。
-
它的范围(“范围”):这是相关的数据类型。它可以是整数或实数、布尔值、字符串、日期等等。请注意:为了以后使用 Python 和 Owlready,最好使用 integer 类型表示整数,decimal 类型表示实数(更多信息请参考表 4-1 )。注意,OWL 属性的范围与 Python
range()
函数无关,Python 函数允许你创建数字列表(见 2.6)。 -
它的功能性状态(“功能性”复选框):当属性是功能性的时,给定的个人对于该属性只能有(最多)一个值。相反,如果属性不是功能性的,一个给定的个体可以有几个值。
域和范围是可选的。可以定义几个域和范围;然而,考虑的是不同域/范围的交集,而不是它们的联合,这通常不是期望的结果。例如,考虑属性“has_shape”和两个类(细菌和病毒),其中个体可以具有形状。如果我们定义两个域,细菌和病毒,只有属于细菌类和病毒类的个体才能有形状!如果有人想说所有的病毒和所有的细菌都可能有一个形状,有必要将这个域定义为类的联合*,也就是说,“细菌或病毒”。*
这里,我们将如下配置我们的两个数据属性:
-
革兰氏阳性:功能性(勾选方框),域:细菌,范围:布尔
-
nb _ 菌落:功能性(打勾),域:细菌,范围:整数
对象属性
在 Protégé中,“对象属性”选项卡允许您创建对象属性。使用和按钮,我们创建了四个名为“has_shape”、“has_grouping”、“is_shape_of”和“is_grouping_of”的新对象属性,如下图所示:
可以通过指定以下内容来配置每个对象属性:
-
itsdomain(protégé中的“Domains (intersection)”):这是为其定义属性的类。
-
其范围(“范围(交集)”):这是关联对象的类。
如前所述,如果指示了几个域或范围,则考虑它们的交集。
-
其逆属性(“的逆”):逆属性对应于当该属性被反向读取时存在的关系;如果属性存在于 A 和 B 之间,则它的逆属性存在于 B 和 A 之间。例如,属性“is_shape_of”是“has_shape”的逆属性:如果细菌 X 具有形状 A,则 A 是 X 的形状。这些逆属性在 Python 中使用关系 has_shape/is_shape_of 双向导航时非常有用。
-
它的功能性状态(“功能性”复选框):当属性是功能性的时,给定的个人对于该属性只能有(最多)一个值。相反,如果属性不是功能性的,一个给定的个体可以有几个值。
-
其逆功能状态(“逆功能”复选框):如果逆属性是功能性的,则该属性是逆功能性的。例如,属性 is_father_of 是反函数:一个男人 A 可以是几个孩子 B、C、D 等等的父亲,但是对于这些孩子中的每一个,A 都是他们唯一的父亲。
-
它的可传递的状态(“可传递的”复选框):如果一个属性可以“链接”到几个对象上,那么这个属性就是可传递的。例如,性质“is _ larger _ than”是传递性的:如果个体 A 大于 B,如果 B 本身大于 C,那么我们可以推导出 A 大于 C。
-
它的对称状态(“对称”复选框):一个属性是对称的,如果它可以在两个方向上被无差别地读取(因此它是它自己的逆)。例如,属性“is_married_to”是对称的:如果人 A 与人 B 结婚,则 B 与 A 结婚。
-
它的非对称状态(“非对称”复选框):如果一个属性从来不是对称的,那么它就是非对称的。例如,属性“has_father”是非对称的:如果 A 对父亲 B 有,那么 B 不可能对父亲 A 有。
-
它的自反状态(“自反”复选框):如果一个属性总是应用于任何对象和它自身之间,那么它就是自反的。例如,属性“知道”是反身的:每个人 X 知道他自己。
-
它的非自反状态(“非自反”复选框):如果一个属性从不自反,那么它就是非自反的。例如,属性“is_married_to”是非反射性的:一个人不能与他/她自己结婚。
这里,我们将按如下方式配置对象属性:
-
has_shape:功能性(勾选方框),域:细菌,范围:形状
-
has_grouping:无功能(不要勾选该框),域:细菌,范围:分组
-
is_shape_of:无功能,域:形式,范围:细菌,逆:has_shape
-
is_grouping of:无功能,域:分组,范围:细菌,反向:has_grouping
注意,只定义对的两个属性中的一个属性的逆属性就足够了:比如这里,我们不需要指定 has _ shape has for inverse is _ shape _ of。这很容易从 is_shape_of 的逆性质推导出来。
限制
现在我们已经创建了属性,我们可以返回到类并基于这些属性添加限制。
通过单击“描述”部分中“子类”右侧的“+”按钮,可以在 Protégé的“类别”选项卡中添加限制。“子类化”允许你添加超类到类中;它可以是之前创建的 OWL 命名的类,也可以是构造函数,比如分区(见 3.4.3)和限制。
例如,假单胞菌具有革兰氏阴性染色。这导致 OWL 受到以下限制:布尔属性“gram_positive”必须具有 false 值。这种限制相当于一个类别:它是“革兰氏阳性”属性值为假的细菌类别。因此,我们可以将假单胞菌类定义为这个限制类的一个亚类。
OWL 提供了几类限制。以下限制用于模拟两个类之间的关系:
- 存在限制( some ):它代表了与属于某一类的个人在某一属性上至少有一种关系的那一类个人。
这个限制写在 Protégé的“property some class”中。例如,我们已经看到(图 3-1 )假单胞菌都是杆状的。rod 是一个类,这意味着 Rod 形状可能有几个子类型(例如,我们可以区分规则和不规则的 Rod 形状)。因此,这个限制将被写成“具有 _ 形状某个杆”。
- 基数限制(确切地说是、最小、最大):它表示某个属性与某个属于某个类的个体有一定数量关系的个体的类。该数字可以是精确值(精确值)或最小值(最小值)或最大值(最大值)。
这些限制写在被保护人的“财产确切地说是数量级”、“财产最小数量级”或“财产最大数量级”。它是存在性限制的一个更具体的版本:存在性限制等价于基数“min 1”的限制。
- 普遍限制( only ):代表与属于某一类(包括其子类)的一个(或多个)个体只有某一属性关系的个体的类。
这个限制在 Protégé中被写成“property only class”。例如,观察到假单胞菌仅具有杆状,我们将写为“仅具有杆状”。
注意不要将通用限制“仅具有杆的形状”与之前的存在限制“具有杆的形状”相混淆。存在限制说明所有假单胞菌至少有一个杆状,而普遍限制说明所有假单胞菌除杆状外没有其他形状。将两个相似的限制(一个是普遍性的,另一个是存在性的)与同一个目标类结合起来是很常见的。
另一方面,我们不会对分组使用一个通用的限制,因为我们以前已经看到,细菌偶尔可以呈现不同于其典型分组的其他分组。
以下限制使得对类和个体或数据类型值之间的关系建模成为可能:
- 价值限制( value ,有时被称为角色填充):它代表对某个属性具有某个价值的个人的类别。
这个限制被写成 Protégé中的“属性值个体/数据类型”。例如,假单胞菌总是与革兰氏阴性染色有关。这个限制将被写成“gram_positive value false”。
要在 Protégé中添加限制,单击“+”按钮后,您可以:
-
在“类表达式编辑器”选项卡中手动输入限制(提示:制表键允许您完成部分输入,例如,“Bact”代表“细菌”),
-
或者使用“对象限制创建者”或“数据限制创建者”选项卡(取决于属性的类型)并从下拉列表中选择值。
为了进一步描述假单胞菌类,我们将增加以下限制:
-
“有 _ 形一些杆”
-
“只有形状的杆”
-
1 的【克 _ 正值假】
请注意,我们对形状使用了一个存在性和一个普遍性限制,因为 Rod 是一个类,而不是一个个体或数据,相反,对 Gram 着色使用了值限制,因为 false 是一个数据类型值。
并集、交集和补集
OWL 还允许使用逻辑操作符作为构造函数。这些运算符有不同的名称,这取决于它们是从逻辑观点还是从集合论观点来考虑;但是,确实是一回事。有三种运算符可用:
- 逻辑 AND or 交集:这些是同时属于几个类的个体。
该交叉点在 Protégé中写为“class1 和 class2”。当然,更多的类别可以包含在交集中,例如,“类别 1 和类别 2 和类别 3”。
- 逻辑 OR 或 union:这些个体属于几个类中的一个类。
在 Protégé中,union 被写成“class1 或 class2”。同样,工会也不限于两类,例如,“一类或二类或三类”。例如,假单胞菌可分为两类:分离的和成对的。因此,我们可以建立这两个类的并集,这将写成“孤立的或成对的”。
此外,我们之前已经使用了并集来表示分区(参见 3.4.3)。
- 逻辑非或补语:这些人不属于某一特定的阶层。补语在 Protégé中写的是“而不是类”。
OWL 还允许您通过将括号中的不同元素分组,将逻辑运算符与限制和类结合起来。
为了细化假单胞菌类,我们将添加以下超类:
- " has_grouping 一些(孤立的或成对的)"
这一限制规定所有假单胞菌至少有一个分离的或成对的类群。
3.4.8 定义(相当于关系)
在前两节中,我们使用了限制和构造函数来描述类的属性。然而,这不是一个正式意义上的定义,因为我们还没有完整和唯一地描述这个类。比如所有的假单胞菌都是杆状的,但不是所有杆状的细菌都是假单胞菌!
OWL 允许你给一个类一个形式上的等价定义,通过一个等价关系。然后,定义的类允许在自动推理过程中对个体进行重新分类(我们将在 3.5 节和第七章中看到)。
例如,球菌类是具有圆形形状的细菌类(即,至少一个圆形形状且只有一个圆形形状)。因此,我们可以将其定义如下:
- 球菌:“细菌和(形状有些圆)
和(仅具有 _shape 圆形)"
注意,与我们之前作为假单胞菌的超类使用的限制和构造函数不同,等价类必须“在一块”定义。除非我们完全改变它的意思,否则我们不能把这个定义分成三个部分“细菌”、“有一些圆形”和“只有圆形”!
要在 Protégé中添加限制,请单击“等效于”右侧的“+”按钮,然后在“类表达式编辑器”选项卡中手动输入限制(同样,您可以使用制表键完成)。
Protégé用不同的图标标记已定义的类:一个棕色的圆圈,圆圈中出现符号“≡在描述逻辑中表示“等价于”。
同样,我们将杆菌、葡萄球菌和链球菌分类定义如下:
- 芽孢杆菌:“细菌和(有 _ 形状的一些杆)
和(has_shape only 杆)”
- 葡萄球菌:“细菌和(形状有些圆)
和(仅具有圆形形状)
和(有 _ 分组一些包含者)
and (gram_positive value true)"
- 链球菌:“细菌和(形状有些圆)
和(仅具有圆形形状)
和(has_grouping some InSmallChain)
and(仅 has_grouping(非隔离))
and (gram_positive value true)"
对于链球菌,限制条件“仅具有分组(非分离的)”表示链球菌只能具有非分离的分组:从未观察到分离的。
个人
Protégé的“个人”选项卡允许您浏览个人并创建新的个人。为了测试我们的本体,我们将创建几个个体。为此,在“类层次结构”面板中选择类,然后单击“成员列表”面板中的按钮(该面板列出了属于该类的个人)。我们将首先选择 Round 类并创建一个名为“round1”的形状,如下图所示:
同样,我们创建一个名为“in_cluster1”的个体,它属于“InCluster”类。
然后,我们创建一个名为“未知 _ 细菌”的个体,属于“细菌”类。最后,在“属性断言”面板中,我们通过单击“对象属性断言”和“数据属性断言”右侧的“+”按钮来输入此人的关系。我们将输入以下关系:
-
对象属性:
-
has_shape:圆形 1
-
散列分组:in_cluster1
-
-
数据属性:
-
革兰氏阳性:是
-
nb _ 菌落数:6
-
下面的屏幕截图显示了预期的结果:
其他结构
OWL 和 Protégé还提供了其他不常用的构造函数。
-
一组个体(也称为中的*)允许创建一个仅限于一组个体的类。它写在大括号之间:“{个人 1,个人 2,…}".它还可以用来将一个个体转换成一个类(也称为 singleton 类,因为它只有一个实例/个体),如下:“{个体}”。*
-
一个属性的逆写成“逆(属性)”。比如“逆(has_shape)”就相当于我们细菌本体中的“is_shape_of”。当本体没有定义命名逆属性时,这个构造函数特别有用。
-
属性链写为“property1 o property2”(圆圈对应小写字母“o”)。它们也被称为属性组合。它们使得“链”几个属性成为可能,例如,“是形状或分组”直接从一种形状传递到具有这种形状的细菌分组。
3.5 自动推理
现在我们的细菌本体已经准备好了!
为了验证本体中不存在不一致并测试自动推理,我们可以使用“推理机➤启动推理机”菜单来执行自动推理机。几个推理机是可用的;我推荐用隐士。
一旦推理完成,个人就被重新归类为受保护者。例如,我们创建的个体“未知细菌”属于细菌类。我们可以看到它被重新分类为一个新类别:葡萄球菌(新类别出现在 Protégé的黄色背景上)。事实上,这种细菌满足葡萄球菌的条件(圆形,成簇,革兰氏阳性)。
此外,推理机还重组了类。为了观察这一点,我们将返回到 Classes 选项卡并单击“类层次结构(推断)”。类别树已被更改。例如,我们可以看到假单胞菌类被重新归类为芽孢杆菌类的一个子类。事实上,该类的所有个体都满足芽孢杆菌类的定义,因为假单胞菌属都具有杆状。
你也可以尝试以下两种体验:
-
创建一个细菌类个体,呈杆状,成对分组和/或隔离,革兰氏阴性状态。该个体将被重新分类为杆菌类,而不是假单胞菌类。事实上,我们还没有给出假单胞菌类的正式定义;推理者因此不能推断这种细菌是假单胞菌。在设计本体时,缺乏定义是一个理想的选择,因为假单胞菌不是唯一具有杆状、分离或成对的革兰氏阴性细菌(见图 3-1 )。
-
创建一个细菌类的个体,具有圆形形状,以小链分组,并且具有革兰氏阳性状态。该个体将被重新分类为球菌类,而不是链球菌类。但是,这个类确实包含了一个定义!然而,我们刚刚创建的个体并不完全符合链球菌类的定义。
事实上,在定义中,我们指明了“仅 has_grouping(非孤立)”。在个体中,我们指出了一个小的连锁群;但是,属性“has_grouping”不起作用,因此可能有几个值。开放世界的假设意味着推理者不能排除另一个群体的存在,这个群体在本体论中没有提到,但可能是孤立的。
因此,为了能够推断出我们的个体是链球菌,有必要在本体中指出该个体除了那些明确提到的分组之外没有其他分组,或者他没有分离的类的分组。
另一方面,在正式定义中,我们还对“has_shape”属性使用了通用约束(“only”)。然而,这并不妨碍将个体分为球菌、杆菌和葡萄球菌类。为什么呢?因为该属性是功能性的,并且 Round 和 Rod 类是不相交的。因此,当细菌呈杆状时,它不可能呈圆形,反之亦然。相反,属性“has_grouping”不起作用,因此这种推理不再可能。
我们将回到这个问题,解决方案将在 7.3 中提供。
3.6 建模练习
这里有一些练习来训练你进行本体建模:
-
在细菌本体中,添加一个棒状的葡萄球菌类个体。运行推理器;你观察到了什么?
-
使用 Protégé editor,通过添加过氧化氢酶测试来扩展细菌的本体。这种生物测试有助于识别细菌,其结果可以是阳性或阴性。过氧化氢酶试验对葡萄球菌和假单胞菌呈阳性,对链球菌呈阴性。
-
使用 Protégé editor,通过添加细菌的颜色来扩展细菌本体。葡萄球菌是白色或金黄色的(这就是著名的金黄色葡萄球菌),链球菌是半透明的,假单胞菌一般是有色的(也就是说不是白色的)。
-
使用 Protégé editor,添加一类新的细菌:麻风分枝杆菌(汉森氏杆菌,导致麻风病)。这种细菌呈革兰氏阳性,杆状,成对分离或聚集。过氧化氢酶试验与这种细菌无关,因为它很难在体外生长。颜色是黄色的。最后,所有这些特征足以识别细菌。
-
在 Protégé编辑器中,添加一个细菌类个体,杆状,孤立,颜色为黄色。检查该个体是否被正确归类为麻风分枝杆菌。
-
在细菌本体论中,在细菌的不同亚类(葡萄球菌、链球菌、假单胞菌等)之间增加一个分界点。).这是否改变了对未知细菌的推理结果?
-
进行 OWL 本体论以构建药物相互作用。该本体旨在使用推理机自动检测医生处方中的交互。开放世界假设会在推理过程中造成问题吗?
-
使用 Protégé editor,构建一个描述书籍、作者和编辑的本体。你从 2.9 中的对象模型中获得灵感。
3.7 摘要
在这一章中,我们通过一个简单的细菌本体的例子介绍了 OWL 本体和 Protégé editor 的使用。我们已经看到了主要的 OWL 构造,也看到了一些经常遇到的困难,比如那些与开放世界假设相关的困难。
注意,在 OWL 中,false 和 true 是不用大写字母写的,而在 Python 中是用(即 False 和 True;参见 2.4.2)。
四、使用 Python 访问本体
在这一章中,我们将看到如何使用 Owlready 访问 Python 中本体的内容。我们将使用我们在第三章中创建的细菌本体,以及基因本体,一种在生物信息学中广泛使用的本体。
4.1 导入 Owlready
Owlready(版本 2)是以如下方式导入 Python 的:
>>> from owlready2 import *
注意,用“from owlready2 import *”导入模块内容比用“import owlready2”导入模块更好(见 2.10.1),因为 owl 已经重新定义了一些 Python 函数,比如issubclass()
函数。
4.2 加载本体
Owlready 允许您将 OWL 本体加载到 Python 中,并像从 Python 模块访问“传统”对象一样访问 OWL 实体。
本体可以通过三种不同的方式加载:
- 从其 IRI ( 国际化资源标识符),即互联网地址:
>>> onto = get_ontology("http://lesfleursdunormal.↲fr/static/
_downloads/bacteria.owl").load()
然后从互联网上下载并加载本体。
- 从包含本体副本的本地文件,例如,在 Linux/Unix/Mac 下:
>>> onto = get_ontology("/home/jiba/owlready/↲bacteria.owl").load()
或者在 Windows 下:
>>> onto = get_ontology("C:\\owlready\\bacteria.↲owl").load()
也可以将本体从本地副本加载到当前目录:
>>> onto = get_ontology("bacteria.owl").load()
然后从一个已经存在的 OWL 文件中加载本体(如果用引号括起来的文件不存在,显然会出现错误;当然,前面几行代码中的文件名只是示例)。小心,在 Windows 下,不要忘记在文件名中加双反斜杠!
- 从用
open()
、urlopen()
和其他函数获得的 Python 文件对象(见 2.4.8)。这种情况非常少见,但有时很有用(我们将在 8.8.1 中使用它来加载 DBpedia)。这里有一个例子:
>>> my_file = open("/path/to/file.owl")
>>> onto = get_ontology("http://lesfleursdunormal.↲fr/static/
_downloads/bacteria.owl")
>>> onto.load(fileobj = my_file)
Owlready 目前支持以下文件格式进行读取:
-
RDF/XML(OWL 本体最常用的文件格式)
-
OWL/XML
-
n-三元组
Owlready 维护一个已加载本体的缓存:如果第二次加载相同的本体,将返回相同的本体对象,而不必重新读取相应的文件。为了强制重载一个本体,我们将使用load()
方法的可选的reload
参数:
>>> onto.load(reload = True)
本体的base_iri
属性允许获得它的 IRI:
>>> onto.base_iri
'http://lesfleursdunormal.fr/static/_downloads/bacteria.owl#'
请注意,Owlready 已经自动确定了分隔符“#”或“/”,放在本体的 IRI 之后,并将其添加到末尾(这里是“#”)。然而,当调用get_ontology()
时,也可以在 IRI 的末尾显式包含分隔符。
4.3 导入的本体
本体的imported_ontologies
属性包含它导入的其他本体的列表:
>>> onto.imported_ontologies
[]
这里,我们的细菌本体没有导入任何其他本体(因此出现了前面的空列表)。Owlready 递归地自动加载导入的本体。
4.4 清单本体的内容
本体对象有许多方法来根据它们的类型遍历包含在本体中的实体。下表总结了所有这些方法:
|方法
|
遍历的实体
|
| — | — |
| individuals()
| 所有个人 |
| classes()
| 所有类别 |
| properties()
| 所有属性 |
| object_properties()
| 所有对象属性 |
| data_properties()
| 所有数据属性 |
| annotation_properties()
| 所有注释属性 |
| disjoints()
| 所有成对分离(包括成对不同个体和分离/不同对) |
| disjoint_classes()
| 所有成对不相交的类(包括不相交的类对) |
| disjoint_properties()
| 所有成对分离的属性(包括分离的属性对) |
| different_individuals()
| 所有成对的不同个体(包括不同的个体对) |
| rules()
| 所有 SWRL 规则 |
| variables()
| 所有 SWRL 变量 |
| general_axioms()
| 所有一般公理 |
这些方法返回生成器(参见 2.7);要显示内容,使用list()
Python 函数,该函数将生成器转换成一个列表:
>>> onto.classes()
<generator object _GraphManager.classes at 0x7f5a000fae58>
>>> list(onto.classes())
[bacteria.Bacterium, bacteria.Shape, bacteria.Grouping, bacteria.Round, bacteria.Rod, bacteria.Isolated, bacteria.InPair, bacteria.InCluster, bacteria.InChain, bacteria.InSmallChain, bacteria.InLongChain, bacteria.Pseudomonas, bacteria.Coccus, bacteria.Bacillus, bacteria.Staphylococcus, bacteria.Streptococcus]
然而,当发电机出现在回路中时,最好不要使用list()
,以提高性能:
>>> for c in onto.classes(): print(c.name)
Bacterium
Shape
Grouping
[...]
4.5 访问实体
在加载本体时,Owlready 已经分析了本体文件,并自动将其翻译成“主语-动词-宾语”三元组形式的 RDF 图(我们将在第十一章中更详细地返回 RDF)。这个 RDF 图以 SQLite3 格式存储在数据库中,默认情况下存储在 RAM 中(但是数据库也可以存储在磁盘上,我们将在 4.7 中看到)。然后,按需动态创建用于访问包含在本体中的实体的 Python 对象。因此,如果一个本体包括 100 个类,但是 Python 中只使用了 1 个,那么 Python 中将只创建这个类,其他 99 个将保留在数据库中的 RDF 图级别。
IRIS
伪字典允许从它的 IRI 访问任何实体。例如,要访问个人“未知 _ 细菌”,其完整 IRI 如下:
http://lesfleursdunormal.fr/static/_downloads/bacteria.owl#unknown_bacterium
我们将使用
>>> IRIS["http://lesfleursdunormal.fr/static/_downloads/↲
bacteria.owl#unknown_bacterium"]
然而,这种表示法相当冗长。Owlready 允许更容易地访问存在于本体中的实体,用点符号“.”,好像本体是一个包含类和对象的 Python 模块。例如,我们也可以访问单个“未知 _ 细菌”,如下所示:
>>> onto.unknown_bacterium
当使用点符号时,Owlready 获取本体的基本 IRI(onto.base_iri
,这里是“”http://lesfleursdunormal.fr/static/_downloads/bacteria.owl#
“”),并附加出现在点之后的内容(这里是“未知 _ 细菌”),以获得所请求实体的 IRI。
一些 IRI 可能在属性名称中包含 Python 不支持的字符(例如空格);在这种情况下,也可以使用以下替代语法:
>>> onto["unknown_bacterium"]
最后,一些本体定义的实体的 IRI 不是从本体的 IRI 开始的;在这种情况下,可以通过IRIS
伪字典或名称空间来访问它(见 4.8)。
实体的iri
属性包含其完整的 IRI,而name
属性包含 IRI 的最后一部分(在“#”字符或最后一个“/”之后):
>>> onto.unknown_bacterium.iri
'http://lesfleursdunormal.fr/static/_downloads/↲
bacteria.owl#unknown_bacterium'
>>> onto.unknown_bacterium.name
'unknown_bacterium'
4.5.1 个人
个体可以像“正常的”Python 对象一样被操纵。特别是,可以用isinstance()
函数测试它们在给定类中的成员资格,就像对任何 Python 对象一样:
>>> isinstance(onto.unknown_bacterium, onto.Bacterium)
True
__class__
属性允许获取对象的类别:
>>> onto.unknown_bacterium.__class__
bacteria.Bacterium
然而,在本体中,一个对象可以同时属于几个类,这在 Python 中是不允许的。在这种情况下,Owlready 自动创建一个“merge”类,该类继承自所有个体的类。为了获得个体的类的列表,因此最好使用is_a
属性(它也包含限制和逻辑构造器,如果有的话):
>>> onto.unknown_bacterium.is_a
[bacteria.Bacterium]
最后,equivalent_to
属性包含等价个体的列表(在 OWL 或 Protégé editor 中通常称为“same as”)。
>>> onto.unknown_bacterium.equivalent_to
[]
这里,列表是空的,因为我们未知的细菌还没有被定义为等同于任何其他细菌。
4.5.2 关系
个体的关系可以通过指向“个体.属性”的点符号来获得,例如:
>>> onto.unknown_bacterium.gram_positive
True
>>> onto.unknown_bacterium.has_shape
bacteria.round1
>>> onto.unknown_bacterium.has_grouping
[bacteria.in_cluster1]
关系返回一个值列表(对于前面的属性“has_grouping ”),或者如果属性是功能性的,返回一个值(对于前面的另外两个属性)。
值列表的first()
方法可用于检索第一个结果(当列表为空时默认为None
)。
>>> onto.unknown_bacterium.has_grouping.first()
bacteria.in_cluster1
此外,Owlready 在查询关系时会自动考虑反向属性。例如,我们可以问哪种细菌与“in_cluster1”组相关联:
>>> onto.in_cluster1.is_grouping_of
[bacteria.unknown_bacterium]
关系“in _ cluster 1 is _ grouping _ of unknown _ bacterium”没有出现在本体中(我们在第三章 Protégé中没有输入)。但是,从我们输入的关系“未知细菌在 _cluster1 中具有 _for_grouping”很容易推导出来。
存在于本体中的值被自动翻译成 Python 数据类型(int
、float
、str
等)。),根据表 4-1 中给出的对应关系。
当属性名以“INDIRECT_
”为前缀时,Owlready 也返回间接定义的关系,考虑到:
-
传递性、对称性和自反性
-
属性(即子属性)之间的继承关系
-
个人所属的阶级(对阶级的存在或价值限制)
-
类之间的继承(超类的存在或值限制)
-
等价关系(类和等价属性以及相同的个体)
这里有一个例子:
表 4-1
OWL 和 Python + Owlready 数据类型之间的对应关系。当多个 OWL 数据类型对应于同一个 Python 类型时,粗体的 OWL 数据类型是 Owlready 在保存本体时默认使用的数据类型
|猫头鹰
|
python+owlrayed
|
| — | — |
| XMLSchema#integer | (同 Internationalorganizations)国际组织 |
| XMLSchema #字节 | |
| XMLSchema#short | |
| XMLSchema#int | |
| XMLSchema#long | |
| XMLSchema#unsignedByte | |
| XML schema # unsigned hort | |
| XML schema # unsigned nt | |
| XML schema # anonymous | |
| XMLSchema#negativeInteger | |
| XMLSchema#nonNegativeInteger | |
| XMLSchema#positiveInteger | |
| XMLSchema #布尔值 | 弯曲件 |
| XMLSchema#decimal | 漂浮物 |
| XMLSchema#double | |
| XMLSchema#float | |
| 猫头鹰#真实 | |
| XMLSchema #字符串 | 潜艇用热中子反应堆(submarine thermal reactor 的缩写) |
| XML schema # normal string | owlready2.normstr |
| XMLSchema#anyURI | |
| XMLSchema#Name | |
| 简单的文字 | str(如果没有指定语言)owlready2.locstr(如果指定了语言,请参见 8.2) |
| XMLSchema#dateTime | datetime.datetime |
| XMLSchema #日期 | 日期时间.日期 |
| XMLSchema #时间 | 日期时间.时间 |
>>> onto.unknown_bacterium.INDIRECT_has_grouping
[bacteria.in_cluster1]
get_properties()
方法返回一个生成器,列出个人至少有一个关系的所有属性,例如:
>>> list(onto.unknown_bacterium.get_properties())
[bacteria.has_shape, bacteria.has_grouping,
bacteria.gram_positive, bacteria.nb_colonies]
最后,get_inverse_properties()
方法对反向属性做同样的处理,并返回“(subject,property)”形式的对,例如:
>>> list(onto.round1.get_inverse_properties())
[(bacteria.unknown_bacterium, bacteria.has_shape)]
4.5.3 类别
可以通过与其他实体相同的方式获得类:
>>> onto.Bacterium
本体类是真正的 Python 类,可以这样使用。例如,issubclass()
函数测试一个类是否是后代(子类、子类、*等等)。*另一种说法:
>>> issubclass(onto.Coccus, onto.Bacterium)
True
属性用来获得父类的列表。然而,对于个人来说,最好使用is_a
属性(它也包含限制和逻辑构造函数):
>>> onto.Coccus.is_a
[bacteria.Bacterium]
subclasses()
方法获取子类的列表(注意subclasses()
返回一个生成器,因此使用了list()
):
>>> list(onto.Bacterium.subclasses())
[bacteria.Pseudomonas, bacteria.Coccus, bacteria.Bacillus]
ancestors()
和descendants()
方法用于获得祖先类的集合(父母、祖父母、等)。)和后代类(子女、孙辈、等)。),分别为。
>>> onto.Bacterium.descendants()
{bacteria.Bacterium, bacteria.Pseudomonas, bacteria.Streptococcus,
bacteria.Staphylococcus, bacteria.Bacillus, bacteria.Coccus}
默认情况下,起始类包含在结果中(这就是为什么我们在前一个结果中找到bacteria.Bacterium
)。可选参数include_self
删除起始类。它的用法如下:
>>> onto.Bacterium.descendants(include_self = False)
{bacteria.Pseudomonas, bacteria.Streptococcus,
bacteria.Staphylococcus, bacteria.Bacillus, bacteria.Coccus}
instances()
方法用于获得属于一个类的个体列表(包括子类和子类的实例):
>>> onto.Bacterium.instances()
[bacteria.unknown_bacterium]
direct_instances()
方法以同样的方式工作,但是仅限于直接实例。
equivalent_to
属性包含等价类的列表:
>>> onto.Streptococcus.equivalent_to
[bacteria.Bacterium
& bacteria.has_shape.some(bacteria.Round)
& bacteria.has_shape.only(bacteria.Round)
& bacteria.has_grouping.some(bacteria.InSmallChain)
& bacteria.has_grouping.only(Not(bacteria.Isolated))
& bacteria.gram_positive.value(True)]
我们用不同的 OWL 构造函数获得了我们在第三章中输入的正式定义;我们将在第六章中看到如何操作这些。
如前所述,可以通过前缀“INDIRECT_
”来获得间接等价(例如,如果 A 等价于 B and B 等价于 C,我们将获得 A 等价于 B 和 C)。
>>> onto.Streptococcus.INDIRECT_equivalent_to
最后,disjoints()
和constructs()
方法返回生成器,分别列出引用该类的所有分离和构造函数。
存在性限制
Owlready 允许您访问存在性限制(some 和 value 类型的限制),就好像它们是“类属性”,使用点符号“Class.property ”,例如,在类链球菌上:
>>> onto.Streptococcus.gram_positive
True
>>> onto.Streptococcus.has_grouping
[bacteria.InSmallChain]
Owlready 还提供了对类定义中使用的所有构造函数的详细访问(参见 6.2)。
属性
超属性、子属性、祖先、后代和等效属性可以用与类相同的方式获得。
domain
和range
属性用于获取属性的域和范围。注意,这些属性都返回一个列表。当存在多个值时,OWL 认为域或范围是不同值的交集。
>>> onto.has_grouping.domain
[bacteria.Bacterium]
>>> onto.has_grouping.range
[bacteria.Grouping]
range_iri
属性用于以 IRI 列表的形式获取属性的范围,这有助于区分 OWL 支持的不同类型的数据(例如,XMLSchema#decimal、XMLSchema#double 和 XMLSchema#float,而对于 Owlready 中的所有三种类型,范围属性都是 Python float
类型,Python 只有一种类型的浮点数)。
python_name
属性用于改变一个属性的名字,在这个名字下可以用点符号访问属性。这允许您使用更符合 Python 精神的名称。事实上,OWL 属性通常被称为“has_ …”,而在 Python 中,属性名很少这样开头。类似地,在 Python 中,我们更喜欢在包含值列表的属性末尾加上复数“s”。例如,我们可以将属性“has_grouping”的名称更改为“grouping ”,如下所示:
>>> onto.has_grouping.python_name = "groupings"
>>> onto.unknown_bacterium.groupings
[bacteria.in_cluster1]
请注意,只有在 Python 中使用点符号时,属性的名称才会改变。另一方面,object 属性仍然可以作为onto.has_grouping
访问,并且它的 IRI 不会改变。可以返回到以前的名称,如下所示:
>>> onto.has_grouping.python_name = onto.has_grouping.name
get_relations()
方法返回一个生成器,列出属性的所有(subject,object)对,例如:
>>> for subject, object in onto.has_grouping.get_relations():
... print(subject, "has for grouping" , object)
bacteria.unknown_bacterium has for grouping bacteria.in_cluster1
还可以使用另一种语法“property[individual]”来获取给定个人的属性值。与通常的语法“individual.property”不同,这种替代语法总是返回一个值列表(即使是在函数属性的情况下),这在某些情况下可能很有用:
>>> prop = onto.gram_positive
>>> prop[onto.unknown_bacterium]
[True]
如果属性名包含 Python 中的无效字符(例如," "),此语法也很有用)或者如果本体包括具有不同 IRI 但以相同名称结尾的几个属性。以下示例显示了如何在 Python 中访问具有无效名称的属性:
onto["my.propertyindividual"]
4.6 搜索实体
本体对象的search()
方法使得从实体的 IRI 和/或关系中搜索实体成为可能。搜索时,下列关键字是可用的,并且可以相互组合:
-
iri
由 IRI 去搜索 -
搜索给定类别的个人
-
subclass_of
搜索给定类的后代类 -
is_a
搜索给定类的个体和后代类 -
按关系搜索的任何属性名
此外,在字符串中,“*”可以用作通配符。以下示例搜索 IRI 包含“Coccus”的所有实体:
>>> onto.search(iri = "*Coccus*")
[bacteria.Coccus]
默认情况下,搜索区分大小写。_case_sensitive
参数用于改变这种行为,例如:
>>> onto.search(iri = "*Coccus*", _case_sensitive = False)
[bacteria.Coccus, bacteria.Staphylococcus, bacteria.Streptococcus]
这一次,我们发现更多的结果,因为“葡萄球菌”和“链球菌”确实包含“球菌”,但带有小写的“c”而不是大写的。
search()
返回的结果看起来像 Python 列表,可以作为列表使用。然而,它不是一个经典的列表;我们可以用 class 属性来检查它:
>>> r = onto.search(iri = "*Coccus*", _case_sensitive = False)
>>> r.__class__
<class 'owlready2.triplelite._SearchList'>
这是一个特殊的列表,称为“惰性”列表,其元素只在最后一刻才确定。例如,在下面的代码中,第一行创建了“lazy”列表,但是还没有执行搜索。只有在最后时刻,当我们请求访问列表的内容时,才会这样做(例如,使用print()
进行显示)。
>>> r = onto.search(iri = "*Coccus*", _case_sensitive = False)
>>> print(r) # The search is only performed here
[bacteria.Coccus, bacteria.Staphylococcus, bacteria.Streptococcus]
search()
方法可以接受多个参数。以下示例搜索属于革兰氏阳性菌类(=与True
有gram_positive
关系)的所有个体:
>>> onto.search(type = onto.Bacterium, gram_positive = True)
[bacteria.unknown_bacterium]
字符串“*”可以用作“通配符”,也就是说,不管关联的值是什么(包括非文本值:数字、对象等),都可以搜索关系的存在。).以下示例搜索革兰氏状态已知的所有细菌(无论它是什么):
>>> onto.search(type = onto.Bacterium, gram_positive = "*")
[bacteria.unknown_bacterium]
几个值的列表也可以用作search()
的参数。在这种情况下,只返回与列表中每个元素都有关系的实体。这里有一个例子(你必须在本体中创建个体isolated1
和by_two1
来测试这个例子):
>>> onto.search(type = onto.Bacterium,↲
has_grouping = [onto.isolated1, onto.by_two1])
也可以使用None
值搜索没有关系的个人。例如,我们可以搜索没有形状的细菌,如下所示:
>>> onto.search(type = onto.Bacterium, has_shape = None)
要在所有本体中进行搜索(如果已经加载了几个本体),可以搜索默认的“世界”default_world
,如下所示:
>>> default_world.search(iri = "*Coccus*")
带有search()
的搜索也可以嵌套。在这种情况下,Owlready 自动组合搜索,在 quadstore 中生成一个优化的 SQL 查询。以下示例搜索具有 InChain 分组的所有细菌(包括 InSmallChain 和 InFilament)。为此,我们嵌套了对search()
的两个调用:一个用于查找链分组,另一个用于查找相关的细菌。
>>> onto.search(type = onto.Bacterium,↲
has_grouping = onto.search(type = onto.InChain))
最后,search_one()
方法的工作方式与search()
相同,但是只返回一个结果,而不是一个列表。
要执行更复杂的搜索,可以通过结合使用 Owlready 和 RDFlib 来使用 SPARQL 查询语言(参见 11.3 节)。
4.7 巨大的本体和磁盘缓存
基因本体(GO)是生物信息学中广泛使用的本体,它非常庞大(近 200 MB)。使用以下命令加载 GO 需要几十秒甚至几分钟,这取决于计算机的功率和 OWL 文件的下载时间:
>>> go = get_ontology("http://purl.obolibrary.org/obo/go.owl").↲ load()
默认情况下,Owlready 在 RAM 中以 RDF 格式存储包含本体的 quadstore。在 Python 程序执行结束时,quadstore 丢失,每次新的执行都必须重新加载 OWL 文件。为了避免这些长时间的重新加载,可以使用default_world.set_backend()
方法将 quadstore 放在磁盘上。然后,default_world.save()
会保存它,例如:
>>> default_world.set_backend(filename = "quadstore.sqlite3")
>>> go = get_ontology("http://purl.obolibrary.org/obo/go.owl").↲ load()
>>> default_world.save()
这里,我们为 quadstore 使用了相对文件路径;我们可以使用绝对路径(例如,Linux/Mac 上的“/home/jiba/owlready/quadstore.sqlite3
”或 Windows 上的“C:\\quadstore.sqlite3
”)。
为了从 quadstore 加载本体,在 Python 的新的执行期间,通过使用与之前相同的三行代码,重新定义 quadstore 文件并加载本体就足够了(行default_world.save()
可以被忽略或保留;它将没有任何效果,因为没有要保存的更改)。加载是即时的,因为本体是直接从 quadstore 中恢复的,不需要任何下载或解析 OWL 文件的操作。
注意,如果内存中加载了几个本体(例如前面的细菌和 GO 的本体),所有的本体都存储在同一个 quadstore 中,因此保存在同一个文件中。
最后,default_world.save()
方法用于保存 quadstore 中所做的更改(该方法对应于数据库上的“提交”操作,因此如果没有要记录的更改,即使对于非常大的本体,它的性能成本也几乎为零)。如果更改没有保存,它们将在程序执行结束时丢失。
4.8 名称空间
一些本体在不属于它们自己的名称空间中定义实体。围棋就是这种情况:围棋 IRI 是“ http://purl.obolibrary.org/obo/go.owl
,但是围棋实体有以“ http://purl.obolibrary.org/obo/
”开头的虹膜(没有“go.owl”后缀)。因此,不可能使用go
本体对象来访问带点符号的实体:
>>> go.GO_0035065
None
的确,前面一行对应的是 IRI 的“ http://purl.obolibrary.org/obo/go.owl#GO_0035065
”,而预料中的 IRI 的概念是“ http://purl.obolibrary.org/obo/GO_0035065
”(所以不带“go.owl”后缀)。
要访问 GO 实体,可以使用 IRIS 全局伪字典(见 4.5)。另一个选择是为“”http://purl.obolibrary.org/obo/
”创建一个命名空间,如下:
>>> obo = get_namespace("http://purl.obolibrary.org/obo/")
然后,obo
名称空间可以用来访问带点符号的实体:
>>> obo.GO_0035065
obo.GO_0035065
>>> obo.GO_0035065.label
['regulation of histone acetylation']
4.9 修改实体渲染为文本
默认情况下,Owlready 显示实体的名称,前面有一个句点和 IRI 的最后一部分(不带扩展名“.”。猫头鹰”,如果存在的话)。但是,当实体的名称是任意标识符时,这种显示是不令人满意的,如下例所示:
>>> obo.GO_0035065
obo.GO_0035065
全局函数set_render_func()
允许重新定义 Owlready 呈现实体的方式。在下面的示例中,我们使用“label”注释属性来呈现实体的名称(即,其标识符),如果没有,则使用实体的名称:
>>> def my_rendering(entity):
... return entity.label.first() or entity.name
>>> set_render_func(my_rendering)
>>> obo.GO_0035065
regulation of histone acetylation
在围棋中,几乎所有的实体都是类(而不是个体;这在生物医学本体论中是相当普遍的做法)。如前所述(在 4.5.4 中),可以使用点符号访问这些类的存在性限制,如下例所示(其中RO_0002211
是“regulates”属性的名称 GO):
>>> obo.GO_0035065.RO_0002211
[histone acetylation]
然而,像以前一样,当属性名是任意代码时,使用属性名有时会很费力。接下来的三行允许您使用属性标签而不是它们的名称(在用下划线替换空格之后):
>>> for prop in go.properties():
... if prop.label:
... prop.python_name = prop.label.first().replace(" " , "_")
这使得查询本体更容易:
>>> obo.GO_0035065.regulates
[histone acetylation]
但是要小心,因为 GO 不能保证标签从一个版本到另一个版本的守恒性!因此,在设计持久的程序时,应该避免这个提示。
如前所述,可以在属性名前加上前缀"INDIRECT_
,以获得间接定义的限制,例如,从超类继承的限制:
>>> obo.GO_0035065.INDIRECT_regulates
[cellular component organization,
metabolic process,
protein metabolic process,
protein acetylation,
histone acetylation,
...]
4.10 本体的本地目录
Owlready 还可以处理一个或多个包含本地副本的目录。本地副本将被优先使用,而不是从互联网上下载本体。在 Unix/Linux/Mac 下,本地目录必须按照以下方式填入全局变量onto_path
:
>>> onto_path.append("/home/jiba/owlready")
>>> onto = get_ontology("http://lesfleursdunormal.fr/static/↲
_downloads/bacteria.owl#").load()
或者在 Windows 下:
>>> onto_path.append("C:\\owlready")
>>> onto = get_ontology("http://lesfleursdunormal.fr/static/↲
_downloads/bacteria.owl#").load()
全局变量onto_path
包含本地本体目录列表;默认情况下,它是空的。在从互联网上下载一个本体之前,owl 已经检查了在onto_path
中的一个目录中是否没有本地副本。在前面的示例中,如果缓存目录中存在一个“bacteria.owl”文件(在 Linux/Unix/Mac 示例中为/home/jiba/owlready,在 Windows 示例中为“C:\owlready”),将使用该文件。否则,本体将从互联网上下载。
onto_path
的工作类似于sys.path
列表,它允许 Python 找到 Python 模块和包,或者类似于 Java 中的CLASSPATH
环境变量。
此外,可选参数only_local
允许您禁止从互联网加载本体,如下例所示:
>>> onto = get_ontology("http://lesfleursdunormal.fr/static/↲
_downloads/bacteria.owl#").load(only_local = True)
如果你想使用在互联网上找到的不同版本的本体,例如,旧版本或“外来”格式,本地本体目录特别有用。特别是,一些在线可用的本体是 Owlready 无法阅读的格式。使用本地目录,有可能为 Owlready 提供先前翻译成 RDF/XML 或 N-Triples(例如,通过 Protégé手动翻译)的这些本体的版本。
4.11 在 quadstore 中重新加载本体
当使用本地文件(如前所述的本地本体目录或从本地 OWL 文件加载本体)和存储在磁盘上的四元存储时,更新四元存储中的本体的问题出现了。当修改本地 OWL 文件时,必须在 quadstore 中更新本体。这可以通过前面看到的load()
方法的reload
选项来完成,但是也可以通过reload_if_newer
选项来完成,只有当 OWL 文件比存储在 quadstore 中的版本新时,该选项才会重新加载本体:
>>> go = get_ontology("http://purl.obolibrary.org/obo/↲go.owl#")
.load(reload_if_newer = True)
请注意,从 OWL 文件重新加载本体会覆盖存储在 quadstore 中的版本。因此,您必须避免同时修改本体的 OWL 文件及其存储在 quadstore 中的版本!
4.12 示例:从本体创建动态网站
在这个例子中,我们将生成一个动态网站来呈现一个本体的类和个体。为此,我们将使用 Owlready 和 Flask,这是一个 Python 模块,允许您轻松创建网站。Flask 允许您将网站上的 URL 路径与 Python 函数相关联;当请求这个路径时,函数被调用,它必须返回相应的 HTML 页面。路径是通过在函数前一行添加@app.route('/path')
来定义的(这是一个 Python 函数装饰器)。路径可以包含参数(在路径中用尖括号<...>
表示),这些参数将作为参数传递给 Python 函数。
以下函数显示了一个带有 Flask 的网页的简单示例:
from flask import Flask, url_for
app = Flask(__name__)
@app.route(’/path/<parameter>’)
def generate_web_page(parameter):
html = "<html><body>"
html += "The value of the parameter is: %s % parameter"
html += "</body></html>"
return html
我们网站的完整程序如下:
# File dynamic_website.py
from owlready2 import *
onto = get_ontology("bacteria.owl").load()
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def ontology_page():
html = """<html><body>"""
html += """<h2>'%s' ontology</h2>""" % onto.base_iri
html += """<h3>Root classes</h3>"""
for Class in Thing.subclasses():
html += """<p><a href="%s">%s</a></p>""" %↲
(url_for("class_page", iri = Class.iri), Class.name)
html += """</body></html>"""
return html
@app.route('/class/<path:iri>')
def class_page(iri):
Class = IRIS[iri]
html = """<html><body><h2>'%s' class</h2>""" % Class.name
html += """<h3>superclasses</h3>"""
for SuperClass in Class.is_a:
if isinstance(SuperClass, ThingClass):
html += """<p><a href="%s">%s</a></p>""" %↲
(url_for("class_page", iri = SuperClass.iri), SuperClass.name)
else:
html += """<p>%s</p>""" % SuperClass
html += """<h3>equivalent classes</h3>"""
for EquivClass in Class.equivalent_to:
html += """<p>%s</p>""" % EquivClass
html += """<h3>Subclasses</h3>"""
for SubClass in Class.subclasses():
html += """<p><a href="%s">%s</a></p>""" %↲
(url_for("class_page", iri = SubClass.iri), SubClass.name)
html += """<h3>Individuals</h3>"""
for individual in Class.instances():
html += """<p><a href="%s">%s</a></p>""" %↲
(url_for("individual_page", iri = individual.iri),↲ individual.name)
html += """</body></html>"""
return html
@app.route('/individual/<path:iri>')
def individual_page(iri):
individual = IRIS[iri]
html = """<html><body><h2>'%s' individual</h2>""" %↲ individual.name
html += """<h3>Classes</h3>"""
for Class in individual.is_a:
html += """<p><a href="%s">%s</a></p>""" %↲
(url_for("class_page", iri = Class.iri), Class.name)
html += """<h3>Relations</h3>"""
if isinstance(individual, onto.Bacterium):
html += """<p>shape = %s</p>""" % individual.has_shape
html += """<p>grouping = %s</p>""" % individual.has_grouping
if individual.gram_positive == True:
html += """<p>Gram +</p>"""
elif individual.gram_positive == False:
html += """<p>Gram -</p>"""
html += """</body></html>"""
return html
import werkzeug.serving
werkzeug.serving.run_simple("localhost", 5000, app)
在这个程序中,我们定义了三个函数,每个函数都与一个 URL 路径相关联,并且对应于网站上三种不同类型的页面:
-
“本体”页面(对应于网站根的路径“/”)显示本体的 IRI,并列出根类(即,事物的直接子类)。对于每个类,我们在指向相应类页面的互联网链接中显示其名称。这些链接的 URL 是通过 Flask 的
url_for()
函数获得的,该函数根据相应函数的名称和任何参数返回给定网页的 URL。 -
“类”页面(路径“/class/IRI_of_the_class”)显示所请求的类的名称,并列出其超类、等价类、子类和个体。对于超类,当它是一个实体(即 ThingClass 的一个实例)时,我们用一个链接显示超类的名称,或者当它是一个 OWL 逻辑构造函数(例如一个限制)时,简单地显示该类(没有链接)。
-
个人页面(路径“/个人/个人的 IRI”)显示所请求的个人的姓名并列出其类别。如果它是一种细菌,我们也显示它的形状,它的分组,和它的革兰氏状态。
最后两行用于通过 Werkzeug 服务器(Flask 安装的 Python 模块)启动网站。一旦程序被执行,就可以通过浏览器中的地址“http://127.0.0.1:5000
”访问该网站。以下截图显示了动态网站的“本体”和“类”页面:
4.13 总结
在这一章中,你已经学会了如何使用 Owlready 来访问和阅读 Python 中的 OWL 本体。我们已经使用了上一章设计的细菌本体,但是还有一个更大更复杂的资源,基因本体。最后,我们看到了如何在基于 Flask 的动态网站中使用本体。