關於python2和3版本不同引發的urllib報錯及引出的字串問題
在python2裡有urllib和urllib2兩個庫,但是在python3裡urllib2庫沒有了,因此程式碼從2移植到3會報一些錯誤。程式碼如下:
#!/usr/bin/env python
# -*- coding:UTF-8 -*-
import urllib
import urllib2
import json
deviceID="0000000666"
apikey = "a7e72c97-3aab-44db-af13-d9af0aee6506"
s = "s"
door = "door"
PIR = "pir"
Leak = "leak"
Smoke = "smoke"
Remote = "remote"
def http_post(data):
try:
url = 'http://www.linksprite.io/api/http'
jdata = json.dumps(data)
# print jdata
req = urllib2.Request(url, jdata)
req.add_header('Content-Type','application/json')
print (req)
print type(req)
print(urllib2.urlopen(req))
print(type(urllib2.urlopen(req)))
response = urllib2.urlopen(req)
print response.read()
return response.read()
except urllib2.URLError:
print "connect failed"
return "connect failed"
pass
def http_post1(data):
url = 'http://www.linksprite.io/api/http'
jdata = json.dumps(data)
# print jdata
req = urllib2.Request(url, jdata)
req.add_header('Content-Type' ,'application/json')
response = urllib2.urlopen(req)
print response.read()
return response.read()
values ={
"action":"update",
"apikey":apikey,
"deviceid":deviceID,
"params":
{
"d4": "0",
"d3": "1",
"d2": "1",
"d1": "1",
# "Door":door,
# "PIR":PIR,
# "Leak":Leak,
# "Smoke":Smoke,
# "Remote":Remote,
# "SOS":s
}}
print http_post(values)
這是一段向linkspriteIO平臺傳送post請求操作的python原始碼,通過post請求更新spriteIO平臺上的資料,進而對LinkNode R4/R8進行有效的控制,實現能夠遠端操控LinkNode R4/R8的繼電器開關的作用。
在python3中,Request函式從urllib2庫裡移到了urllib.request裡;URLError從urllib2庫裡移到了urllib.error裡。因此從python2.7搬到python3.5之後做出修改如下:
import urllib
from urllib import request
from urllib import error
# import urllib2
import json
def http_post(data):
deviceID = "0000000666"
apikey = "a7e72c97-3aab-44db-af13-d9af0aee6506"
s = "s"
door = "door"
PIR = "pir"
Leak = "leak"
Smoke = "smoke"
Remote = "remote"
try:
url = 'http://www.linksprite.io/api/http'
jdata = json.dumps(data)
# print jdata
req = request.Request(url, jdata)
req.add_header('Content-Type','application/json')
# print(req)
# print(type(req))
# # print(request.urlopen(req))
# print(type(request.urlopen(str.encode(req))))
# response = request.urlopen(str.encode(req))
response = request.urlopen(req)
print (response.read())
return response.read()
except error.URLError:
print ("connect failed")
return "connect failed"
pass
def http_post1(data):
url = 'http://www.linksprite.io/api/http'
jdata = json.dumps(data)
# print jdata
req = request.Request(url, jdata)
req.add_header('Content-Type','application/json')
response = request.urlopen(req)
print (response.read())
return response.read()
values ={
"action":"update",
"apikey":"a7e72c97-3aab-44db-af13-d9af0aee6506",
"deviceid":"0000000666",
"params":
{
"d4": "0",
"d3": "0",
"d2": "0",
"d1": "0",
# "Door":door,
# "PIR":PIR,
# "Leak":Leak,
# "Smoke":Smoke,
# "Remote":Remote,
# "SOS":s
}}
http_post(values)
出現如下錯誤:
TypeError: POST data should be bytes or an iterable of bytes. It cannot be of type str.
錯誤是在response = request.urlopen(req)
這一句,對比urllib2的urlopen和urllib的request.urlopen,兩者並無不同。上網搜尋發現,問題出在python3的bytes型別和str型別的轉換上。將jdata
進行編碼使它從str轉成bytes之後就執行正常了。
jdata = json.dumps(data).encode("utf-8")
下面簡單介紹一下python2和python3的字串型別的變化。
與 Python2 相比,Python3 的字串型別改成了 str 和 bytes,其中 str 相當於 Python2 的 unicode,bytes 相當於 Python2 的 str。從 redis 中拿回的資料是 bytes 型別,bytes 型別的與 list 中的 str 去比較則是永遠都是 False。
在 Python2 中,unicode 和 str 的混合使用會有隱式的型別轉換,Python3 中則是完全兩種型別,不存在比較的可能性
print(u'' == '') # Python2 -> True
print(b'' == '') # Python3 -> False
Python2 中的 unicode 和 str 實際上都繼承於 basestring
# python2
isinstance('', basestring) # True
isinstance(u'', basestring) # True
在 Python2 中處理字串編碼問題的時候,經常會讓人感到疑惑,我究竟是要呼叫 decode 方法還是 encode 方法呢?哪怕你混用 decode 方法和 encode 方法都是沒有問題的,不會有異常丟擲。
# python2
s = ''
print(type(s)) # str
s.encode('utf-8') # 錯誤呼叫,不會報錯
s.decode('utf-8') # 正確呼叫
但在 Python3 環境中,這兩個型別就完全不同了。
Python3 中的正確用法
你如果去檢視 Python3 中的 str 和 bytes 物件的方法,你會看到他們方法其實是大部分相同的,如 split, startswith 等等一類字串的處理的方法兩者都是有的。最重要的不同就是,str 只有 encode 方法,而 bytes 只有 decode 方法
# python3
s = ''
s.encode('utf-8')
e.decode('utf-8') # AttributeError: 'str' object has no attribute 'decode'
# 其對應的方法引數還是需要和原物件一致
b = b''
b.startswith('') # TypeError: startswith first arg must be bytes or a tuple of bytes, not str
除此之外,在 Python2 中,很多時候為了型別轉換,可能就直接通過 str(obj) 來進行操作,之前這樣處理是沒問題的,但現在這種處理方式不可行了
# python3
b = b'hello world'
str(b) # b'hello world'
上述程式碼可以看到,通過 str 之後,bytes 的確是變成了 str 型別,但是其多出了一個 b 的字首。這裡的正確姿勢是
# python3
if isinstance(b, bytes):
b = b.decode('utf-8')
else:
b = str(b)
除此以外,不少的標準庫的函式接收的型別也限制了,例如 hashlib 中的方法只接收 bytes 型別,json.loads 只接收 str 型別等等。
Python3 的更新預設的 utf-8 編碼解決了很多的問題。
相比於 Python2,可能 Python3 的處理要繁瑣一點,但安全性會好很多,一些很奇怪的問題可以及時發現。例如 decode 和 encode 方法的明確。同時,因為這些變化,我們需要在 bytes 可能出現的地方留心(一般是程式外部來的資料),進行型別轉換,資料互動的層面統一使用 str 進行處理。
與 Python2 相比,str 和 bytes 的命名其實也更貼近實際的情況。我是這樣去記兩者的關係的:str 是 unicode 的 code 的序列,可認為是該字元在世界的唯一標識(code point),而 bytes 則是 str 通過某種編碼(utf-8)實際儲存的二進位制資料。unicode 是種協議,而 utf-8 是這種協議的某種實現方式。