Quantcast
Channel: CodeSection,代码区,Python开发技术文章_教程 - CodeSec
Viewing all articles
Browse latest Browse all 9596

精通 Oracle+Python,第 3 部分:数据解析

$
0
0

进行数据解析的理由不计其数,相关的工具和技巧也同样如此。但是,当您需要用这些数据做一些新的事情时,即使有“合适的”工具可能也是不够的。这一担心对于异类数据源的集成同样存在。用来做这项工作的合适工具迟早应该是一种编程语言。

Oracle 提供了一些非常强大的实用程序来加载、处理和卸载数据。SQL*Loader、Data Pump、外部表、Oracle Text、正则表达式都能提供这些功能。然而人们常常会需要在数据库外做一些事情(或者,说得琐碎些,可能您还没有获得必要的数据库权限)。

利用 python 可以进行高水平的、有效的数据解析。而利用互联网上免费提供的大量标准库和众多模块可以处理数据逻辑,不必手动剖析字节。

字符串理论

文 本解析的最低级别是字符串。Python 并不把字符区分为单独的数据类型,但却区分普通字符串和 Unicode 字符串类型。字符串可以括在单引号、双引号或三引号中,并且是 Python 的一种不可变对象 ― 一旦创建就不能对其进行修改。每一个操作都会创建一个新的字符串对象。对于具有静态类型语言经验的编程人员而言,乍听上去这可能真得很奇怪,但此类实现有 一些 特定的原因 ,多数与性能有关。

因为 Python 完全支持 Unicode,所以处理多语言信息不存在问题。手动创建 Unicode 字符串时,您可以选择直接在字符串前使用 u 前缀(如 u"Unicode text" )或者使用内置的 unicode() 函数。可以使用 unicode() 或 encode() 方法在任何支持的字符集中对字符串进行编码。有关支持的编码列表,请查阅 Python 库参考 的 标准编码部分 或使用导入编码;输出 encodings._aliases.keys()。

您可以放心地使用 UTF-8 编写 Python 程序,记住仅变量名必须是有效的 ASCII 字符串。注释可以是希腊文、汉字或任意内容。不过,这样的文件或者要求使用附加字节顺序标记 (BOM) 的编辑器来保存,或者,需要您编写第一行代码:

# -*- coding: utf-8 -*-

字符串提供有一组方法可用于进行大多数有用的文本操作,如 find()、split()、rjust() 或 upper()。它们在内置的 str 类型上实现,该类型可以表示普通字符串和原始字符串。(原始字符串与普通字符串对反斜线的解释不同。)

>>> zen = "Explicit is better than implicit."
>>> print zen.title()
'Explicit Is Better Than Implicit.'
>>> zen.split(' ')
['Explicit', 'is', 'better', 'than', 'implicit.']
>>> zen.rstrip('.').lower().replace('is', 'is always')
'explicit is always better than implicit' Python 的可迭代类型的最棒的一个特性是索引方法。普通索引以 0 开始而负索引向后计数,所以 [-1] 表示最后一个字符,[:5] 表示前 5 个字符,而 [5:-5] 表示前 5 个和后 5 个字符组成的字符串。 >>> sample = "Oracle Database"
>>> sample[0]
'O'
>>> sample[0:6], sample[7:15]
('Oracle', 'Database')
>>> sample[-8:]
'Database'
>>> sample[sample.index('Data')+4:]
'base' 正则表达式

Python 当然支持正则表达式。事实上,Python 的正则表达式 re 模块支持 Unicode、匹配、搜索、拆分、替换和分组。如果您熟悉 Oracle 对正则表达式的实现方式,您就不会对 Python 的函数感到陌生。

在详细比较 Python 和 Oracle 对正则表达式的实现时,值得注意的差异包括:

当关系设计要求一种不同于编程语言 1 的方法时,re.search() 可用于代替 Oracle 的 REGEXP_LIKE、REGEXP_INSTR 和 REGEXP_SUBSTR。

对 Python 语法改写后,re.sub() 的使用方式可以与 REGEXP_REPLACE 完全相同。不过,要注意 Oracle 的位置参数从 1 开始,而 Python 编制任何索引时都从 0 开始。

Oracle 的 match_parameter 表示正则表达式的一组标志,方式与 Python 在搜索模式或模式对象编译属性中使用 (?iLmsux) 语法的方式相同。要获得有效标志的列表,请比较 Python 库参考 的 4.2.3 节 和 Oracle 数据库 SQL 语言参考 中 match_parameter 的有效值列表。

Python 的 re.search() 函数非常灵活,这归功于正则表达式这一基本概念。re 模块的最底层有一个对象,它表示匹配模式的方式允许以多种不同的方法对源字符串进行剖析。re.compile() 函数返回一个采用某一模式和若干可选标志的编译模式对象,如 re.I,它表示不区分大小写的匹配。

>>> import re
>>> p = re.compile("^a.*", re.I)
>>> print p
<_sre.SRE_Pattern object at 0x011CA660>

您无须显式编译正则表达式。re 模块中的函数以透明方式完成此工作。如果代码中多处用到编译模式,使用该模式非常有益,但是如果该模式仅使用一次,则不需要这样的编码开销。

Python 中有六个正则表达式编译标志:

I (IGNORECASE) 用于不区分大小写的匹配

L (LOCALE) 使得特殊的序列(如词和空格)与语言设置相关

M (MULTILINE) 意味着在多行中搜索该模式,这样 可以匹配字符串的开始位置和每一个换行符后面的位置,$ 可以匹配每一个换行符前面的位置和字符串的结束位置

S (DOTALL) 强制使用点专用字符 (.) 匹配任意字符,包括换行符

U (UNICODE) 使得特殊的序列可以识别 Unicode

X (VERBOSE) 可以增强您编写的正则表达式的可读性。

要 一次使用多个标志,只需将它们加在一起即可 ― 如 re.compile("Oracle", re.I+re.S+re.M)。另一种方式是使用 (?iLmsux) 语法将使用所需数量的标志选项作为搜索模式的前缀。这样,前一表达式可写作 re.compile("(?ism)Oracle")。

有关使用正则表达式的最好建议是尽可能地避免使用它们。在将它们嵌入代码前,请确定没有字符串方法可以完成相同的工作,因为字符串方法更快且不会带来导入以及正则表达式处理这些额外的开销。在字符串对象上使用 dir() 就可以看到可用的内容。

下例展示了在 Python 这样一种动态语言中看待正则表达式的方式。解析 tnsnames.ora 文件以便为每个网络别名创建简单连接字符串(将 file() 函数指向您的 tnsnames.ora 文件的位置):

>>> import re
>>> tnsnames = file(r'tnsnames.ora').read()
>>> easy_connects = {}
>>> tns_re = "^(\w+?)\s?=.*?HOST\s?=\s?(.+?)\).*?PORT\s?=\s?(\d+?)\).

*?SERVICE_NAME\s?=\s?(.+?)\)"

>>> for match in re.finditer(tns_re, tnsnames, re.M+re.S):

... t = match.groups()

... easy_connects[t[0]] = "%s:%s/%s" % t[1:]

>>> print easy_connects

此程序在 Oracle Database XE 默认的 tnsnames.ora 文件上的输出是:

{'XE': 'localhost:1521/XE'}

请注意,此正则表达式非常愚钝,会被 IPC 条目所阻塞,因此需要将这些条目放在文件的结尾处。解析匹配圆括号是一个 NP 完成问题。

因为提供有多种公开方法,Python 匹配对象的功能非常强大,这些方法包括 span()(它可以返回匹配范围)、group()(它可以按给定的索引返回匹配组)以及 groupdict()(它可以在模式含有命名的组时以字典形式返回匹配组)。

逗号分隔值

CSV 格式因其简洁性和跨平台设计常用于组织间的信息交换。使用正则表达式通常可以轻松地解析逗号分隔值,但使用 Python 的 csv 模块可以使此任务变得更为容易。

使 用该模块要求开发人员熟悉该模块所采用的逻辑。有关 CSV 文件的最重要的信息是它的“方言”,它包含分隔符、引号字符、行终止符等相关信息。Python 2.5 中目前可用的方言是 excel 和 excel-tab。内置的嗅探器总是试图猜测正确的格式。写入器与阅读器对象支持 CSV 数据的输入和输出。

就本例而言,我用的是 HR 模式的 JOBS_HISTORY 表中的数据。它演示了如何直接从一个 SQL 查询创建 CSV 文件 job_history.csv。

>>> import csv
>>> import cx_Oracle
>>> db = cx_Oracle.connect('hr/hrpwd@localhost:1521/XE')
>>> cursor = db.cursor()
>>> f = open("job_history.csv", "w")
>>> writer = csv.writer(f, lineterminator="\n", quoting=csv.QUOTE_NONNUMERIC)
>>> r = cursor.execute(" "SELECT * FROM job_history ORDER BY employee_id, start_date")
>>> for row in cursor:
... writer.writerow(row)
...
>>> f.close()

该文件包含:

101,"1989-09-21 00:00:00","1993-10-27 00:00:00","AC_ACCOUNT",110
101,"1993-10-28 00:00:00","1997-03-15 00:00:00","AC_MGR",110
102,"1993-01-13 00:00:00","1998-07-24 00:00:00","IT_PROG",60
114,"1998-03-24 00:00:00","1999-12-31 00:00:00","ST_CLERK",50
122,"1999-01-01 00:00:00","1999-12-31 00:00:00","ST_CLERK",50
176,"1998-03-24 00:00:00","1998-12-31 00:00:00","SA_REP",80
176,"1999-01-01 00:00:00","1999-12-31 00:00:00","SA_MAN",80
200,"1987-09-17 00:00:00","1993-06-17 00:00:00","AD_ASST",90
200,"1994-07-01 00:00:00","1998-12-31 00:00:00","AC_ACCOUNT",90
201,"1996-02-17 00:00:00","1999-12-19 00:00:00","MK_REP",20

或者,您也可以使用 Oracle SQL Developer 以 CSV 格式导出数据。

可以通过以下方式读取该 CSV 文件:

>>> reader = csv.reader(open("job_history.csv", "r"))
>>> for employee_id, start_date, end_date, job_id, department_id in reader:
... print job_id,
...
JOB_ID IT_PROG AC_ACCOUNT AC_MGR MK_REP ST_CLERK ST_CLERK

AD_ASST SA_REP SA_MAN AC_ACCOUNT

注意我不必在上面显式指定方言,它是自动推断出的。我只是输出了 job_id 列,但对这样经过解析的文件我确实可以做的是将其插入数据库中。为确保日期得到正确处理,在批量插入前对 NLS_DATE_FORMAT 进行手动设置。

SQL> CREATE TABLE job_his (
2 employee_id NUMBER(6) NOT NULL,
3 start_date DATE NOT NULL,
4 end_date DATE NOT NULL,
5 job_id VARCHAR2(10) NOT NULL,
6 department_id NUMBER(4)
7 );
>>> reader = csv.reader(open("job_history.csv", "r"))
>>> lines = []
>>> for line in reader:
... lines.append(line)
...
>>> cursor

Viewing all articles
Browse latest Browse all 9596

Trending Articles