1. 程式人生 > >關於python2和3版本不同引發的urllib報錯及引出的字串問題

關於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 是這種協議的某種實現方式。