用 Oracle 和 Python 武裝您的頭腦

idba發表於2008-08-21

PL/SQL 比較適合於 Oracle 資料庫中的程式設計任務。而大多數 Oracle 專業人員並不會侷限於完全在資料庫中進行工作。您可能曾經遇到過許多不便於使用甚至無法使用 PL/SQL 的任務,如操作磁碟上的檔案、呼叫外部程式或 shell 指令碼、執行復雜的文字分析以及頻繁執行物件導向的工作。如果 PL/SQL 是您唯一的程式語言,那麼可能會為您的 Oracle 工作帶來很大的不便。

既然如此,應另外選擇哪種語言呢?您的時間可能並不多,因此它應該是一種簡單的語言,即易於學習和使用。某些動態語言(尤其是 Perl、Python、PHP 和 Ruby)正是因為其易用性而獲得了越來越多的關注。動態語言的許多使用者發現,他們編寫有效程式碼的速度要比使用更傳統的語言(如 C++ 或 Java)快很多倍。

尤其值得一提的是 Python,該語言不但易於使用,而且還更高效,它生成的程式碼不但具有可讀性而且結構良好。因此,當您再看幾個月前寫的程式碼時,仍可以理解、修改並重用它。由於具備近乎自描述的外觀,因此 Python 清晰、簡潔的語法有時被稱作“可執行的虛擬碼”。Python 是一種高度物件導向的語言,它易於學習並遵循良好的程式設計風格,即使那些沒有經過正式軟體工程培訓的使用者也可以使用它。由於易於學習,因此它無論是對於新手還是對於專家都具有很大的吸引力。

與 Perl 一樣,Python 的功能可以滿足各種軟體需要;該語言的簡單並不代表它膚淺和應用面窄。一旦您使用了 Python,您就不會再想使用第三種語言。

最後,Python 是免費的跨平臺開放原始碼軟體。Python 實在值得一試!

本文並不嘗試對 Python 進行全面的介紹或細緻的講解,而是建立一個有用的示例應用程式,用以演示對 Oracle 使用者最重要的 Python 功能。如果您確信 Python 值得進一步研究,那麼本文提供的連結可以為您提供更多資訊。

快速入門

如果您正在執行 Linux,則可能已經擁有了 Python,它包含在常見的發行版本中。否則,請從 www.python.org 下載它。除了原始碼以外,還提供了便於使用的 RPM 和 Windows 可執行安裝程式。安裝後,請確保 PATH 中包含 Python 可執行檔案,然後在命令提示符下鍵入 python 啟動互動式直譯器。您將看到類似如下所示的資訊:
c:\>python
Python 2.4 (#60, Nov 30 2004, 11:49:19) [MSC v.1310 32 bigt (Intel)] on win32
Type "help", "copyright", "credits", or "license" for more information.
>>>
>>> 提示符等待接受您發出的 Python 命令。與 SQL*Plus 一樣,Python 允許您即席執行命令或在編寫成熟的指令碼之前試驗命令。

按照慣例,您接下來將執行以下命令:
>>> print 'Hello, World'
直譯器做出以下響應:
Hello, World
注意不要鍵入 Print 或 PRINT。在 Python 中,所有東西(命令、變數名等)都是區分大小寫的。

使用文字檔案

假設您用一個標準的 init.ora 檔案來資料庫中的所有預設引數。您需要將其內容與特定資料庫例項的 init.ora 檔案內容進行比較。

清單 1:init_default.ora

DB_BLOCK_SIZE=4096
COMPATIBLE=9.2.0.0.0
SGA_MAX_SIZE=104857600
SHARED_POOL_SIZE=50331648

清單 2:init_orcl.ora

FAST_START_MTTR_TARGET=300
SHARED_POOL_SIZE=50331648
DB_BLOCK_SIZE=8192
COMPATIBLE=9.2.0.0.0
UNDO_MANAGEMENT=AUTO
SGA_MAX_SIZE=135339844
首先開啟要讀取的 init_orcl.ora。
>>> initFile = open('init_orcl.ora')
您現在已經開啟了該檔案並指定了一個變數 initFile 來引用它。(Python 使用一個 = 進行賦值,並使用 == 進行比較,而 PL/SQL 分別使用 := 和 =。)注意,您不必宣告 initFile 或指定它的資料型別;作為一種“動態型別”語言,Python 可以自動識別。

我們來看看結果如何。
>>> firstLine = initFile.readline()
>>> firstLine
'FAST_START_MTTR_TARGET=300\n'
其中的 readline 是物件 initFile 中定義的方法。如果您不熟悉物件導向的程式設計,則對您而言這將是一個全新的語言,而 Python 語言為您瞭解此概念提供了一個出色的環境。

從 PL/SQL 的使用經歷中可以知道,readline 之後不需要圓括號,這是因為您並未向它傳遞任何引數。但在 Python 中,省略圓括號將導致意外的結果。
>>> firstLine = initFile.readline
>>> firstLine

您實際上並未呼叫 readline 方法,而是隻將一個指標指向它並將其插入到 firstLine 中。這在某些高階程式設計技術中可能非常有用;例如,您實際上可以從分配給該函式的位置呼叫該函式。
>>> firstLine()
'SHARED_POOL_SIZE=50331648\n'
但對現在而言,只需記住在呼叫 Python 函式時必須使用 ()。下面我們向 firstLine 中插入一個全新的字串以繼續操作。
>>> firstLine = initFile.readline()
>>> firstLine
'DB_BLOCK_SIZE=8192\n'

自省

firstLine 結尾的 \n 是一個您並不需要的換行符。如何刪除它?儘管 Python 提供了出色的文件,但您也不必參考它 - Python 的自省功能可以幫助您確定如何從提示符下執行正確操作。

在 Python 中,類似 firstLine 這樣的字串是一個物件。作為物件,它包含方法,即為操作它而定義的函式。下面我們來看看可以對 firstLine 執行哪些操作。
>>> type(firstLine)

>>> dir(firstLine)
['__add__', '__class__', '__contains__', '__delattr__', 
'__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', 
'__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', 
'__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', 
'__new__', '__reduce__', '__reduce_ex__', 
'__repr__', '__rmod__', '__rmul__', '__setattr__',
'__str__', 'capitalize', 'center', 'count', 'decode',
'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum',
'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper',
'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind', 'rindex',
'rjust', 'rstrip', 'split', 'splitlines', 'startswith', 'strip',
'swapcase', 'title', 'translate', 'upper', 'zfill']
能否執行 strip?根據上面顯示的內容,應該可以執行。按照約定,一個簡單的文件字串儲存在每個物件的 __doc__ 方法中。(類似 __doc__ 這樣的以兩個下劃線開頭和結尾的名稱保留給系統定義的特殊方法和屬性使用。)下面我們將列印 strip.__doc__,看看它是否是您所需的。
>>> print firstLine.strip.__doc__
S.strip([chars]) -> string or unicode

返回字串 S 的一個副本,並刪除了前導空格和字尾空格。
如果給出了 chars 且非 None,則刪除 chars 中字元。
如果 chars 為 unicode,則在剝離前將把 S 轉換為 unicode

>>> firstLine = firstLine.strip()
>>> firstLine
'DB_BLOCK_SIZE=8192'
接下來,您需要對 firstLine 中的引數與其值進行區分。儘管很想展示一下 Python 強大的正規表示式處理能力,但我們還是採用了一個更簡單的方式:字串方法 split()。
>>> firstLine.split('=')
['DB_BLOCK_SIZE', '8192']

變數和賦值

呼叫 split() 生成了一個由位於分隔符 = 之前和之後的字串組成的列表。遺憾的是,由於您未將該結果指定給任何變數,因此結果只顯示到螢幕,隨後就不見了。這次,您將使用多個賦值來同時捕獲兩個結果。您可能想使用向上箭頭鍵來省去一些鍵入操作。
>>> param, val = firstLine.split('=')
>>> param
'DB_BLOCK_SIZE'
>>> val

'8192'
實際上,您將需要分別儲存多個引數的值。這種場合最適於使用一個稱作字典的強大 Python 變數型別。字典是一個無序的鍵集,即值對;實際上,它與 Oracle 資料庫中的普通(堆)表非常相似。鍵和值可以是數字、字串或其他物件。您將建立一個空字典,並使用您到目前為止提取的資訊填充它。
>>> initParams = {}
>>> initParams[param] = val
>>> initParams
{'DB_BLOCK_SIZE': '8192'}
現在,從開啟的檔案中提取另一行,並將該行也儲存到字典中。這次,您將把 strip() 直接連線到 readline() 的結尾,就好象您在使用一個 UNIX 管道。
>>> nextLine = initFile.readline().strip()
>>> param, val = nextLine.split('=')
>>> initParams[param] = val
>>> initParams
{'DB_BLOCK_SIZE':'8192', 'COMPATIBLE': '9.2.0.0.0'}

編寫指令碼

現在,您已經以互動方式試用瞭直譯器,下面您將準備編寫一個 Python 指令碼來處理整個檔案。使用 Ctrl-D(在 Unix/Linux 中)或 Ctrl-Z(在 Windows 中)退出 Python 直譯器,然後建立一個文字檔案 - 稱之為 readInitOra.py。
initFile = open('init_orcl.ora', 'r')
initParams = {}
rawTextLine = initFile.readline()
while rawTextLine:
param, val = rawTextLine.strip().split('=')
initParams[param] = val
rawTextLine = initFile.readline()
print initParams
在閱讀此程式碼時,您很可能想到“命令結尾的分號哪去了?BEGIN 和 END 語句以及用於標記塊的大括號哪去了?”實際上,Python 並不使用這些符號。按照慣例,幾乎所有程式設計人員都使用換行符分隔命令,並使用縮排指示程式碼塊,以便使用者可以閱讀程式碼。同時,Python 以外的大多數語言都要求他們使用一套不同的符號與編譯器或直譯器進行通訊。使用兩套符號將使程式碼雜亂不堪,並將產生一個很大的風險 - 顯示給人眼的符號可能與顯示給計算機的符號不同步,從而產生另人難以理解的錯誤。而 Python 可以像使用者那樣讀取程式碼,從而消除了混亂並避免出現風險。

下面我們來看看程式碼操作。在作業系統提示符(而非 Python 直譯器提示符)下,鍵入
c:\> python readInitOra.py
{'UNDO_MANAGEMENT':'AUTO', 'COMPATIBLE': '9.2.0.0.0',
'DB_BLOCK_SIZE':'8192', 'FAST_START_MTTR_TARGET' : '300',
'SGA_MAX_SIZE':157286400, 'SHARED_POOL_SIZE': '50331648'}
如果您習慣將 readInitOra.py 中的所有行縮排幾個空格,則將使 Python 混淆併產生一個語法錯誤。縮排指示程式碼塊,因此在不指示程式碼塊的情況下請不要進行縮排。返回並確保不在塊中的每一行都從第 1 列開始。

您實際上需要在多個位置使用此程式碼,因此我們將其從簡單的指令碼改進為接受引數的函式定義。
def read(fileName):
initFile = open(fileName, 'r')
initParams = {}
rawTextLine = initFile.readline()
while rawTextLine:
param, val = rawTextLine.strip().split('=')
initParams[param] = val
rawTextLine = initFile.readline()
return initParams

巢狀

下面,您需要建立一個包含 init_default.ora 中的預設引數的相似字典。您完全可以將其讀取到全新的變數,但這裡我們將介紹物件如何可靠地巢狀在 Python 中。您將建立一個簡單的父目錄 initParams,並針對每個 init.ora 檔案在其中巢狀一個目錄。您還將匯入剛剛編寫的檔案,以便可以呼叫它的 read() 函式。建立一個名為 compareInitOra.py 的新文字檔案。
import readInitOra
initParams = {}                                     
# brackets denote a list that we can loop through
for fileName in ['init_orcl.ora', 'init_default.ora']:                          
initParams[fileName] = readInitOra.read(fileName)
print initParams

c:\> python compareInitOra.py
{'init_orcl.ora': 
{'UNDO_MANAGEMENT':'AUTO', 'COMPATIBLE': '9.2.0.0.0', 
'DB_BLOCK_SIZE':'8192', 'FAST_START_MTTR_TARGET': '300',
'SGA_MAX_SIZE':'157286400, 'SHARED_POOL_SIZE': '50331648'}
'init_default.ora': 
{'COMPATIBLE':'9.2.0.0.0', 'DB_BLOCK_SIZE': '4096',
'FAST_START_MTTR_TARGET':'300', 'SGA_MAX_SIZE': '100663296',
'SHARED_POOL_SIZE': '50331648'}}
這次的輸出包含一些空格,以幫助您檢視巢狀結構。您可以輕鬆地編寫 Python 程式碼以可靠地列印它,也可以使用 Python 的 pprint 模組,但由於我們是資料庫使用者,因此將該資料置入 Oracle 資料庫中。

在 Python 中使用 SQL

要訪問資料庫,Python 直譯器需要安裝資料庫模組。您有許多選擇,這些選擇均遵守標準化的 API 規範,並且曾經以程式設計方式使用過 ODBC 或 JDBC 的任何人均非常熟悉它們。您將使用 cx_Oracle,因為它比較易於安裝。只需下載一個與您的 Python 和 Oracle 資料庫版本匹配的 Windows 安裝程式。

安裝 cx_Oracle 後,返回 Python 命令列直譯器進行試用。由於 cx_Oracle 是一個獨立於核心 Python 語言的模組,因此在將其用於任何會話或指令碼之前必須匯入它。
>>> import cx_Oracle   
請記住要使用大寫字母!下面,我們將建立一個用於儲存結果的表。
>>> rcl = cx_Oracle.connect('scott/tiger@orcl')
>>> curs = orcl.cursor()
>>> sql = """CREATE TABLE INIT_PARAMS 
... ( fileName VARCHAR2(30),
...   param VARCHAR2(64),
...   value VARCHAR2(512) )"""
三個引號 (""") 是一個用於輸入包含換行符的方便語法。Python 直譯器將其提示符從 >>> 更改為 ... - 提醒您將繼續從上一行開始的輸入。
>>> curs.execute(sql)
>>> curs.close()
現在您的表已經準備就緒,下面我們將編寫 recordInitOra.py 來填充它。
import readInitOra, cx_Oracle
initParams = {}                                     
for fileName in ['init_orcl.ora', 'init_default.ora']:                          
initParams[fileName] = readInitOra.read(fileName)
rcl = cx_Oracle.connect('scott/tiger@orcl')
curs = orcl.cursor()
for fileName in initParams.keys():   
for param in initParams[fileName].keys():
value = initParams[fileName][param]
sql = """INSERT INTO INIT_PARAMS VALUES 
(:fileName, :param, :value)"""
bindVars = {'fileName':fileName, 
'param':param, 'value':value}
curs.execute(sql, bindVars)
curs.close()              
orcl.commit()        
以上就是全部程式碼了。注意,您這次在 SQL 字串中使用了繫結變數,並在一個單獨字典中為它們提供了值。使用繫結變數可以幫助您擺脫 SPCSP(防止錯誤使用共享池協會)的麻煩。

從查詢中獲取結果略微複雜一些。對遊標物件呼叫 execute() 後,可以使用 fetchone() 一次獲取一行,也可以使用 fetchall() 獲取所有行的列表。無論在哪種情況下,每一行均採用位元組組(即可以由數值索引訪問的有序值序列)的形式。例如,我們將編寫 compareInitOra.py 來列印與 V$PARAMETER 中的當前值衝突的 init_orcl.ora 引數:
import readInitOra, cx_Oracle
def readLiveParams():
liveParams = {}
rcl = cx_Oracle.connect('scott/tiger@orcl')
curs = orcl.cursor()
curs.execute('SELECT name, value FROM V$PARAMETER')
row = curs.fetchone()
while row:
(param, val) = (row[0], row[1])
liveParams[param.upper()] = val
row = curs.fetchone()
return liveParams

liveParams = readLiveParams()
fileName = 'init_orcl.ora'
fileParams = readInitOra.read(fileName)
for (param, val) in fileParams.items():
liveVal = liveParams.get(param)
if liveVal != val:
print """For %s, V$PARAMETER shows %s, 
but the file %s shows %s""" % \
(param, liveVal, fileName, val)

Python 的物件關係對映工具

您可能對物件關係對映 (ORM) 工具很感興趣,這些工具可以取代 SQL 的編寫併為程式設計人員提供一個物件導向的介面。Oracle TopLink 便是一個適用於 Java 的物件關係對映器的例子。用於 Python 的與 Oracle 相容的 ORM 工具有:

該指令碼引入了一些您還沒見過的技巧:
  • 對字典 fileParams 呼叫 items() 將返回一個(鍵, 值)對列表。可以通過在 for 語句中指定兩個迴圈變數來遍歷這些鍵值。
  • 呼叫 liveParams.get(param) 的工作方式類似於 liveParams[param],區別在於如果在 liveParams 中未找到引數,則將返回一個錯誤 - 非常類似於“ORA-01403:no data found in PL/SQL”訊息。而 liveParams.get(param) 將在 liveParams 中不存在引數的情況下返回 None。
  • Python 可以使用 % 運算子執行字串替換。與 C 的 printf 相似,%s 表示將在該點插入一個字串形式的值。這些值按順序從 % 之後的位元組組中提取。
  • 最後一行程式碼比您在沒有換行符的情況下進行鍵入執行時間更長,因此您使用了一個反斜線,從而打破了 Python 將換行符解釋為命令結尾這一通常的規則。

Web 釋出

最後,我們將通過 Python Web 應用程式公開此功能。首先,您需要選擇要使用的 Python Web 應用程式平臺,這可能要比實際編寫應用程式困難!由於它們非常易於用 Python 編寫,因此有大量平臺可供使用。最流行的平臺是 Zope;Zope 應用程式可以完全從易於使用者使用的網頁中建立和管理。或者,如果您對 Ruby on Rails 很感興趣,則可能要考慮 TurboGearsDjango - Python 自身的從上自下的應用程式構建器。TurboGears 也支援在應用程式中方便地包含 AJAX。現在,我們將選擇一個非常簡單的框架 CherryPy。請在此處下載它。

首先,對 compareInitOra.py 稍加修改(如下所示),建立一個 html() 函式,從而返回包含所需文字和標記的字串。然後,只需要編寫七行多程式碼即可將該函式公開為 Web 應用程式。

清單 3: compareInitOra

import readInitOra, cx_Oracle
def readLiveParams(instance):
liveParams = {}
rcl = cx_Oracle.connect('scott/tiger@' + instance)
curs = orcl.cursor()
curs.execute('SELECT name, value FROM V$PARAMETER')
row = curs.fetchone()
while row:
(param, val) = (row[0], row[1])
liveParams[param.upper()] = val
row = curs.fetchone()
return liveParams

fileName = 'init_orcl.ora'

def html(initOraFile, instance):
result = ""
fileParams = readInitOra.read(initOraFile)
liveParams = readLiveParams(instance)
for (param, val) in fileParams.items():
liveVal = liveParams.get(param)
if liveVal == val:
result += "%s
\n" % (val) else result += "%s (file) %s (live)\n" % (val, liveVal) return result import cherrypy class WebApp: def index(self, initOraFile='init.ora', instance='orcl'): return html(initOraFile, instance) index.exposed = True cherrypy.root = WebApp() cherrypy.server.start()
執行該指令碼時,它開始以 Web 伺服器的形式(預設情況下)在埠 8080 上工作。將瀏覽器指向 http://localhost:8080/?initOraFile=init_orcl 將提供您所需的 HTML 頁面。

結論

衷心希望 Python 的易用性、簡潔行和可讀行能夠引起您的興趣。您對 Python 的強大功能還不瞭解。它的功能包括簡潔的異常處理、單元測試、物件導向、實用程式設計、GUI 工具包、Web 框架、XML、Web 服務 - 程式設計人員幾乎可以執行任何操作。隨著工作變得更高階,您不必“過渡”到其他語言。

http://www.oracle.com/technology/global/cn/pub/articles/devlin-python-oracle.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1384/viewspace-432927/,如需轉載,請註明出處,否則將追究法律責任。

相關文章