首頁 > 軟體

Python 3.x踩坑實戰彙總

2022-03-22 16:00:12

紀要

本文用於記錄學習 Python 過程中遇到的一些小問題,如果遇到的是比較大的問題會單獨開頁面分析學習

處處有坑

1. 檔案讀取 open

# 我們開啟檔案使用 open 方法
xml = open("demo.xml")
# 使用 open 命令讀取檔案時,經常會出現下列錯誤
Traceback (most recent call last):
  File "TempConvert.py", line 84, in <module>
    for line in xml:
UnicodeDecodeError: 'gbk' codec can't decode byte 0x8d in position 38: illegal multibyte sequence
# 出現這個錯誤的原因是系統預設開啟的編碼方式和檔案不一致,需要通過帶格式引數的方式開啟
# 比如,檔案如果是 utf-8 格式檔案,則需要採用下列格式引數:
xml = open("demo.xml", encoding="utf-8")

2. 正規表示式 S 與 \S

首先提出一個問題,使用正規表示式獲取到字串中的郵箱列表。 例:A message from csev@umich.edu to cwen@iupui.edu about meeting @2PM

# 我們可以通過一個簡單的正規表示式,這裡不考慮其他複雜條件
import re
str = 'A message from csev@umich.edu to cwen@iupui.edu about meeting @2PM'
lst1 = re.findall('S+@S+', s)
print(lst1) # ['csev@umich.edu', 'cwen@iupui.edu']

# 然而我們發現,下列正規表示式也有同樣的結果
lst2 = re.findall('\S+@\S+', s)
print(lst2)

這就比較奇怪了,因為在其他語言的正規表示式中,S\S 代表的含義並不相同,S 表示一個非空字元,而 \S 表示匹配字串 S,於是我們作下列嘗試:

'S' == '\S' # True
len('\S') # 2
len('S') # 2

是不是驚呆了!於是我又嘗試

's' == '\s' # True
len('\s') # 2
len('s') # 2

'n' == '\n' # False
len('\n') # 2
len('n') # 1

我們發現 sn 的情況並不相同,通過一番查詢,找到了下面的文章:

Python regex 's' vs 's'

文中提到

Don't confuse python-level string-escaping and regex-level string-escaping. Since s is not an escapable character at python-level, the interpreter understand a string like s as the two characters and s. Replace s with n, and it understands it as the newline character.
不要混淆 Python 中的字串跳脫和正規表示式級別的字串跳脫。由於 s 在 Python 不是可跳脫字元,直譯器將 s 這樣的字串理解為兩個字元 和 s。將 s 替換為 n,它將其理解為換行符。

雖然沒有提及到更權威的說法,但是也反應出了,如果是 s 會被當做是兩個字元,如果是 \s 因為 \ 是可跳脫字元,被當做了 一個字元,\s 也就被當做了 s 兩個字元。所以才會出現這種情況。

's' == '\s' # True

3. 正規表示式匹配方法 match

在學習正規表示式匹配規則時候發現,Python 正則匹配的方式和其他的稍有不同,比如上一條提到的 S\S 的問題,然後還有下面的:

Python 的正則匹配是從頭匹配,舉個例子,如果我們要匹配一個字串中的電話號碼

在 JS 中你可以用下列的正則匹配

// 使用 JS 的方式,我們可以有下列的寫法
'我的手機號碼是15900000000不要告訴別人,否則我就把你號碼是13900000000告訴別人'.match(/1[0-9]{10}/g)
// (2) ['15900000000', '13900000000']

但是如果你把同樣的正則放到 Python 中則不那麼好使

import re
str = '我的手機號碼是15900000000不要告訴別人,否則我就把你號碼是13900000000告訴別人'

# 錯誤的寫法
mah = re.match('1[0-9]{10}', str)
print(mah)
# None

因為 Python 的匹配 match 預設是從開頭開始匹配的,而 1 並不一定是給定的字串的首字母。

# 應該使用另一個方法 findall 代替
mah = re.findall('1[0-9]{10}', str)
print(mah)
# ['15900000000', '13900000000']

從這一點可以看出,Python 的很多庫都提供用不同於其他語言的方法,作為其他語言轉學 Python 的小夥伴要實際測試過方法或者熟知的情況下使用,而不應該不加思考的定式思維,一廂情願的覺得 Python 就和其他的語言一樣。

4. 幫助檔案 pydoc

Python 中對庫或者方法的幫助檢視可以用下列的方式進行:

  • 【可選】在命令列環境下輸入 python 即可進入 Python 編譯環境
  • 使用 dir(庫、物件) 的方式檢視庫或者物件可以提供的方法
dir('字串') # 檢視字串有哪些操作方法

import re
dir(re) # 檢視正規表示式庫有哪些操作方法
  • 使用 help(庫、物件) 的方式檢視庫或者物件的幫助資訊
import re
help(re) # 檢視正規表示式庫的幫助檔案
dir(re.match) # 檢視正規表示式的 `match` 的幫助資訊

如果我們是想把幫助檔案寫入文字檔案中,可以在 命令列中 使用命令:

# 將 re 庫的幫助資訊到 html 檔案
python -m pydoc -w re

# windows 下可以用下列方法輸出到文字檔案
python -m pydoc re > d:re.txt

更多關於 pydoc 的資訊可以參考官方檔案 pydoc

5. 字串 encode base64 編碼

一些教學上對字串的 base64 編碼的方式是這樣的:

str = "this is string example....wow!!!";
print "Encoded String: " + str.encode('base64','strict')

# 預計輸出結果
Encoded String: dGhpcyBpcyBzdHJpbmcgZXhhbXBsZS4uLi53b3chISE=

但是這個程式碼卻會報錯:

LookupError: 'base64' is not a text encoding; use codecs.encode() to handle arbitrary codecs

據瞭解,這種錯誤的寫法其實是來源於 Python 2.x 的寫法,但是在 Python 3.x 中寫法發生了變化,字串的 base64 正確編碼方式應該是:

import base64
str = "this is string example....wow!!!"

# 返回原字串編碼為位元組串物件的版本
strb = str.encode()
base64b = base64.b64encode(strb)
base64 = base64b.decode()
print(base64)

6. Python 呼叫 C# 動態連結庫

在百度搜尋了很多關於 Python 呼叫 C# 動態連結庫的方式,大多是如下程式碼:

import clr
# clr.FindAssembly('DotNetWithPython.dll') # dll在當前目錄
clr.AddReferenceToFile('DotNetWithPython.dll') # dll在當前目錄

from DotNetWithPython import * # 匯入動態連結庫中的所有類

if __name__ == '__main__':
    mainapp = MainForm() # 初始化 MainForm 類物件

可惜啊,沒有能正常使用的,我也不清楚到底是哪裡出了問題,為什麼都沒有效果呢,難不成這些都是 Python 2.x 的用法嗎?(我學的是 Python 3.x)

作了如下思考:

python 的 clr 即 PythonNet,那麼是否直接到 PythonNet 官方或者 github 上查詢相關程式碼呢?

於是搜尋到了下列地址:pythonnet.github.io/按照裡面給出的程式碼逐個嘗試,首先是這個:

from System import String
from System.Collections import *

我們發現會報錯:

Traceback (most recent call last):
  File "d:/Temp/PythonProjects/Demos/DllDo.py", line 10, in <module>
    from System import String
ModuleNotFoundError: No module named 'System'

我們嘗試把程式碼修改為:

import clr
from System import String
from System.Collections import *

可以確定,我們對 .NET 相關類的呼叫必須要 import clr 我們繼續嘗試,當嘗試到下列程式碼時:

import clr
from System.Drawing import Point
p = Point(5, 5)

又報錯了:

d:/Temp/PythonProjects/Demos/DllDo.py:11: DeprecationWarning: The module was found, but not in a referenced namespace.
Implicit loading is deprecated. Please use clr.AddReference('System.Drawing').
  from System.Drawing import Point

從給出的錯誤資訊中,我們可以看出,我們需要對空間進行參照:

import clr
clr.AddReference('System.Drawing')
from System.Drawing import Point

p = Point(5, 5)
print(p)
# {X=5,Y=5}

到了這一步,我們基本確定 Python 呼叫 C# 是沒有問題的,那麼如果才能呼叫自己定義的 dll 動態連結庫呢?我們嘗試按照前文系統類的參照方式:

import clr
clr.AddReference('DotNetWithPython')
from DotNetWithPython import MainForm

mainapp = MainForm()

結果報錯:

Traceback (most recent call last):
  File "d:/Temp/PythonProjects/Demos/DllDo.py", line 12, in <module>
    from DotNetWithPython import MainForm
ModuleNotFoundError: No module named 'DotNetWithPython'

於是我又想:

clr 可以正常呼叫 .NET 本身提供的類物件,呼叫不到我的 自己寫的動態連結庫和 .NET 本身提供的差異在於不在系統環境中,自己的 dll 在當前目錄或者其他目錄

於是我們使用 dir(clr) 確定了一下是否有什麼方法可用

import clr
dir(clr)

# ['AddReference', 'FindAssembly', 'GetClrType', 'ListAssemblies', 'Microsoft', 'Python', 'System', '_AtExit', '__class__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__version__', '_extras', 'clrModule', 'clrmethod', 'clrproperty', 'e__NativeCall', 'getPreload', 'setPreload']

我們發現了方法 FindAssembly 感覺很像,於是我們按照前文系統類的參照方式及這一句進行測試:

import clr
clr.FindAssembly('DotNetWithPython.dll')
clr.AddReference('DotNetWithPython')
from DotNetWithPython import MainForm

mainapp = MainForm()

還是一樣的錯誤,我都要哭了,於是我只能到 PythonNet Github 的 issues 中尋找答案,發現提出這個問題的人很多,並且問題被鎖定在了 .net core、.net 5,而 .Net Framework 中沒有出現這種問題,我於是新建了一個基於 .Net Framework 4.x 的專案進行簡單測試,發現確實不會報錯。

現在問題很明確了,但是並沒有得到解決,於是我只能一條條看那難懂的 issues 列表,功夫不負有心人,我找到了這個貼文 issues 1536,明確的給出了說法,Pythonnet 2.5 does not support .NET 5It is supported in v3 previews.

好的吧,於是我用 pip list 檢視所有 Python 第三方庫的版本

C:UsersAdministrator>pip list
Package          Version
---------------- ----------
click            7.1.2
pip              22.0.3
pycparser        2.21
PyQt5            5.15.4
pyqt5-plugins    5.15.4.2.2
PyQt5-Qt5        5.15.2
PyQt5-sip        12.9.1
pyqt5-tools      5.15.4.3.2
python-dotenv    0.19.2
pythonnet        2.5.2
qt5-applications 5.15.2.2.2
qt5-tools        5.15.2.1.2
setuptools       41.2.0

果然,pythonnet 的版本是 2.5.2,我對專案進行降級測試,發現 .net core 僅在版本為 net core 1.x 時候支援,2.x-3.x、.NET 5 均不支援。

所以你如果使用的是 pythonnet 2.x 版本,就不要嘗試使用更高版本的 .net core 實現你的功能了,否則需要更新 pythonnet 到更高版本

繼續看 issues 1536,發現即使更新了版本還是會存在問題,並跟蹤到了 issues 1473 我嘗試將 pythonnet 升級到 3.x previews 版本但是出現的錯誤,沒有升級成功,所以並沒有繼續測試後續的功能。

總結

到此這篇關於Python 3.x踩坑實戰彙總的文章就介紹到這了,更多相關Python3.x踩坑內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com