當前位置:首頁 » 編程語言 » sqlalchemy前後端分離
擴展閱讀
webinf下怎麼引入js 2023-08-31 21:54:13
堡壘機怎麼打開web 2023-08-31 21:54:11

sqlalchemy前後端分離

發布時間: 2023-01-04 07:57:42

A. ...與 Python 作為後端服務的編程語言各有什麼優劣

Node.js相優於Python的地方:
快:這個快有兩方面,第一是V8引擎快,第二是非同步執行,Node.js
功能上是一個基於V8引擎的非同步網路和IO Library,和Python的Twisted很像,不同的是Node.js的event
loop是很底層的深入在語言中的,可以想像成整個文件在執行的時候就在一個很大的event loop里;
npm:npm可以說是用起來最順手的package management了,npm作為Node.js的官方package management,匯集了整個社區最集中的資源;不像Python經歷過easy_install和pip,還有2to3的問題;
Windows支持:Node.js有微軟的加持,Windows基本被視為一等公民來支持,libuv已經可以很好的做到統一跨平台的API;而Python雖然也對Windows有官方的支持,但是總感覺是二等公民,時不時出些問題。
Python優於Node.js的地方:

言:就單純從語言的角度來說,Python寫起來要比Javascript舒服很多;Javascript
設計本身有許多缺陷,畢竟當時設計的時候只是作為在瀏覽器中做一些簡單任務的script,所以代碼一旦龐大,維護還是有困難(不過Node.js的
mole很大的改善了這個問題),不過用Coffeescript可以很大的改善Javascript,幾乎可以和Python等同;

熟:成熟包括語言本身已經成熟,還有Framework和ecosystem也很龐大。Node.js的絕大多數framework都很新,有的API一
直在變,有的感覺已經不在維護,總之沒有一個像Django那種百足之蟲感覺的framework。Python的主流ORM
sqlalchemy也很成熟。
Python 和 Node.js 很難分高下的地方:

步Style:Node.js的非同步Style是CPS,也就是層層callback,基於event,和瀏覽器中的Javascript很像。CPS好
處是讓熟悉瀏覽器Javascript的人能很快上手,學習難度也不大。缺點是邏輯一復雜,就變得很難維護,基本上需要通過async.js這種
library,或者用promise。Python的非同步除了和Node.js很像的Twisted之外,也有基於coroutine的
gevent,coroutine讓非同步代碼維護起來更容易,不過學習曲線陡;
應用場景:如果是一個CRUD的app,
那麼想都不想直接是Python,Node.js本身不擅長CRUD的app(絕大多數Node.js都是直接裸在外面的,而不是有一個Nginx在前
面,否則websocket就不能用了,不過新版nginx開始支持websocket),代碼又不好維護,而Python的WSGI很適合,成熟的
stack也有很多。如果更偏向於real-time,比如一個chat room,那麼Node.js實現更容易。這兩個應用場景還是有差別的。
來源於知乎:https://www.hu.com/question/20961574

B. SQLAlchemy 中的 Session、sessionmaker、scoped_session

目錄


Session 其實 就是一個會話, 可以和資料庫打交道的一個會話

在一般的意義上, 會話建立與資料庫的所有對話,並為你在其生命周期中載入或關聯的所有對象表示一個「等待區」。他提供了一個入口點獲得查詢對象, 向資料庫發送查詢,使用會話對象的當前資料庫連接, 將結果行填充在對象中, 然後存儲在會話中, 在這種結構中稱為身份映射 – 這種數據結構維護了每一個副本的唯一, 這種唯一意味著一個對象只能有一個特殊的唯一主鍵。

會話以基本無狀態的形式開始,一旦發出查詢或其他對象被持久化,它就會從一個引擎申請連接資源,該引擎要麼與會話本身相關聯,要麼與正在操作的映射對象相關聯。此連接標識正在進行的事務, 在會話提交或回滾其掛起狀態之前,該事務一直有效。

會話中維護的所有變化的對象都會被跟蹤 - 在再次查詢資料庫或提交當前事務之前, 它將刷新對資料庫的所有更改, 這被稱為工作模式單元。

在使用會話時候,最重要的是要注意與它相關聯的對象是會話所持有的事務的代理對象 - 為了保持同步,有各種各樣的事件會導致對象重新訪問資料庫。可能從會話中分離對象並繼續使用他們,盡管這種做法有其局限性。但是通常來說,當你希望再次使用分離的對象時候,你會將他們與另一個會話重新關聯起來, 以便他們能夠恢復表示資料庫狀態的正常任務。

可能會將這里的session與http中的session搞混,需要注意的是,它有點用作緩存,因為它實現了 身份映射 模式,並存儲了鍵入其主鍵的對象。但是,它不執行任何類型的查詢緩存。 此外,默認情況下,Session使用弱引用存儲對象實例。這也違背了將Session用作緩存的目的。關於session強應用下次再討論。


1. session創建和管理資料庫連接的會話 2. model object 通過session對象訪問資料庫,並把訪問到的數據以 Identity Map 的方式,映射到Model object 中


1. session在剛被創建的時候,還沒有和任何model object 綁定,可認為是無狀態的 2. session 接受到query查詢語句, 執行的結果或保持或者關聯到session中 3. 任意數量的model object被創建,並綁定到session中,session會管理這些對象 4. 一旦session 裡面的objects 有變化,那可是要commit/rollback提交或者放棄changs


一般來說,session在需要訪問資料庫的時候創建,在session訪問資料庫的時候,准確來說,應該是「add/ update / delete 」資料庫的時候,會開啟 database transaction 。 假設沒有修改autocommit的默認值( False ), 那麼, database transaction 一直會保持,只有等到 session 發生rolled back、committed、或者closed的時候才結束,一般建議,當 database transaction 結束的時候,同時 close session ,以保證,每次發起請求,都會創建一個新的 session 特別是對web應用來說,發起一個請求,若請求使用到 Session 訪問資料庫,則創建 session ,處理完這個請求後,關閉 session



Session 是一個直接實例化的常規的Python 類。然而, 為了標准會會話的配置和獲取方式, sessionmaker 類通常用於創建頂級會話配置, 然後可以在整個應用程序中使用它, 就不需要重復配置參數。


下面是sessionmaker 的使用方式


在上面,該 sessionmaker()創建了一個工廠類,在創建這個工廠類時我們配置了參數綁定了引擎。將其賦值給Session。每次實例化Session都會創建一個綁定了引擎的Session。 這樣這個session在訪問資料庫時都會通過這個綁定好的引擎來獲取連接資源當你編寫應用程序時, 請將sessionmaker 工廠放在全局級別,視作應用程序配置的一部分。例如:應用程序包中有三個.py文件,您可以將該sessionmaker行放在__init__.py文件中; 在其他模塊「from mypackage import Session」。這樣,所有的Session()的配置都由該配置中心控制。

直接只用 create_engine 時,就會創建一個帶連接池的引擎:


創建一個session,連接池會分配一個connection。當session在使用後顯示地調用 session.close(),也不能把這個連接關閉,而是由由QueuePool連接池管理並復用連接。

確保 session 在使用完成後用 session.close、session.commit 或 session.rollback 把連接還回 pool,這是一個必須在意的習慣。

關於SQLAlchemy 資料庫連接池:

session 和 connection 不是相同的東西, session 使用連接來操作資料庫,一旦任務完成 session 會將資料庫 connection 交還給 pool。 在使用 create_engine 創建引擎時,如果默認不指定連接池設置的話,一般情況下,SQLAlchemy 會使用一個 QueuePool 綁定在新創建的引擎上。並附上合適的連接池參數

create_engine() 函數和連接池相關的參數有:

SQLAlchemy不使用連接池:在創建引擎時指定參數 poolclass=NullPool 即禁用了SQLAlchemy提供的資料庫連接池。SQLAlchemy 就會在執行 session.close() 後立刻斷開資料庫連接。當然,如果沒有被調用 session.close(),則資料庫連接不會被斷開,直到程序終止。



關於 SQLAlchemy 的 engine ,這里有一篇文章寫的很好: http://sunnyingit.github.io/book/section_python/SQLalchemy-engine.html

session不是線程安全的,在多線程的環境中,默認情況下,多個線程將會共享同一個session。試想一下,假設A線程正在使用session處理資料庫,B線程已經執行完成,把session給close了,那麼此時A在使用session就會報錯,怎麼避免這個問題?

1 . 可以考慮在這些線程之間共享Session及其對象。但是應用程序需要確保實現正確的鎖定方案,以便多個線程不會同時訪問Session或其狀態。SQLAlchemy 中的 scoped_session 就可以證線程安全,下面會有討論。 2 . 為每個並發線程維護一個會話,而不是將對象從一個Session復制到另一個Session,通常使用Session.merge()方法將對象的狀態復制到一個不同Session的新的本地對象中。


上面簡單介紹了sessionmaker的作用,下面開始探討 scoped_session 對創建 Session 的影響。現在先探討單線程情況。



結論:

通過 sessionmaker 工廠創建了兩個 Session ,而且可以看到 s1 s2 是兩個不同的 Session 。 在 s1 添加 person 後,繼續使用 s2 添加 person 報錯. 說 person 這個對象 已經和 另一個 Session 關聯一起來了, 所以再次關聯另一個 Session 就會報錯。


即在上面代碼的 s1.add(person) 之後, s1.commit() ,然後再 s2.add(persion)這里就沒帖代碼了。

結論:

即使在 s1 提交之後, s2 再去添加 person 也會發生錯誤,但 s1 的提交是成功了的,數據 person 已經存放在資料庫了。 當 s1 添加 person 並提交,然後關閉 s1 , s2 再去添加並提交 person 資料庫,這不會報錯,但是資料庫也不會出現兩條 person 數據。



結論:

s1 關閉之後, s2 再去添加提交同一個對象,不會報錯,但是資料庫值有一條 person 數據。



結論:

當然, s1 , s2 添加提交不同的對象,不會出錯。在資料庫成功新增數據。



以上說明:

一個對象一旦被一個 Session 添加,除非關閉這個 Session ,不然其他的 Session 無法添加這個對象。 一個 Session 添加並提交一個對象,然後關閉該 Session ,其他的 Session 可以添加並提交這個對象,但是資料庫並不會有這條數據。



結論:

可以看到,通過 scoped_session再去創建 Session ,返回的是同一個 Session 。 scoped_session類似單例模式,當我們調用使用的時候,會先在Registry里找找之前是否已經創建Session,未創建則創建 Session ,已創建則直接返回。


這里探討在多線程下使用 scoped_session 與不使用 scoped_session 的情況

當不使用 scoped_session 時,也分兩種情況,是否創建全局性 Session


結論:

每個線程下的 Session 都是不同的 Session 資料庫成功新增了線程3提交的數據,其他的線程中的數據並沒有提交到資料庫中去。




結論:

全部線程下的 Session 都時同一個 Session 每個線程下的數據都被提交到了資料庫




結論:

每個線程下的 Session 都不相同 只有線程3下的數據被提交到了資料庫






結論:

每個線程下的 Session 是同一個 Session 每個線程下的數據都沒提交到了資料庫


以上說明:

在同一個線程中,有 scoped_session 的時候,返回的是同一個 Session 對象。 在多線程下,即使通過 scoped_session 創建Session,每個線程下的 Session 都是不一樣的,每個線程都有一個屬於自己的 Session 對象,這個對象只在本線程下共享。 scoped_session 只有在單線程下才能發揮其作用。在多線程下顯得沒有什麼作用。

C. 如何在Ubuntu14.04的Docker容器中運行OpenVPN

本文將指導你如何搭建私有docker registry,並確保其安全性。在本教程的最後,你將親身體驗上傳自製的docker鏡像到私有registry,並在不同的機器上安全的將該其拉去下來。
本教程不包含如何容器化應用服務的內容,而是旨在引導你去創建registry來存儲你要部署的服務資源。如果你想要docker的入門教程,也許這兒可以幫到你。
基於Ubuntu14.04系統的單registry和單客戶機模式,本教程已經通過測試,也許在其他基於debian的發布上仍可以運行。
docker概念
如果在此之前你尚未接觸過docker,這需要花費你幾分鍾的時間來熟悉docker的關鍵概念。如果你對docker已得心應手,只是想了解如何構建私有registry的話,那麼,你可以直接進入下一小節。
對於一個新手該如何使用docker,不妨試試這里優秀的docker筆記。
docker的核心就是要對應用以及應用的依賴與操作系統分離。為了達到上述目的,docker採用容器與鏡像機制。一個docker鏡像基本上是一個文件系統的模板。當你通過docker run命令運行一個docker鏡像時,該文件系統的一個實例即被激活,並且運行在系統內的docker容器之中。默認的,該容器無法觸及原始鏡像以及docker本身所運行在的主機的文件系統。這是一個獨立的環境。
對容器所做的任何改動都將保存在容器本身之內,並不會影響原始的鏡像。如果想保留這些改動,那麼,可以通過docker commit命令將容器保存為鏡像。這意味你可以通過原有的容器來衍生出新的容器,而不會對原始容器(或鏡像)造成任何影響。如果你熟悉git,那麼你會覺得該流程十分眼熟:從任意的容器上去創建新的分支(這里,分支的意思即為docker中的鏡像)。運行鏡像類似於執行git checkout操作。
更進一步形容,運行私有docker registry,就好比為docker鏡像運行私有git倉庫。
第一步——安裝必要的軟體
在docker registry伺服器上,應當創建一個擁有sudo許可權的用戶(如果可以,在客戶機上也如此)。
docker registry軟體是一款python應用,因此為了使其能夠運行起來,需要安裝python開發環境以及必要的庫:
sudo apt-get updatesudo apt-get -y install build-essential python-dev libevent-dev python-pip liblzma-dev

第二步——安裝並配置docker registry
為了安裝最新的穩定的docker registry發行版(作者撰文時為0.7.3,譯者譯時為0.9.1),我們將使用python包管理工具pip:
sudo pip install docker-registry

docker-registry需要配置文件。
默認地,pip將該配置文件放置在相當偏僻的地方,因系統中python的安裝位置而異。因此,為了找到該路徑,我們將嘗試運行registry,以查看相關的輸出:
gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application

由於配置文件不在正確的位置,上述嘗試將會以失敗而告終,並輸出一條包含FileNotFoundError錯誤消息,如下所示[在某些版本中,無下述信息輸出,譯者注]:
FileNotFoundError: Heads-up! File is missing: /usr/local/lib/python2.7/dist-packages/docker_registry/lib/../../config/config.yml

registry在同樣的路徑上包含一個示例配置文件,該文件名為config_sample.yml,因此,我們可以通過上述給出的路徑名來定位示例配置文件。
從錯誤消息中復制路徑信息(此時為/usr/local/lib/python2.7/dist-packages/docker_registry/lib/../../config/config.yml),然後,將config.yml部分去除,這樣我們可以切換到該路徑下。
cd /usr/local/lib/python2.7/dist-packages/docker_registry/lib/../../config/

將config_sample.yml文件的內容復制到config.yml中:
sudo cp config_sample.yml config.yml

默認情況下,docker的數據文件存放在/tmp文件夾下,但是在許多的類Linux系統中,系統重啟時該文件夾被清空,這並不是我們所希望的。那麼,我們創建一個永久性的文件夾來存儲數據:
sudo mkdir /var/docker-registry

好的,下面我們配置config.yml文件,將對文件夾/tmp的引用更改為/var/docker-registry。首先,首先找到以sqlalchemy_index_database為首的靠近文件首部的一行:
sqlalchemy_index_database:_env:SQLALCHEMY_INDEX_DATABASE:sqlite:////tmp/docker-registry.db

將其更改指向/var/docker-registry,如下所示:
sqlalchemy_index_database:_env:SQLALCHEMY_INDEX_DATABASE:sqlite:////var/docker-registry/docker-registry.db

向下一點,到local:部分,重復上述操作,更改如下內容:
local: &localstorage: localstorage_path: _env:STORAGE_PATH:/tmp/registry

為:
local: &localstorage: localstorage_path: _env:STORAGE_PATH:/var/docker-registry/registry

樣例配置文件中的其他默認值均無需修改。一目十行即可。然而,如果你想要做一些復雜的配置,諸如採用擴展存儲裝置來存儲docker數據,那麼該文件正具有此功能。當然,這已超出本教程的范圍,你可以查看docker-registry文檔以獲取更多的幫助。
既然配置文件已置於正確的位置,那麼再一次嘗試來測試docker registry伺服器:
gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application

你會看到如下的輸出:
2014-07-27 07:12:24 [29344] [INFO] Starting gunicorn 18.02014-07-27 07:12:24 [29344] [INFO] Listening at: http://0.0.0.0:5000 (29344)2014-07-27 07:12:24 [29344] [INFO] Using worker: gevent2014-07-27 07:12:24 [29349] [INFO] Booting worker with pid: 293492014-07-27 07:12:24,807 DEBUG: Will return docker-registry.drivers.file.Storage

棒極了!現在我們已經擁有一個運行著的docker registry。下面執行Ctrl+C終止該程序。
到目前為止,docker registry並不是那麼有用。它並不會自行啟動除非我們執行上述gunicorn命令。另外,docker registry不沒有引入任何的內置的認證機制,因此,其當前狀態下是不安全並且對外部完全開放的。
第三步——以服務的形式啟動docker registry
通過創建Upstart腳本,設置docker registry在系統的啟動程序中開始運行。
首先,創建日誌文件目錄:
sudo mkdir -p /var/log/docker-registry

然後,用一款你拿手的文本編輯器來創建Upstart腳本:
sudo nano /etc/init/docker-registry.conf

將如下內容寫入上述腳本中:
description "Docker Registry"start on runlevel [2345]stop on runlevel [016]respawnrespawn limit 10 5script exec gunicorn --access-logfile /var/log/docker-registry/access.log --error-logfile /var/log/docker-registry/server.log -k gevent --max-requests 100 --graceful-timeout 3600 -t 3600 -b localhost:5000 -w 8 docker_registry.wsgi:applicationend script

更多關於Upstart腳本的內容,請閱讀該教程。
此時,執行如下命令:
sudo service docker-registry start

將看到下面的輸出:
docker-registry start/running, process 25303

當然,你也可以通過運行下面的命令查看server.log日誌文件,驗證docker-registry服務是否正在運行:
tail /var/log/docker-registry/server.log

如果一切正常的話,你會看到像之前執行gunicorn命令時輸出的文本信息。
既然docker-registry伺服器已在後台運行,那麼下面我們來配置Nginx,以使registry更加安全。

D. 學會Python之後更適合做哪方面的工作

下面我們來說一下Python具體的工作崗位以及其崗位要求:

Python後台開發工程師:主要是負責搭建和改進平台產品的後台,並與前端開發工程師相互配合完成整體產品的開發工作。要求工程師具備至少一門Python Web開發框架(Tornado、Django、Flask等),了解並熟悉MySQL/Redis/MongoDB。還要熟悉分布式、微服務、高性能Web服務的開發。

Python爬蟲開發工程師:爬蟲開發工程師並非我們預想的那樣,只是負責為公司爬取相對應的數據內容。爬蟲開發工程師主要負責對傳統網頁、SNS及微博等各種網站信息高效採集與正確解析,然後對用戶數據進行整理分析,參與建模的構建,總結分析不同網站、網頁的結構特點及規律,負責爬蟲架構設計和研發,參與爬蟲核心演算法和策略優化研究。需要開發工程師熟悉了解robot規則、selenium、mitmproxy、pymouse等內容。當然作為爬蟲開發工程師一定要有一定的職業情況,所有工作都需要在合理合法的需求下進行。

Python全棧開發工程師:是指可以使用Python相關工具,獨立完成網站開發,稱之為全棧開發。全棧開發工程師需要掌握非常多的技能,包括:項目管理、前後端開發、界面設計、產品設計、資料庫開發、多端產品等等。

自動化運維工程師:是在基本的運維工作的基礎上,實現運維工作的自動化,並且對自動化程序進行優化提升。需要從業者在掌握基本的運營工作的前提下,掌握Python中的IPy、Ansible、Saltstack等常用模塊。

自動化測試工程師:首要要完成測試的基本工作,包括測試計劃、測試用例、黑盒測試、性能測試等等。其次要是完成產品的自動化測試的部署以及維護工作,並且不斷嘗試新的方法,新的工具,以提高測試的效率。需要掌握Python以及selenium相關的技能。

數據分析師:指的是不同行業中,專門從事行業數據搜集、整理、分析,並依據數據做出行業研究、評估和預測的專業人員。需要從業者了解行業相關業務知識、相關管理工作、掌握足夠的數據分析方法、了解數據分析工具使用、能夠完成數據分析建模等,工作內容偏重於分析,同樣也要掌握一定的開發能力,例如R語言和Python語言。

數據分析開發工程師:根據數據分析師的建模完成數據相關的開發工作,搭建倉庫、完成數據存儲、數據處理、計算處理以及報表開發等工作。需要從業者熟練應用資料庫、數據建模開發、Python相關數據科學知識等技能。

人工智慧開發工程師:根據企業人工智慧AI相關的開發需求,完成相應產品或者功能開發。需要從業者掌握充分的數據理論基礎、Python開發基礎、機器學習理論與實踐、深度學習理論與實踐、自然語言處理等一系列相關的開發技能。

Python游戲開發工程師:主要負責游戲服務端的邏輯開發。需要從業者掌握Python各種性能優化方法、soket網路編程知識、運維相關基礎知識、以及Python相關的游戲開發庫與框架。此外還可以將Python開發相關工作按照崗位晉升分為初級Python開發工程師、中級Python開發工程師、高級Python開發工程師、項目經理、架構師、CTO等。主要是根據從業者工作年限,在某個就業方向的工作經驗以及解決問題的能力進行定位。

無論是哪個就業方向,扎實的學習好Python相關知識是重中之重,在互聯網行業,無論是大廠還是創業創新的公司,招聘人才的最核心要求是技術能力,只有自己的能力和崗位匹配的時候,才能獲得更多的工作機會。

E. 後端編程Python3-資料庫編程

對大多數軟體開發者而言,術語資料庫通常是指RDBMS(關系資料庫管理系統), 這些系統使用表格(類似於電子表格的網格),其中行表示記錄,列表示記錄的欄位。表格及其中存放的數據是使用SQL (結構化査詢語言)編寫的語句來創建並操縱的。Python提供了用於操縱SQL資料庫的API(應用程序介面),通常與作為標準的SQLite 3資料庫一起發布。

另一種資料庫是DBM (資料庫管理器),其中存放任意數量的鍵-值項。Python 的標准庫提供了幾種DBM的介面,包括某些特定於UNIX平台的。DBM的工作方式 與Python中的字典類似,區別在於DBM通常存放於磁碟上而不是內存中,並且其鍵與值總是bytes對象,並可能受到長度限制。本章第一節中講解的shelve模塊提供了方便的DBM介面,允許我們使用字元串作為鍵,使用任意(picklable)對象作為值。

如果可用的 DBM 與 SQLite 資料庫不夠充分,Python Package Index, pypi.python.org/pypi中提供了大量資料庫相關的包,包括bsddb DBM ("Berkeley DB"),對象-關系映射器,比如SQLAlchemy (www.sqlalchemy.org),以及流行的客戶端/伺服器數據的介面,比如 DB2、Informix、Ingres、MySQL、ODBC 以及 PostgreSQL。

本章中,我們將實現某程序的兩個版本,該程序用於維護一個DVD列表,並追蹤每個DVD的標題、發行年份、時間長度以及發行者。該程序的第一版使用DBM (通過shelve模塊)存放其數據,第二版則使用SQLite資料庫。兩個程序都可以載入與保存簡單的XML格式,這使得從某個程序導出DVD數據並將其導入到其他程序成為可能。與DBM版相比,基於SQL的程序提供了更多一些的功能,並且其數據設計也稍干凈一些。

12.1 DBM資料庫

shelve模塊為DBM提供了一個wrapper,藉助於此,我們在與DBM交互時,可以將其看做一個字典,這里是假定我們只使用字元串鍵與picklable值,實際處理時, shelve模塊會將鍵與值轉換為bytes對象(或者反過來)。

由於shelve模塊使用的是底層的DBM,因此,如果其他計算機上沒有同樣的DBM,那麼在某台計算機上保存的DBM文件在其他機器上無法讀取是可能的。為解決這一問題,常見的解決方案是對那些必須在機器之間可傳輸的文件提供XML導入與導出功能,這也是我們在本節的DVD程序dvds-dbm.py中所做的。

對鍵,我們使用DVD的標題;對值,則使用元組,其中存放發行者、發行年份以及時間。藉助於shelve模塊,我們不需要進行任何數據轉換,並可以把DBM對象當做一個字典進行處理。

程序在結構上類似於我們前面看到的那種菜單驅動型的程序,因此,這里主要展示的是與DBM程序設計相關的那部分。下面給出的是程序main()函數中的一部分, 忽略了其中菜單處理的部分代碼。

db = None

try:

db = shelve.open(filename, protocol=pickle.HIGHEST_PROTOCOL)

finally:

if db is not None:

db.dose()

這里我們已打開(如果不存在就創建)指定的DBM文件,以便於對其進行讀寫操作。每一項的值使用指定的pickle協議保存為一個pickle,現有的項可以被讀取, 即便是使用更底層的協議保存的,因為Python可以計算出用於讀取pickle的正確協議。最後,DBM被關閉——其作用是清除DBM的內部緩存,並確保磁碟文件可以反映出已作的任何改變,此外,文件也需要關閉。

該程序提供了用於添加、編輯、列出、移除、導入、導出DVD數據的相應選項。除添加外,我們將忽略大部分用戶介面代碼,同樣是因為已經在其他上下文中進行了展示。

def add_dvd(db):

title = Console.get_string("Title", "title")

if not title:

return

director = Console.get_string("Director", "director")

if not director:

return

year = Console.get_integer("Year", "year",minimum=1896,

maximum=datetime,date.today().year)

ration = Console.get_integer("Duration (minutes)", "minutes「, minimum=0, maximum=60*48)

db[title] = (director, year, ration)

db.sync()

像程序菜單調用的所有函數一樣,這一函數也以DBM對象(db)作為其唯一參數。該函數的大部分工作都是獲取DVD的詳細資料,在倒數第二行,我們將鍵-值項存儲在DBM文件中,DVD的標題作為鍵,發行者、年份以及時間(由shelve模塊pickled在一起)作為值。

為與Python通常的一致性同步,DBM提供了與字典一樣的API,因此,除了 shelve.open() 函數(前面已展示)與shelve.Shelf.sync()方法(該方法用於清除shelve的內部緩存,並對磁碟上文件的數據與所做的改變進行同步——這里就是添加一個新項),我們不需要學習任何新語法。

def edit_dvd(db):

old_title = find_dvd(db, "edit")

if old_title is None:

return

title = Console.get.string("Title", "title", old_title)

if not title:

return

director, year, ration = db[old_title]

...

db[title]= (director, year, ration)

if title != old_title:

del db[old_title]

db.sync()

為對某個DVD進行編輯,用戶必須首先選擇要操作的DVD,也就是獲取DVD 的標題,因為標題用作鍵,值則用於存放其他相關數據。由於必要的功能在其他場合 (比如移除DVD)也需要使用,因此我們將其實現在一個單獨的find_dvd()函數中,稍後將査看該函數。如果找到了該DVD,我們就獲取用戶所做的改變,並使用現有值作為默認值,以便提高交互的速度。(對於這一函數,我們忽略了大部分用戶介面代碼, 因為其與添加DVD時幾乎是相同的。)最後,我們保存數據,就像添加時所做的一樣。如果標題未作改變,就重寫相關聯的值;如果標題已改變,就創建一個新的鍵-值對, 並且需要刪除原始項。

def find_dvd(db, message):

message = "(Start of) title to " + message

while True:

matches =[]

start = Console.get_string(message, "title")

if not start:

return None

for title in db:

if title.lower().startswith(start.lower()):

matches.append(title)

if len(matches) == 0:

print("There are no dvds starting with", start)

continue

elif len(matches) == 1:

return matches[0]

elif len(matches) > DISPLAY_LIMIT:

print("Too many dvds start with {0}; try entering more of the title".format(start)

continue

else:

matches = sorted(matches, key=str.lower)

for i, match in enumerate(matches):

print("{0}: {1}".format(i+1, match))

which = Console.get_integer("Number (or 0 to cancel)",

"number", minimum=1, maximum=len(matches))

return matches[which - 1] if which != 0 else None

為盡可能快而容易地發現某個DVD,我們需要用戶只輸入其標題的一個或頭幾個字元。在具備了標題的起始字元後,我們在DBM中迭代並創建一個匹配列表。如果只有一個匹配項,就返回該項;如果有幾個匹配項(但少於DISPLAY_LIMIT, 一個在程序中其他地方設置的整數),就以大小寫不敏感的順序展示所有這些匹配項,並為每一項設置一個編號,以便用戶可以只輸入編號就可以選擇某個標題。(Console.get_integer()函數可以接受0,即便最小值大於0,以便0可以用作一個刪除值。通過使用參數allow_zero=False, 可以禁止這種行為。我們不能使用Enter鍵,也就是說,沒有什麼意味著取消,因為什麼也不輸入意味著接受默認值。)

def list_dvds(db):

start =」"

if len(db)> DISPLAY.LIMIT:

start = Console.get_string(「List those starting with [Enter=all]」, "start」)

print()

for title in sorted(db, key=str.lower):

if not start or title.Iower().startswith(start.lower()):

director, year, ration = db[title]

print("{title} ({year}) {ration} minute{0}, by "

"{director}".format(Util.s(ration),**locals()))

列出所有DVD (或者那些標題以某個子字元串引導)就是對DBM的所有項進行迭代。

Util.s()函數就是簡單的s = lambda x: "" if x == 1 else "s",因此,如果時間長度不是1分鍾,就返回"s"。

def remove_dvd(db):

title = find_dvd(db, "remove")

if title is None:

return

ans = Console.get_bool("Remove {0}?".format(title), "no")

if ans:

del db[title]

db.sync()

要移除一個DVD,首先需要找到用戶要移除的DVD,並請求確認,獲取後從DBM中刪除該項即可。

到這里,我們展示了如何使用shelve模塊打開(或創建)一個DBM文件,以及如何向其中添加項、編輯項、對其項進行迭代以及移除某個項。

遺憾的是,在我們的數據設計中存在一個瑕疵。發行者名稱是重復的,這很容易導致不一致性,比如,發行者Danny DeVito可能被輸入為"Danny De Vito",用於 一個電影;也可以輸入為「Danny deVito",用於另一個。為解決這一問題,可以使用兩個DBM文件,主DVD文件使用標題鍵與(年份,時間長度,發行者ID)值; 發行者文件使用發行者ID (整數)鍵與發行者名稱值。下一節展示的SQL資料庫 版程序將避免這一瑕疵,這是通過使用兩個表格實現的,一個用於DVD,另一個用於發行者。

12.2 SQL資料庫

大多數流行的SQL資料庫的介面在第三方模塊中是可用的,Python帶有sqlite3 模塊(以及SQLite 3資料庫),因此,在Python中,可以直接開始資料庫程序設計。SQLite是一個輕量級的SQL資料庫,缺少很多諸如PostgreSQL這種資料庫的功能, 但非常便於構造原型系統,並且在很多情況下也是夠用的。

為使後台資料庫之間的切換盡可能容易,PEP 249 (Python Database API Specification v2.0)提供了稱為DB-API 2.0的API規范。資料庫介面應該遵循這一規范,比如sqlite3模塊就遵循這一規范,但不是所有第三方模塊都遵循。API規范中指定了兩種主要的對象,即連接對象與游標對象。表12-1與表12-2中分別列出了這兩種對象必須支持的API。在sqlite3模塊中,除DB-API 2.0規范必需的之外,其連接對象與游標對象都提供了很多附加的屬性與方法。

DVD程序的SQL版本為dvds.sql.py,該程序將發行者與DVD數據分開存儲,以 避免重復,並提供一個新菜單,以供用戶列出發行者。該程序使用的兩個表格在圖12-1

def connect(filename):

create= not os.path.exists(filename)

db = sqlite3.connect(filename)

if create:

cursor = db.cursor()

cursor.execute("CREATE TABLE directors ("

"id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, "

"name TEXT UNIQUE NOT NULL)")

cursor.execute("CREATE TABLE dvds ("

"id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, "

"title TEXT NOT NULL, "

"year INTEGER NOT NULL,"

"ration INTEGER NOT NULL, "

"director_id INTEGER NOT NULL, 」

"FOREIGN KEY (director_id) REFERENCES directors)")

db.commit()

return db

sqlite3.connect()函數會返回一個資料庫對象,並打開其指定的資料庫文件。如果該文件不存在,就創建一個空的資料庫文件。鑒於此,在調用sqlite3.connect()之前,我們要注意資料庫是否是准備從頭開始創建,如果是,就必須創建該程序要使用的表格。所有査詢都是通過一個資料庫游標完成的,可以從資料庫對象的cursor()方法獲取。

注意,兩個表格都是使用一個ID欄位創建的,ID欄位有一個AUTOINCREMENT 約束——這意味著SQLite會自動為ID欄位賦予唯一性的數值,因此,在插入新記錄時,我們可以將這些欄位留給SQLite處理。

SQLite支持有限的數據類型——實際上就是布爾型、數值型與字元串——但使用數據'『適配器」可以對其進行擴展,或者是擴展到預定義的數據類型(比如那些用於日期與datetimes的類型),或者是用於表示任意數據類型的自定義類型。DVD程序並不需要這一功能,如果需要,sqlite3模塊的文檔提供了很多詳細解釋。我們使用的外部鍵語法可能與用於其他資料庫的語法不同,並且在任何情況下,只是記錄我們的意圖,因為SQLite不像很多其他資料庫那樣需要強制關系完整性,sqlite3另一點與眾不同的地方在於其默認行為是支持隱式的事務處理,因此,沒有提供顯式的「開始事務」 方法。

def add_dvd(db):

title = Console.get_string("Title", "title")

if not title:

return

director = Console.get_string("Director", "director")

if not director:

return

year = Console.get_integer("Year", "year」, minimum=1896,

maximum=datetime.date.today().year)

ration = Console.get_integer("Duration (minutes)", "minutes",

minimum=0,maximum=60*48)

director_id = get_and_set_director(db, director)

cursor = db.cursor()

cursor.execute("INSERT INTO dvds 」

"(title, year, ration, director_id)"

"VALUES (?, ?, ?, ?)",

(title, year, ration, director_id))

db.commit()

這一函數的開始代碼與dvds-dbm.py程序中的對應函數一樣,但在完成數據的收集後,與原來的函數有很大的差別。用戶輸入的發行者可能在也可能不在directors表格中,因此,我們有一個get_and_set_director()函數,在資料庫中尚無某個發行者時, 該函數就將其插入到其中,無論哪種情況都返回就緒的發行者ID,以便在需要的時候插入到dvds表。在所有數據都可用後,我們執行一條SQL INSERT語句。我們不需要指定記錄ID,因為SQLite會自動為我們提供。

在査詢中,我們使用問號(?)作為佔位符,每個?都由包含SQL語句的字元串後面的序列中的值替代。命名的佔位符也可以使用,後面在編輯記錄時我們將看到。盡管避免使用佔位符(而只是簡單地使用嵌入到其中的數據來格式化SQL字元串)也是可能的,我們建議總是使用佔位符,並將數據項正確編碼與轉義的工作留給資料庫模塊來完成。使用佔位符的另一個好處是可以提高安全性,因為這可以防止任意的SQL 被惡意地插入到一個査詢中。

def get_and_set_director(db, director):

director_id = get_director_id(db, director)

if directorjd is not None:

return director_id

cursor = db.cursor()

cursor.execute("lNSERT INTO directors (name) VALUES (?)」,(director,))

db.commit()

return get_director_id(db, director)

這一函數返回給定發行者的ID,並在必要的時候插入新的發行者記錄。如果某個記錄被插入,我們首先嘗試使用get_director_id()函數取回其ID。

def get_director_id(db, director):

cursor = db.cursor()

cursor.execute("SELECT id FROM directors WHERE name=?",(director,))

fields = cursor.fetchone()

return fields[0] if fields is not None else None

get_director_id()函數返回給定發行者的ID,如果資料庫中沒有指定的發行者,就返回None。我們使用fetchone()方法,因為或者有一個匹配的記錄,或者沒有。(我們知道,不會有重復的發行者,因為directors表格的名稱欄位有一個UNIQUE約束,在任何情況下,在添加一個新的發行者之前,我們總是先檢査其是否存在。)這種取回方法總是返回一個欄位序列(如果沒有更多的記錄,就返回None)。即便如此,這里我們只是請求返回一個單獨的欄位。

def edit_dvd(db):

title, identity = find_dvd(db, "edit")

if title is None:

return

title = Console.get_string("Title","title", title)

if not title:

return

cursor = db.cursor()

cursor.execute("SELECT dvds.year, dvds.ration, directors.name"

「FROM dvds, directors "

"WHERE dvds.director_id = directors.id AND "

"dvds.id=:id", dict(id=identity))

year, ration, director = cursor.fetchone()

director = Console.get_string("Director", "director", director)

if not director:

return

year = Console,get_integer("Year","year", year, 1896,datetime.date.today().year)

ration = Console.get_integer("Duration (minutes)", "minutes",

ration, minimum=0, maximum=60*48)

director_id = get_and_set_director(db, director)

cursor.execute("UPDATE dvds SET title=:title, year=:year,"

"ration=:ration, director_id=:directorjd "

"WHERE id=:identity", locals())

db.commit()

要編輯DVD記錄,我們必須首先找到用戶需要操縱的記錄。如果找到了某個記錄,我們就給用戶修改其標題的機會,之後取回該記錄的其他欄位,以便將現有值作為默認值,將用戶的輸入工作最小化,用戶只需要按Enter鍵就可以接受默認值。這里,我們使用了命名的佔位符(形式為:name),並且必須使用映射來提供相應的值。對SELECT語句,我們使用一個新創建的字典;對UPDATE語句,我們使用的是由 locals()返回的字典。

我們可以同時為這兩個語句都使用新字典,這種情況下,對UPDATE語句,我們可以傳遞 dict(title=title, year=year, ration=ration, director_id=director_id, id=identity)),而非 locals()。

在具備所有欄位並且用戶已經輸入了需要做的改變之後,我們取回相應的發行者ID (如果必要就插入新的發行者記錄),之後使用新數據對資料庫進行更新。我們採用了一種簡化的方法,對記錄的所有欄位進行更新,而不僅僅是那些做了修改的欄位。

在使用DBM文件時,DVD標題被用作鍵,因此,如果標題進行了修改,我們就需要創建一個新的鍵-值項,並刪除原始項。不過,這里每個DVD記錄都有一個唯一性的ID,該ID是記錄初次插入時創建的,因此,我們只需要改變任何其他欄位的值, 而不需要其他操作。

def find_dvd(db, message):

message = "(Start of) title to " + message

cursor = db.cursor()

while True: .

start = Console.get_stnng(message, "title")

if not start:

return (None, None)

cursor.execute("SELECT title, id FROM dvds "

"WHERE title LIKE ? ORDER BY title」,

(start +"%",))

records = cursor.fetchall()

if len(records) == 0:

print("There are no dvds starting with", start)

continue

elif len(records) == 1:

return records[0]

elif len(records) > DISPLAY_LIMIT:

print("Too many dvds ({0}) start with {1}; try entering "

"more of the title".format(len(records),start))

continue

else:

for i, record in enumerate(records):

print("{0}:{1}".format(i + 1, record[0]))

which = Console.get_integer("Number (or 0 to cancel)",

"number", minimum=1, maximum=len(records))

return records[which -1] if which != 0 else (None, None)

這一函數的功能與dvdsdbm.py程序中的find_dvd()函數相同,並返回一個二元組 (DVD標題,DVD ID)或(None, None),具體依賴於是否找到了某個記錄。這里並不需要在所有數據上進行迭代,而是使用SQL通配符(%),因此只取回相關的記錄。

由於我們希望匹配的記錄數較小,因此我們一次性將其都取回到序列的序列中。如果有不止一個匹配的記錄,但數量上又少到可以顯示,我們就列印記錄,並將每條記錄附帶一個數字編號,以便用戶可以選擇需要的記錄,其方式與在dvds-dbm.py程序中所做的類似:

def list_dvds(db):

cursor = db.cursor()

sql = ("SELECT dvds.title, dvds.year, dvds.ration, "

"directors.name FROM dvds, directors "

"WHERE dvds.director_id = directors.id")

start = None

if dvd_count(db) > DISPLAY_LIMIT:

start = Console.get_string("List those starting with [Enter=all]", "start")

sql += " AND dvds.title LIKE ?"

sql += 」 ORDER BY dvds.title"

print()

if start is None:

cursor.execute(sql)

else:

cursor.execute(sql, (start +"%",))

for record in cursor:

print("{0[0]} ({0[1]}) {0[2]} minutes, by {0[3]}".format(record))

要列出每個DVD的詳細資料,我們執行一個SELECT査詢。該査詢連接兩個表,如果記錄(由dvd_count()函數返回)數量超過了顯示限制值,就將第2個元素添加到WHERE 分支,之後執行該査詢,並在結果上進行迭代。每個記錄都是一個序列,其欄位是與 SELECT査詢相匹配的。

def dvd_count(db):

cursor = db.cursor()

cursor.execute("SELECT COUNT(*) FROM dvds")

return cursor.fetchone()[0]

我們將這幾行代碼放置在一個單獨的函數中,因為我們在幾個不同的函數中都需要使用這幾行代碼。

我們忽略了 list_directors()函數的代碼,因為該函數在結構上與list_dvds()函數非常類似,只不過更簡單一些,因為本函數只列出一個欄位(name)。

def remove_dvd(db):

title, identity = find_dvd(db, "remove")

if title is None:

return

ans = Console.get_bool("Remove {0}?".format(title), "no")

if ans:

cursor = db.cursor()

cursor.execute("DELETE FROM dvds WHERE id=?", (identity,))

db.commit()

在用戶需要刪除一個記錄時,將調用本函數,並且本函數與dvds-dbm.py程序中 相應的函數是非常類似的。

到此,我們完全查閱了 dvds-sql.py程序,並且了解了如何創建資料庫表格、選取 記錄、在選定的記錄上進行迭代以及插入、更新與刪除記錄。使用execute()方法,我們可以執行底層資料庫所支持的任意SQL語句。

SQLite提供了比我們這里使用的多得多的功能,包括自動提交模式(以及任意其他類型的事務控制),以及創建可以在SQL查詢內執行的函數的能力。提供一個工廠函數並用於控制對每個取回的記錄返回什麼(比如,一個字典或自定義類型,而不是欄位序列)也是可能的。此外,通過傳遞「:memory:」作為文件名,創建內存中的SQLite 資料庫也是可能的。

以上內容部分摘自視頻課程05後端編程Python22 資料庫編程,更多實操示例請參照視頻講解。跟著張員外講編程,學習更輕松,不花錢還能學習真本領。

F. Python課程內容都學習什麼啊

賀聖軍Python輕松入門到項目實戰(經典完整版)(超清視頻)網路網盤

鏈接: https://pan..com/s/1C9k1o65FuQKNe68L3xEx3w

提取碼: ja8v 復制這段內容後打開網路網盤手機App,操作更方便哦

若資源有問題歡迎追問~