-1 為什么要強(qiáng)調(diào)「在 Mac OS X」下
據(jù)江湖傳言,在 Linux 下沒有這個問題……沒想到 Mac 也會入這種坑,大跌眼鏡
0 背景
目前 Python 社區(qū)中最流行的用于和 Postgres 打交道的模塊非 psycopg2 莫屬了,連 Postgres 官方 wiki 都在介紹該模塊
猜:psycopg2 = Python + SYstem + COnnection + PostGres + 2
- https://wiki.postgresql.org/wiki/Psycopg
- https://wiki.postgresql.org/wiki/Using_psycopg2_with_PostgreSQL
- https://wiki.postgresql.org/wiki/Psycopg2_Tutorial
順便安利一下好友珂皓專門寫的一個中間層 pgdb 用它來和 Postgres 打交道可以省點(diǎn)力氣。
為了方便配置,我在連接數(shù)據(jù)庫一般是在配置文件中這么寫:
[conn01_name]
database = conn01_name
host = 192.168.0.123
port = 5678
user = user_sssj
password =
然后在需要連接數(shù)據(jù)庫的文件中這么寫
# db.py
import pgdb
conn_01_name = pgdb.Connection(
database=config.get('conn_01_name', 'database'),
user=config.get('conn_01_name', 'user'),
password=config.get('conn_01_name', 'password'),
host=config.get('conn_01_name', 'host'),
port=config.get('conn_01_name', 'port')
)
當(dāng)數(shù)據(jù)庫名即 conn_01_name 為英文名如 zoo 時,這樣做沒什么問題。問題就在數(shù)據(jù)庫名是中文的時候:
下面是在交互式shell中。這里使用的數(shù)據(jù)庫名為:大象系統(tǒng)
>>> from db import conn_01_name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/db.py", line 52, in <module>
port=config.get('conn_01_name', 'port')
File "/path/to/py35venv/lib/python3.5/site-packages/pgdb/pgdb.py", line 28, in __init__
self._reconnect()
File "/path/to/py35venv/lib/python3.5/site-packages/pgdb/pgdb.py", line 63, in _reconnect
self.connection = psycopg2.connect(*self.db_args, **self.db_kwargs)
File "/path/to/py35venv/lib/python3.5/site-packages/psycopg2/__init__.py", line 129, in connect
dsn = _ext.make_dsn(dsn, **kwargs)
File "/path/to/py35venv/lib/python3.5/site-packages/psycopg2/extensions.py", line 177, in make_dsn
parse_dsn(dsn)
psycopg2.ProgrammingError: invalid dsn: missing "=" after "大象系統(tǒng)" in connection info string
報錯啦!
01 嘗試
作為一個面向 StackOverFlow 和面向 Google 編程的程序猿,我當(dāng)然是把錯誤碼(最下方那行)復(fù)制起來丟到 Google 上去搜索。可惜無論我如何修改英文關(guān)鍵字,結(jié)果寥寥。遂想:
這類坑可能也只有我們中文圈比較有可能趟上吧……?
干脆加點(diǎn)中文關(guān)鍵字,結(jié)果依舊少得可憐……嗯其實(shí)是幾乎就沒有結(jié)果,不過好歹找到一篇中文博客,大意是說這可能是編碼類型造成的?唔這我之前也有想過,但怎么設(shè)置呢?是哪里的編碼類型?文章中說的是要去設(shè)置 connection.extensions.set_client_encoding
,顧名思義是客戶端的編碼類型,設(shè)為 "UTF8" 即可。
我當(dāng)時就像看到了救命稻草,也沒再仔細(xì)看,嘗試了一下
conn_01_name = pgdb.Connection(
database=config.get('conn_01_name', 'database'),
user=config.get('conn_01_name', 'user'),
password=config.get('conn_01_name', 'password'),
host=config.get('conn_01_name', 'host'),
port=config.get('conn_01_name', 'port')
)
conn_01_name.connection.set_client_encoding('UTF-8')
(注:這里用 Pgdb 實(shí)驗(yàn)其實(shí)沒有太大影響,因?yàn)?Connection 用于包裝的中間層非常簡單,見下可知)
# pgdb.py
import psycopg2
class Connection:
def __init__(self, *args, **kwargs):
self.connection = None
self.db_args = args
self.db_kwargs = kwargs
self._reconnect()
def _reconnect(self):
self._close()
self.connection = psycopg2.connect(*self.db_args, **self.db_kwargs)
self.autocommit = True
呃,還是連不上。我想了一下就知道自己思路不對:
是因?yàn)槟莻€方法要執(zhí)行,必須要建立連接【后】才有用?而我的連接都還沒建立就報錯了,自然無法到執(zhí)行
set_client_encoding
的那步就報錯了
要不我能不能看看建立連接【時】有沒有相應(yīng)參數(shù)來初始化?又掃了一遍那篇中文博客,文中確實(shí)提到的是「取數(shù)據(jù)時」,情況和我不同,也側(cè)面驗(yàn)證了一下我的猜測。查了一下官方文檔,是有這個參數(shù)的:即在初始化 psycopg2.connect 類時,可使用 client_encoding 來指定編碼類型:
client_encoding
This sets the
client_encoding
configuration parameter for this connection. In addition to the values accepted by the corresponding server option, you can useauto
to determine the right encoding from the current locale in the client (LC_CTYPE
environment variable on Unix systems).
(注意到下面這份代碼直接改用 psycopg2 來連接了,這樣比 pgdb 實(shí)驗(yàn)更快一點(diǎn))
import psycopg2
c = psycopg2.connect(dbname="大象系統(tǒng)", user="ssj", password="",
port="5678", host="192.168.0.123", client_encoding="UTF-8")
結(jié)果也是失敗的:仍然和上面是同樣的報錯。我甚至考慮了會不會是參數(shù)的位置導(dǎo)致的?當(dāng)然事實(shí)證明也沒有用。
02 轉(zhuǎn)機(jī)
不死心,繼續(xù)找答案,一邊看文檔。我想了一下,報錯一直說 dsn:
psycopg2.ProgrammingError: invalid dsn: missing "=" after "大象系統(tǒng)"
in connection info string
上面強(qiáng)行換行,方便看
dsn是啥?可能不是很重要,但是上面這,以及剛才在看官方文檔時的線索似乎給了我提示:(先看文檔,我當(dāng)時注意到了這部分)
# psycopg2 初始化連接的 2 種方式
conn = psycopg2.connect("dbname=test user=postgres password=secret")
# 或者
conn = psycopg2.connect(dbname="test", user="postgres", password="secret")
結(jié)合報錯提示,它是說實(shí)際上 psycopg2 的解析過程是把關(guān)鍵字參數(shù)轉(zhuǎn)換成字符串什么的一種叫 dsn 的東西,然后用 parse_dsn (見上面的報錯提示)解析時報錯是嗎?于是我嘗試重新連接:
import psycopg2
conn = psycopg2.connect("dbname=大象系統(tǒng) user=ssj password='' port=5678 host=192.168.0.123")
仍是失敗的。與此同時,我看到了一個提示:
import psycopg2
conn_string = "host='localhost' dbname='my_database' user='postgres' password='secret'"
conn = psycopg2.connect(conn_string)
啊!引號嵌套,我可能就是少了這個。那么其實(shí)我可以這樣寫
import psycopg2
conn = psycopg2.connect("dbname='%(dbname)s' user='%(user)s' password='%(password)s'\
port='%(port)s' host='%(host)s'"
% {'dbname': '大象系統(tǒng)', 'user': 'ssj', 'password': '', 'port': '5678',
'host': '192.168.0.123'})
這樣嘗試之后就成功了。對應(yīng)的 pgdb 寫法是
import pgdb
conn = pgdb.Connection("dbname='%(dbname)s' user='%(user)s' password='%(password)s'\
port='%(port)s' host='%(host)s'"
% {'dbname': '大象系統(tǒng)', 'user': 'ssj', 'password': '', 'port': '5678',
'host': '192.168.0.123'})
03 尾聲和風(fēng)波
終于能夠成功連接中文名稱的數(shù)據(jù)庫了,別提有多高興。這當(dāng)然馬上要查詢一下某些表看看效果啦:
>>> from db import conn
>>> sql = "SELECT animalcode FROM 動物園"
>>> ret = conn.query(sql,)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/py35venv/lib/python3.5/site-packages/pgdb/pgdb.py", line 76, in query
raise e
File "/path/to/py35venv/lib/python3.5/site-packages/pgdb/pgdb.py", line 69, in query
cursor.execute(*args, **kwargs)
psycopg2.ProgrammingError: relation "動物園" does not exist
LINE 1: SELECT animalcode FROM 動物園
啥?這也行?我還以為是中文表名又出錯了。嘗試了一些英文表名,結(jié)果也是 relation xxx does not exist
難道是我被「騙」了嗎?我沒有實(shí)際建立連接但繞開了報錯?于是我用了 conn_zoo.ensure_connected 和 cursor 等方法查詢了一下,好像是有建立起來呀……我索性用同樣的連接法(字符串初始化連接法)去嘗試連接其他數(shù)據(jù)庫(非中文名),連上了且成功查詢到其他英文表名的記錄。
這說明我這種初始化方法應(yīng)該是沒有大問題的。但到底是哪里出錯我一下子也想不起來。后來我看了一下配置文件,突然想到這兩天配服務(wù)器時看各種文檔,專門說到一些數(shù)據(jù)庫的端口值會設(shè)為非默認(rèn)值(有時候可能是為了安全,有時候是為了方便隧道訪問什么的)——會不會是端口值錯了?啊!那么,可能用戶名之類的也有問題。
然后我去核對了 DataGrip 中可以正確連接數(shù)據(jù)庫的配置,和我寫到程序配置文件中的數(shù)據(jù)庫配置……就知道是我大意了:確實(shí)是配置文件寫錯了。
改完配置文件后,就能正常連接中文名稱的數(shù)據(jù)庫,查詢 SQL 語句啦!