Python ceil 向上取整用法與範例

本篇 ShengYu 介紹 Python ceil 向上取整用法與範例,Python ceil 也是無條件進位的意思,Python 使用 math.ceil() 前要 import math

以下的 Python ceil 用法與範例將分為這幾部分,

  • Python math.ceil() 基本範例
  • Python math.ceil() 負數範例

那我們開始吧!

Python math.ceil() 基本範例

這邊介紹 Python math.ceil() 無條件進位或者向上取整的用法,在 math.ceil() 傳入任何一個浮點數,都會回傳無條件進位的結果,例如傳入 1.2 會回傳 2,傳入 11.1 會回傳 12。

python3-math-ceil.py
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import math

n1 = 1.2
n2 = 1.4
n3 = 1.6
n4 = 1.8
print(math.ceil(n1))
print(math.ceil(n2))
print(math.ceil(n3))
print(math.ceil(n4))

Python ceil 結果輸出如下,

1
2
3
4
2
2
2
2

math.ceil() 如果傳入 1.0 會回傳多少呢?答案是 1。

Python math.ceil() 負數範例

這邊示範 Python math.ceil() 負數的範例,

python3-math-ceil2.py
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import math

n1 = -11.2
n2 = -11.4
n3 = -11.6
n4 = -11.8
print(math.ceil(n1))
print(math.ceil(n2))
print(math.ceil(n3))
print(math.ceil(n4))

Python ceil 負數結果輸出如下,

1
2
3
4
-11
-11
-11
-11

以上就是 Python math.ceil 用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Python 新手入門教學懶人包

Windows findstr 用法與範例

本篇 ShengYu 介紹 Windows findstr 搜尋字串指令,findstr 通常用來搭配其它指令來搜尋字串,例如 findstr 搭配 cat 來搜尋檔案裡的特定字串,findstr 算是 Windows DOS 必學指令,學會這招讓工作更快速輕鬆。

findstr 搜尋特定字串

Windows 基本的 findstr 搜尋特定字串用法如下,假設我要在 xxx.txt 檔案裡搜尋 pattern 字串,

1
cat xxx.txt | findstr "pattern"

findstr 搜尋多個字串

findstr 要搜尋多個字串的話,可以用空格隔開,例如 findstr 搜尋兩個字串的話就這樣寫,

1
cat xxx.txt | findstr "pattern1 pattern2"

findstr 搜尋三個字串的話就這樣寫,

1
cat xxx.txt | findstr "pattern1 pattern2 pattern2"

以上就是 Windows findstr 用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

Python PyQt5 QPainter 用法與範例

本篇 ShengYu 介紹 Python PyQt5 QPainter 用法與範例。

以下的 Python PyQt5 QPainter 用法與範例將分為這幾部分,

  • 建立 PyQt5 QPainter
  • PyQt5 QPainter drawText 繪製文字
  • PyQt5 QPainter drawLine 繪製直線
  • PyQt5 QPainter drawRect 繪製矩形
  • PyQt5 QPainter drawEllipse 繪製橢圓
  • PyQt5 QPainter drawArc 繪製圓弧、圓形
  • PyQt5 QPainter drawPolygon 繪製多邊形
  • PyQt5 QPainter drawImage 繪製影像

那我們開始吧!

建立 PyQt5 QPainter

PyQt5 簡單建立 QPainter 的用法如下,繪製的操作會在 QWidget.paintEvent() 函式裡完成,並且繪製的函式必須放在 QPainter.begin()QPainter.end() 之間,這些繪圖函式稍後會介紹,

python-pyqt-qpainter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

# paint ...

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

如果要改變背景顏色的話可以使用 palette,例如將背景調整成白色或黑色,

python-pyqt-qpainter2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(self.backgroundRole(), Qt.white)
# palette.setColor(self.backgroundRole(), Qt.black)
self.setPalette(palette)

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

# paint ...

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QPainter drawText 繪製文字

PyQt5 QPainter 繪製文字 drawText 函式,在使用之前先設定一下畫筆與字型,QPainter.setPen() 可以設定畫筆顏色,你可以透過 QColor 來指定 RGB 的顏色,或者使用 PyQt5 內建提供的常用顏色,例如:Qt.black 黑色、Qt.white 白色、Qt.red 紅色、Qt.green 綠色、Qt.blue 藍色等等,QPainter.setFont() 是設定字型,第二個參數為字型大小,

QPainter.drawText() 繪製文字時是使用傳入的 (x, y) 座標作為文字的左上角座標,而不是文字中心的座標,QPainter.drawText() 繪製文字也可以傳入 QRect 的方式 x, y 分別為文字的左上角座標與 w, h 寬度跟高度,

python-pyqt-qpainter-drawtext.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5.QtCore import Qt, QPoint, QRect


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

qpainter.setPen(QColor(0, 0, 255))
qpainter.setFont(QFont('Arial', 20))

qpainter.drawText(QPoint(10, 30), 'PyQt5')
# qpainter.drawText(10, 30, 'PyQt5')

# qpainter.drawText(QRect(10, 30, 100, 30), Qt.AlignLeft, 'PyQt5')
# qpainter.drawText(10, 30, 100, 30, Qt.AlignLeft, 'PyQt5')

qpainter.drawText(event.rect(), Qt.AlignCenter, 'hello world')

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QPainter drawLine 繪製直線

PyQt5 QPainter drawLine 繪製直線時需要傳入兩組座標 (x1, y1) 與 (x2, y2),繪製直線之前先設定畫筆,建立 QPen() 分別傳入線條顏色,線條寬度,線條樣式,如下例中的 QPen(Qt.black, 2, Qt.SolidLine),線條樣式有 Qt.SolidLineQt.DashLineQt.DashDotLineQt.DashDotDotLine 可選,另外還有 Qt.CustomDashLine 選項可以客製化,這邊先不介紹,線條樣式也可以透過 QPen.setStyle() 來改變,改變後記得使用 QPen.setPen() 重新設定一下,

python-pyqt-qpainter-drawline.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

qpen = QPen(Qt.black, 2, Qt.SolidLine)
qpainter.setPen(qpen)
qpainter.drawLine(20, 40, 180, 40)

qpen.setStyle(Qt.DashLine)
qpainter.setPen(qpen)
qpainter.drawLine(20, 60, 180, 60)

qpen.setStyle(Qt.DashDotLine)
qpainter.setPen(qpen)
qpainter.drawLine(20, 80, 180, 80)

qpen.setStyle(Qt.DashDotDotLine)
qpainter.setPen(qpen)
qpainter.drawLine(20, 100, 180, 100)

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QPainter drawRect 繪製矩形

PyQt5 QPainter drawRect 繪製矩形時是帶入 QRect,x, y 為矩形的左上角座標 w, h 為矩形的寬度與高度,矩形的顏色可以透過 QPainter.setPen() 來更換,

python-pyqt-qpainter-drawrect.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt, QRect


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

qpainter.setPen(Qt.black)
qpainter.drawRect(QRect(10, 10, 80, 80))

qpainter.setPen(Qt.green)
qpainter.drawRect(100, 10, 80, 80)

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QPainter drawEllipse 繪製橢圓

PyQt5 QPainter 繪製橢圓時是使用 drawEllipse 函式,也可以用來繪製圓形,傳入 QRect 的 x, y 分別為橢圓外圍/圓形的矩形的左上角座標與 w, h 寬度跟高度,

python-pyqt-qpainter-drawellipse.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt, QRect


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

qpen = QPen(Qt.black)
qpainter.setPen(qpen)

qpainter.drawEllipse(QRect(10, 30, 40, 80))
qpainter.drawEllipse(QRect(60, 30, 80, 40))
qpainter.drawEllipse(QRect(150, 30, 40, 40))

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QPainter drawArc 繪製圓弧、圓形

PyQt5 QPainter 繪製圓弧是使用 drawArc 函式,傳入 QRect 的 x, y 分別為矩形的左上角座標與 w, h 寬度跟高度,第二個參數為起始的角度,第二個參數為結束的角度,這兩個角度需要乘 16,因爲單位爲 alen,一度等於 16 alen,

python-pyqt-qpainter-drawarc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt, QRect


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

qpen = QPen(Qt.black)
qpainter.setPen(qpen)

qpainter.drawArc(QRect(20, 20, 50, 50), 0, 90*16)
qpainter.drawArc(QRect(100, 20, 50, 50), 0, 180*16)
qpainter.drawArc(QRect(20, 80, 50, 50), 90*16, 180*16)
qpainter.drawArc(QRect(100, 80, 50, 50), 0, 360*16)

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QPainter drawPolygon 繪製多邊形

PyQt5 QPainter 繪製多邊形是使用 drawPolygon 函式,需要傳入一組座標以上 (x1, y1) 與 (x2, y2) … (xn, yn),可以直接將點群傳入 QPainter.drawPolygon() 函式裡,也可以先建構 QPolygon() 傳入 QPoint list,再把 QPolygon 傳入 QPainter.drawPolygon() 裡,

python-pyqt-qpainter-drawpolygon.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter, QPen, QPolygon
from PyQt5.QtCore import Qt, QPoint


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

qpen = QPen(Qt.black)
qpainter.setPen(qpen)
p1 = QPoint(40, 40)
p2 = QPoint(60, 20)
p3 = QPoint(80, 40)
p4 = QPoint(80, 80)
p5 = QPoint(40, 80)
qpainter.drawPolygon(p1, p2, p3, p4, p5)

qpen = QPen(Qt.red)
qpainter.setPen(qpen)
p1 = QPoint(100, 40)
p2 = QPoint(120, 20)
p3 = QPoint(140, 40)
p4 = QPoint(140, 80)
p5 = QPoint(100, 80)
qpolygon = QPolygon([p1, p2, p3, p4, p5])
qpainter.drawPolygon(qpolygon)

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QPainter drawImage 繪製影像

PyQt5 使用 drawImage 來繪製影像,drawImage 第一個參數為 QRect 繪製的區域 x, y 分別為矩形的左上角座標與 w, h 寬度與高度,第二個參數為影像,在這範例中這影像是在 initUI 時就已經先將影像讀取就緒了,在 paintEvent 裡就直接繪製該影像,

python-pyqt-qpainter-drawimage.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.QtGui import QPainter, QPen, QImage
from PyQt5.QtCore import Qt, QRect


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

self.qimage = QImage('lena.jpg')

def paintEvent(self, event):
qpainter = QPainter()
qpainter.begin(self)

qpainter.drawImage(QRect(10, 10, 120, 120), self.qimage)

qpainter.end()

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

以上就是 Python PyQt5 QPainter 用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Python 新手入門教學懶人包
Python PyQt5 新手入門教學

Python PyQt5 QThread 用法與範例

本篇 ShengYu 介紹 Python PyQt5 QThread 用法與範例,在 GUI 程式中,如果你想要讓程式做一件很耗時的工作,例如:下載檔案、I/O 存取等等,在 UI thread 做這些事的話會讓整個 UI 卡住,出現 UI 無回應的狀態,這時你可以將這些耗時的工作另外開執行緒去做,以避免 UI thread 卡住,在 PyQT 中我們可以使用 QThread 來完成這件事,接下來就介紹如何在 PyQT5 中使用 QThread。

以下的 Python PyQt5 QHBoxLayout 用法與範例將分為這幾部分,

  • PyQt5 QThread 的基本用法
  • PyQt5 錯誤使用執行緒讓畫面卡住
  • PyQt5 在 QWidget 裡使用 QThread

那我們開始吧!

PyQt5 QThread 的基本用法

PyQt5 要使用 QThread 建立一個執行緒的話,需要新增 QThread 的一個子類,然後覆寫 QThread.run() 函式,就像下例子中的 WorkerThread 類別繼承 QThread 並且覆寫了 run 成員函式,

1
2
3
4
5
6
class WorkerThread(QThread):
def __init__(self):
super().__init__()

def run(self):
# do something

接下來就可以使用 QThread.start() 來啟動執行緒,我們先來看一個小例子,在建構完 WorkerThread 還不會去執行 run 函式,直到呼叫 QThread.start() 才會開始去執行 run 函式,另外如果主執行緒要等待這兩個執行緒執行完畢才繼續往下執行的話可以使用 QThread.wait()QThread.wait() 會等待該執行緒執行完成才會返回,如果該執行緒裡寫了一個無窮迴圈的話,那麼執行 QThread.wait() 就會進入無限等待,等到天荒地老了,

python-pyqt-qthread.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import time
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QThread


class WorkerThread(QThread):
def __init__(self):
super().__init__()

def run(self):
for i in range(3):
time.sleep(1)
print('WorkerThread::run ' + str(i))

if __name__ == '__main__':
app = QApplication(sys.argv)
print('main')

work1 = WorkerThread()
work2 = WorkerThread()
work1.start()
work2.start()
work1.wait()
work2.wait()
print('end of main')
# sys.exit(app.exec_())

在這個 run 函式裡有個 for 迴圈執行 3 次迴圈,並且每次 sleep 1 秒就輸出一個訊息,以便我們了解執行迴圈到第幾次了,以下為輸出的結果,可以發現主執行緒是等到兩個 WorkerThread 都執行完畢了以後才輸出了 end of main 訊息,證明了使用 QThread.wait() 是有效的,

1
2
3
4
5
6
7
8
main
WorkerThread::run 0
WorkerThread::run 0
WorkerThread::run 1
WorkerThread::run 1
WorkerThread::run 2
WorkerThread::run 2
end of main

接下來讓我們來修改 WorkerThread 類別,讓輸出的訊息能夠更好地分辨是哪個執行緒,在 WorkerThread 建構時帶入一個名稱,還有 sleep 的秒數,

python-pyqt-qthread2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import time
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QThread


class WorkerThread(QThread):
thread_name = 'unknown'
sleep_seconds = 1

def __init__(self, thread_name, sleep_seconds):
super().__init__()
self.thread_name = thread_name
self.sleep_seconds = sleep_seconds

def run(self):
for i in range(3):
time.sleep(self.sleep_seconds)
print(self.thread_name + ' ' + str(i))

if __name__ == '__main__':
app = QApplication(sys.argv)
print('main')

work1 = WorkerThread('work 1', 2)
work2 = WorkerThread('work 2', 1)
work1.start()
work2.start()
work1.wait()
work2.wait()
print('end of main')
# sys.exit(app.exec_())

跟上例不同的次這次我們帶入不同的 sleep 秒數,來觀察看看是不是 work1 執行緒跟我們的預期一樣應該要比 work2 晚完成,以下為輸出的結果,果然 work1 因為 sleep 得比較久的關係所以比 work2 執行緒晚完成,

1
2
3
4
5
6
7
8
main
work 2 0
work 2 1
work 1 0
work 2 2
work 1 1
work 1 2
end of main

PyQt5 錯誤使用執行緒讓畫面卡住

新手在 PyQt5 開發過程中容易錯誤地使用執行緒導致讓畫面卡住或者畫面變黑,以一個下載檔案的程式為例,按下按鈕會去作約 10 秒的工作,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import time
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout,
QLabel, QPushButton)


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('press button to start download', self)
layout.addWidget(self.mylabel)

self.mybutton = QPushButton('start', self)
self.mybutton.clicked.connect(self.onButtonClick)
layout.addWidget(self.mybutton)

def onButtonClick(self):
self.mybutton.setDisabled(True)
for i in range(10):
time.sleep(1)
self.mylabel.setText('finish')
self.mybutton.setDisabled(False)

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

在執行的過程中按下按鈕後會發現整個 GUI 程式就無法再做其它 UI 操作,之後整個畫面卡住或者畫面變黑,如下圖所示,

這就是錯誤地使用 UI 執行緒,要如何解決這個問題呢?下一章節馬上給你介紹。

PyQt5 在 QWidget 裡使用 QThread

在 PyQT 程式中,主執行緒就是我們說的 UI 執行緒,UI 執行緒會處理所有 widget 的事務,所以如果有一耗時的工作要執行的話我們通常不會寫在 UI 執行緒裡,因為那樣會無法讓其它 widget 進行更新,導致畫面卡住或程式無回應的現象,解決的方法是另外建立一個執行緒在處理這些耗時的工作,

我們在 WorkerThread 裡新增了兩個 signal,分別為 trigger 與 finished,finished 就是完成後的訊號,而 trigger 就是我們執行過程中會發送的訊號,而要客製化 signal 訊號時使用 pysingal 來定義要發射到目標函式的函式原型,例如下例中的 trigger = pyqtSignal(str)

整個程式就是按下按鈕後,會開啟另一個執行緒,每一秒都會更新一次秒數到 label 上,透過 self.trigger.emit(str(i+1)) 來發射訊號並且傳送第幾秒參數,第 5 秒會結束這個執行緒,然後 self.finished.emit() 發射結束訊號,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import time
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout,
QLabel, QPushButton)
from PyQt5.QtCore import QThread, pyqtSignal


class WorkerThread(QThread):
trigger = pyqtSignal(str)
finished = pyqtSignal()

def __init__(self):
super().__init__()

def run(self):
for i in range(5):
time.sleep(1)
self.trigger.emit(str(i+1))
print('WorkerThread::run ' + str(i))
self.finished.emit()


class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('press button to start thread', self)
layout.addWidget(self.mylabel)

self.mybutton = QPushButton('start', self)
self.mybutton.clicked.connect(self.startThread)
layout.addWidget(self.mybutton)

self.work = WorkerThread()

def startThread(self):
self.mybutton.setDisabled(True)
self.work.start()
self.work.trigger.connect(self.updateLabel)
self.work.finished.connect(self.threadFinished)
self.updateLabel(str(0))

def threadFinished(self):
self.mybutton.setDisabled(False)

def updateLabel(self, text):
self.mylabel.setText(text)

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

如果不想要 threadFinished 函式裡僅僅地只是一行程式碼的話,想去除 threadFinished 函式的話可以改成 lambda 的寫法,如下範例所示,將 self.mybutton.setDisabled(False) 操作寫在 self.work.finished.connect() 裡的 lambda 運算式裡,更多詳細的 Python lambda 用法可以參考這篇

1
2
3
4
5
6
7
8
9
10
11
12
13
def startThread(self):
self.mybutton.setDisabled(True)
self.work.start()
self.work.trigger.connect(self.updateLabel)
# self.work.finished.connect(self.threadFinished)
self.work.finished.connect(lambda: self.mybutton.setDisabled(False))
self.updateLabel(str(0))

# def threadFinished(self):
# self.mybutton.setDisabled(False)

def updateLabel(self, text):
self.mylabel.setText(text)

以上就是 Python PyQt5 QThread 用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Python 新手入門教學懶人包
Python PyQt5 新手入門教學

Python PyQt5 QImage 用法與範例

本篇 ShengYu 介紹 Python PyQt5 QImage 用法與範例,QImage 是專門處理影像 pixel 像素的類別,在這篇我們將會介紹 PyQt5 何使用 QImage 來讀取圖片並顯示出來。

以下的 Python PyQt5 QImage 用法與範例將分為這幾部分,

  • PyQt5 QImage 讀取圖片並顯示在 QLabel 上
  • PyQt5 QImage 用某顏色填充/填滿
  • PyQt5 QImage 修改像素
  • PyQt5 從 numpy.ndarray 初始化 QImage
  • PyQt5 從 cv2.imread 讀取影像並轉換成 QImage

PyQt5 QImage 讀取圖片並顯示在 QLabel 上

QImage 跟之前介紹的 Widget 不一樣,之前都是使用 QtWidgets 裡的元件,而 QImage 是為 I/O 或圖片 pixel 像素存取而設計的。如果你想要存取圖片的像素或是修改圖片像素,就需要使用 QImage,實際上要使用 QImage 時要配合著 QLabel 與 QPixmap一起使用,

這邊示範 PyQt5 QImage 基本用法,這邊先示範一個 label 搭配 QImage 使用,self.myqimage = QImage('lena.jpg') 建構 QImage 同時讀取圖片,因為 QLabel 只支援 QPixmap 顯示,所以我們這邊要將 QImage 轉換成 QPixmap,使用的是 QPixmap.fromImage() 函式,接下來用 QLabel.setPixmap() 將該 QImage 透過 QPixmap.fromImage() 轉成 QPixmap 設定給 mylabel,如果目錄下沒有 lena.jpg 這張圖片的話則會顯示空白的 label,範例如下,

python-pyqt-qimage.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel)
from PyQt5.QtGui import QImage, QPixmap

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('this is an image', self)
layout.addWidget(self.mylabel)

self.myqimage = QImage('lena.jpg')
self.mylabel.setPixmap(QPixmap.fromImage(self.myqimage))

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QImage 用某顏色填充/填滿

PyQt5 要將 QImage 用某顏色填充的話,可以使用 QImage.fill() 函式,以下例子示範用紅色填充,一開始先初始化 320x240 大小的 QImage 並且格式為 Format_RGB888,之後再用 QImage.fill() 填充 qRgb,

python-pyqt-qimage2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel)
from PyQt5.QtGui import QImage, QPixmap, qRgb

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('this is an image', self)
layout.addWidget(self.mylabel)

self.myqimage = QImage(320, 240, QImage.Format_RGB888)
self.myqimage.fill(qRgb(255, 0, 0))
self.mylabel.setPixmap(QPixmap.fromImage(self.myqimage))

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

承上例,再 QImage.fill() 裡也可以先用 QColor 再轉 rgb,

1
2
3
self.myqimage = QImage(320, 240, QImage.Format_RGB888)
self.myqimage.fill(QColor(255, 0, 0).rgb())
self.mylabel.setPixmap(QPixmap.fromImage(self.myqimage))

PyQt5 QImage 修改像素

PyQt5 QImage 要修改像素的話可以使用 QImage.setPixel(),這邊示範用 for 迴圈遍歷每一個 pixel,將每個 pixel 都設為藍色,

python-pyqt-qimage3.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel)
from PyQt5.QtGui import QImage, QPixmap, QColor

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('this is an image', self)
layout.addWidget(self.mylabel)

self.myqimage = QImage(320, 240, QImage.Format_RGB888)
for x in range(self.myqimage.width()):
for y in range(self.myqimage.height()):
self.myqimage.setPixel(x, y, QColor(0, 0, 255).rgb())

self.mylabel.setPixmap(QPixmap.fromImage(self.myqimage))

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

上述例子是使用 QColor 轉換成 rgb,也可以直接使用 qRgb,例如將每個 pixel 改成綠色可以這樣寫,

1
2
3
4
5
from PyQt5.QtGui import qRgb
...
for x in range(self.myqimage.width()):
for y in range(self.myqimage.height()):
self.myqimage.setPixel(x, y, qRgb(0, 255, 0))

如果要將每個 pixel 都加 10 的話可以這樣寫,pixel 值超過 255 需要進行邊界處理,這邊先省略了,

1
2
3
4
5
r, g, b, a = QColor(self.myqimage.pixel(x, y)).getRgb()
r += 10
g += 10
b += 10
self.myqimage.setPixel(x, y, QColor(r, g, b, a).rgb())

PyQt5 從 numpy.ndarray 初始化 QImage

如果 PyQt5 要從 numpy.ndarray 初始化 QImage 的話,可以在 QImage() 建構子的第一個引數放入 numpy.ndarray,詳細用法請看下面例子,np.zeros() 是初始化一個 numpy.ndarray 並且用 0 來填充初始化,

python-pyqt-qimage4.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel)
from PyQt5.QtGui import QImage, QPixmap, QColor
import numpy as np

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('this is an image', self)
layout.addWidget(self.mylabel)

img_np = np.zeros((240, 320, 3), dtype=np.uint8)
print(type(img_np)) # numpy.ndarray
print(type(img_np.data)) # memoryview
height, width, channel = img_np.shape
bytesPerline = 3 * width
self.myqimage = QImage(img_np.data, width, height, bytesPerline, QImage.Format_RGB888)
self.mylabel.setPixmap(QPixmap.fromImage(self.myqimage))

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 從 cv2.imread 讀取影像並轉換成 QImage

如果 PyQt5 要從 cv2.imread() 讀取影像並轉換成 QImage 的話,範例如下,從 cv2.imread() 讀入影像後,後面的步驟基本上跟前述例子一樣了,需要注意的是 OpenCV 的 pixel 擺放順序為 bgr,所以這邊要透過 QImage.rgbSwapped() 函式將 rgb 轉換成 bgr,

python-pyqt-qimage5.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel)
from PyQt5.QtGui import QImage, QPixmap
import cv2

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('this is an image', self)
layout.addWidget(self.mylabel)

img = cv2.imread('lena.jpg')
print(type(img)) # numpy.ndarray
print(type(img.data)) # memoryview
height, width, channel = img.shape
bytesPerline = 3 * width
self.myqimage = QImage(img.data, width, height, bytesPerline, QImage.Format_RGB888)
self.mylabel.setPixmap(QPixmap.fromImage(self.myqimage.rgbSwapped()))

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

以上就是 Python PyQt5 QImage 用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Python 新手入門教學懶人包
Python PyQt5 新手入門教學
Python OpenCV 使用 PyQt5 顯示影像圖片

Python PyQt5 QHBoxLayout 水平佈局用法與範例

本篇 ShengYu 介紹 Python PyQt5 QHBoxLayout 水平佈局用法與範例,QHBoxLayout 能讓 UI 元件橫向/水平/左右的方式排列,相較於手動排版 QHBoxLayout 能夠讓開發者很快速方便地讓元件佈局。

以下的 Python PyQt5 QHBoxLayout 用法與範例將分為這幾部分,

  • PyQt5 QHBoxLayout 基本用法
  • PyQt5 QHBoxLayout 放入不同元件

PyQt5 QHBoxLayout 基本用法

這邊示範 PyQt5 QHBoxLayout 基本用法,這邊示範將三個按鈕放入一個 QHBoxLayout 垂直佈局上,

python-pyqt-qvboxlayout.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QHBoxLayout, QPushButton)

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QHBoxLayout()
self.setLayout(layout)

self.mybutton1 = QPushButton('button 1', self)
layout.addWidget(self.mybutton1)

self.mybutton2 = QPushButton('button 2', self)
layout.addWidget(self.mybutton2)

self.mybutton3 = QPushButton('button 3', self)
layout.addWidget(self.mybutton3)

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QHBoxLayout 放入不同元件

這邊示範將不同的 UI 元件放入一個 QHBoxLayout 水平佈局上,分別是一個 label、一個 lineEdit 與一個 button 按鈕,

python-pyqt-qvboxlayout2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QHBoxLayout,
QLabel, QLineEdit, QPushButton)

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QHBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('username:', self)
layout.addWidget(self.mylabel)

self.mylineedit = QLineEdit(self)
layout.addWidget(self.mylineedit)

self.mybutton = QPushButton('Login', self)
layout.addWidget(self.mybutton)

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

以上就是 Python PyQt5 QHBoxLayout 水平佈局用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Python 新手入門教學懶人包
Python PyQt5 新手入門教學

Python PyQt5 QGridLayout 網格佈局用法與範例

本篇 ShengYu 介紹 Python PyQt5 QGridLayout 網格佈局用法與範例,QGridLayout 能讓 UI 元件網格狀的方式排列,相較於手動排版 QGridLayout 能夠讓開發者很快速方便地讓元件佈局。

以下的 Python PyQt5 QGridLayout 用法與範例將分為這幾部分,

  • PyQt5 QGridLayout 基本用法
  • PyQt5 QGridLayout 跨越多欄或多列

PyQt5 QGridLayout 基本用法

這邊示範 PyQt5 QGridLayout 基本用法,QGridLayout.addWidget() 的 3 種函式參數介紹如下,

1
2
3
QGridLayout.addWidget(QWidget)
QGridLayout.addWidget(QWidget, int row, int column, Qt.Alignment alignment=0)
QGridLayout.addWidget(QWidget, int row, int column, int rowSpan, int columnSpan, Qt.Alignment alignment=0)

以下範例是將多個按鈕放入一個 QGridLayout 網格佈局上,這邊單純的介紹 GridLayout 網格佈局就不示範按下按鈕的事件處理,想要進一步了解按鈕事件的可以參考 PyQt5 QPushButton 按鈕用法與範例這篇,

python-pyqt-qgridlayout.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QGridLayout, QPushButton)

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QGridLayout()
self.setLayout(layout)

self.mybutton1 = QPushButton('button 1', self)
layout.addWidget(self.mybutton1, 0, 0)
self.mybutton2 = QPushButton('button 2', self)
layout.addWidget(self.mybutton2, 0, 1)
self.mybutton3 = QPushButton('button 3', self)
layout.addWidget(self.mybutton3, 0, 2)
self.mybutton4 = QPushButton('button 4', self)
layout.addWidget(self.mybutton4, 1, 0)
self.mybutton5 = QPushButton('button 5', self)
layout.addWidget(self.mybutton5, 1, 1)
self.mybutton6 = QPushButton('button 6', self)
layout.addWidget(self.mybutton6, 1, 2)
self.mybutton7 = QPushButton('button 7', self)
layout.addWidget(self.mybutton7, 2, 0)
self.mybutton8 = QPushButton('button 8', self)
layout.addWidget(self.mybutton8, 2, 1)
self.mybutton9 = QPushButton('button 9', self)
layout.addWidget(self.mybutton9, 2, 2)

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QGridLayout 跨越多欄或多列

上述範例介紹每一個元件都是一個網格單位,但有時需要一個網格單位,例如水平橫跨 3 網格單位或者垂直橫跨 2 網格單位等等需求,
這邊示範將不同的 UI 元件放入一個 QGridLayout 垂直佈局上,分別是 1 個 label 1 個 lineedit 文字輸入框與 5 個 button 按鈕,label 與 lineedit 的用法與範例可以分別參考 PyQt5 QLabel 標籤用法與範例PyQt5 QLineEdit 文字輸入框用法與範例這兩篇,下例中的 mylineedit 是水平橫跨 2 網格單位,mybutton1 是水平橫跨 2 網格單位,而 mybutton2 是垂直橫跨 2 網格單位,

python-pyqt-qgridlayout2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QGridLayout,
QLabel, QLineEdit, QPushButton)

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QGridLayout()
self.setLayout(layout)

self.mylabel = QLabel('Input:', self)
layout.addWidget(self.mylabel, 0, 0)
self.mylineedit = QLineEdit(self)
layout.addWidget(self.mylineedit, 0, 1, 1, 2)
self.mybutton1 = QPushButton('button 1', self)
layout.addWidget(self.mybutton1, 1, 0, 1, 3)
self.mybutton2 = QPushButton('button 2', self)
layout.addWidget(self.mybutton2, 2, 0, 2, 1)
self.mybutton3 = QPushButton('button 3', self)
layout.addWidget(self.mybutton3, 2, 1)
self.mybutton4 = QPushButton('button 4', self)
layout.addWidget(self.mybutton4, 2, 2)
self.mybutton5 = QPushButton('button 5', self)
layout.addWidget(self.mybutton5, 3, 1)

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

以上就是 Python PyQt5 QGridLayout 網格佈局用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Python 新手入門教學懶人包
Python PyQt5 新手入門教學

Python Socket 網路通訊教學

本篇 ShengYu 介紹如何寫 Python Socket 網路通訊程式,在這個網路盛行的時代,網路通訊已成為基礎,想要精通學習網路通訊必須先了解 TCP/IP 協定,其中以 TCP 與 UDP 兩種主要通訊方式最常被使用,接下來教學內容將介紹如何使用 Python Socket API 來搭建一個典型的 TCP & UDP 通訊程式,甚至可以寫出一個聊天室的程式,或者像 LINE 或 Skype 這樣的通訊程式。

以下 Python Socket 教學內容將分為幾部分,分別為:

  • Python Socket TCP Server & Client 基本範例
  • Python Socket UDP Server & Client 基本範例

在 Python 中已經內建好 Socket API 供你使用,所以不需另外安裝額外的套件即可使用,接下來我們就來看看 Python Socket TCP Server 與 Client 的程式怎麼撰寫吧!

Python Socket TCP Server & Client 基本範例

這邊介紹 Python Socket TCP 伺服器端與客戶端的網路通訊程式,TCP 這種連線協議具有可靠性,因為 TCP 協議有重傳的機制,所以收到的封包一定沒有錯誤,也因為 TCP 協議的關係在傳輸速度上會有所犧牲,在稍後會介紹 UDP 無重傳機制,提升傳輸性能,不過 TCP 還是給我們帶來很大的便利性,一般瀏覽網頁的 HTTP 協議就是基於 TCP 的基礎,接下來示範一下簡單的 Python Socket TCP Server 伺服器端程式,

如下例所示,伺服器端一開始建立 socket,socket.AF_INET 表示使用 Internet Protocol 的通訊協定,而 socket.SOCK_STREAM 表示傳輸方式為 TCP,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000

使用 listen() 開始監聽,上限連線數為5,之後進入主迴圈,accept() 等待接受客戶端的連線請求,一旦有客戶端連線的話,就會從 accept() 繼續往下執行,

接著從這個連線 recv() 接收資料與 send() 傳送資料,之後就關閉該連線,之後回到 accept() 等待新的客戶端連線,等到新的客戶端連線連上便跟之前的流程一樣,這樣便是一個完整的 Python Socket TCP 伺服器程式。

python3-socket-tcp-server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(5)

print('server start at: %s:%s' % (HOST, PORT))
print('wait for connection...')

while True:
conn, addr = s.accept()
print('connected by ' + str(addr))

indata = conn.recv(1024)
print('recv: ' + indata.decode())

outdata = 'echo ' + indata.decode()
conn.send(outdata.encode())
conn.close()
s.close()

下面範例是對應的 Python Socket TCP Client 客戶端端程式,如下例所示,客戶端一開始建立 socket,之後 connect() 連線伺服器主機的 host 與 port,
接著使用 send()'hello tcp' 字串發送給伺服器端,然後使用 recv() 接收來至伺服器端的資料,接收到資料後就把它印出來,之後就關閉該連線,

python3-socket-tcp-client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

outdata = 'hello tcp'
print('send: ' + outdata)
s.send(outdata.encode())

indata = s.recv(1024)
print('recv: ' + indata.decode())

s.close()

先執行 TCP 伺服器端程式等著,接著再執行 TCP 客戶端端程式就可以將此範例完整體驗,TCP 伺服器端的輸出如下,

1
2
3
4
5
$ python3 python3-socket-tcp-server.py
server start at: 0.0.0.0:7000
wait for connection...
connected by ('127.0.0.1', 42202)
recv: hello tcp

TCP 客戶端的輸出如下,

1
2
3
$ python3 python3-socket-tcp-client.py 
send: hello tcp
recv: echo hello tcp

以上是最基本的 Python Socket TCP 用法與範例,本篇僅示範客戶端傳送一次資料便關閉連線,詳細的 Python Socket TCP 通訊程式範例可以參考 Python TCP Socket Server/Client 網路通訊教學這篇,裡面還會介紹伺服器端如何對同一客戶端多次通訊,以及客戶端如何不間斷地跟伺服器端溝通。

Python Socket UDP Server & Client 基本範例

這邊介紹 Python Socket UDP 服器端與客戶端的網路通訊程式,UDP 無重傳機制,所以相對於 TCP 來說是傳輸效率較好,但因為不保證資料正確性的關係,意味著必須自己實作資料檢查機制,接下來示範一下簡單的 Python Socket UDP Server 伺服器端程式,

如下例所示,伺服器端一開始建立 socket,socket.AF_INET 表示使用 Internet Protocol 的通訊協定,而 socket.SOCK_DGRAM 表示傳輸方式為 UDP,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000

跟 TCP 不同的是 UDP 不需使用 listen()accept(),直接使用 recvfrom 來接收任何一個 socket 地址的客戶端資料,以及 sendto 傳送資料給指定 socket 位址的客戶端,這邊用一個迴圈不斷地重複 recvfrom() 接收資料與 sendto() 傳送資料,這樣便是一個完整的 Python Socket UDP 伺服器程式。

python3-socket-udp-server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((HOST, PORT))

print('server start at: %s:%s' % (HOST, PORT))
print('wait for connection...')

while True:
indata, addr = s.recvfrom(1024)
print('recvfrom ' + str(addr) + ': ' + indata.decode())

outdata = 'echo ' + indata.decode()
s.sendto(outdata.encode(), addr)
s.close()

下面範例是對應的 Python Socket UDP Client 客戶端端程式,如下例所示,客戶端一開始建立 socket,跟 TCP 不同的是 UDP 不需要 connect() 而是直接用 sendto() 將資料送往指定的主機 host 與 port,
接著使用 sendto()'hello udp' 字串發送給伺服器端,然後使用 recvfrom() 接收來至伺服器端的資料,接收到資料後就把它印出來,之後就關閉該連線,

python3-socket-udp-client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000
server_addr = (HOST, PORT)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

outdata = 'hello udp'
print('sendto ' + str(server_addr) + ': ' + outdata)
s.sendto(outdata.encode(), server_addr)

indata, addr = s.recvfrom(1024)
print('recvfrom ' + str(addr) + ': ' + indata.decode())

先執行 UDP 伺服器端程式等著,接著再執行 UDP 客戶端端程式就可以將此範例完整體驗,TCP 伺服器端的輸出如下,

1
2
3
4
$ python3 python3-socket-udp-server.py 
server start at: 0.0.0.0:7000
wait for connection...
recvfrom ('127.0.0.1', 44346): hello udp

UDP 客戶端的輸出如下,

1
2
3
$ python3 python3-socket-udp-client.py
sendto ('0.0.0.0', 7000): hello udp
recvfrom ('127.0.0.1', 7000): echo hello udp

以上是最基本的 Python Socket UDP 用法與範例,本篇僅示範客戶端傳送一次資料便關閉連線,詳細的 Python Socket UDP 通訊程式範例可以參考 Python UDP Socket Server/Client 網路通訊教學這篇,裡面還會介紹伺服器端如何對同一客戶端多次通訊,以及客戶端如何不間斷地跟伺服器端溝通。

以上就是 Python Socket 網路通訊教學介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其他參考
socket — Low-level networking interface — Python 3 documentation
https://docs.python.org/3/library/socket.html

其它相關文章推薦
Python 新手入門教學懶人包

Python PyQt5 QVBoxLayout 垂直佈局用法與範例

本篇 ShengYu 介紹 Python PyQt5 QVBoxLayout 垂直佈局用法與範例,QVBoxLayout 能讓 UI 元件縱向/垂直/上下的方式排列,相較於手動排版 QVBoxLayout 能夠讓開發者很快速方便地讓元件佈局。

以下的 Python PyQt5 QVBoxLayout 用法與範例將分為這幾部分,

  • PyQt5 QVBoxLayout 基本用法
  • PyQt5 QVBoxLayout 放入不同元件

PyQt5 QVBoxLayout 基本用法

這邊示範 PyQt5 QVBoxLayout 基本用法,這邊示範將三個按鈕放入一個 QVBoxLayout 垂直佈局上,

python-pyqt-qvboxlayout.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QPushButton)

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mybutton1 = QPushButton('button 1', self)
layout.addWidget(self.mybutton1)

self.mybutton2 = QPushButton('button 2', self)
layout.addWidget(self.mybutton2)

self.mybutton3 = QPushButton('button 3', self)
layout.addWidget(self.mybutton3)

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

PyQt5 QVBoxLayout 放入不同元件

這邊示範將不同的 UI 元件放入一個 QVBoxLayout 垂直佈局上,分別是一個 label 與一個 button 按鈕,

由於 label 預設顯示的文字為靠左對齊,為了讓 label 顯示的文字置中對齊這邊使用 QLabel.setAlignment() 指定為 PyQt5.QtCore.Qt.AlignCenter

python-pyqt-qvboxlayout2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout,
QLabel, QPushButton)
from PyQt5.QtCore import Qt

class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('my window')
self.setGeometry(50, 50, 200, 150)

layout = QVBoxLayout()
self.setLayout(layout)

self.mylabel = QLabel('hello world', self)
self.mylabel.setAlignment(Qt.AlignCenter)
layout.addWidget(self.mylabel)

self.mybutton = QPushButton('button', self)
layout.addWidget(self.mybutton)

if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())

結果圖如下,

以上就是 Python PyQt5 QVBoxLayout 垂直佈局用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Python 新手入門教學懶人包
Python PyQt5 新手入門教學

Python UDP Server/Client 網路通訊教學

本篇 ShengYu 介紹如何寫 Python UDP Socket Server/Client 網路通訊程式,在這個網路盛行的時代,網路通訊已成為基礎,想要精通學習網路通訊必須先了解 TCP/IP 協定,除了 TCP 以外,想要自行客製化通訊規則的話就一定要學習 UDP 通訊方式,UDP 通訊程式通常分成伺服器端與客戶端兩部份程式,接下來教學內容將介紹如何使用 socket API 來搭建一個典型的 UDP 通訊程式,甚至可以寫出一個視訊或音訊聊天的程式,或者像 Skype、遠端桌面連線或 TeamViewer 這樣的通訊程式。

以下 Python UDP 內容將分為幾部分,分別為:

  • Python UDP Server/Client 通訊流程
  • Python UDP Server 伺服器端程式 (Echo Sever)
  • Python UDP Client 客戶端程式 (傳送使用者的輸入)
  • Python UDP Client 客戶端程式 (定時傳送資料)
  • Python UDP 常見問題

那麼就開始吧!

Python UDP Server/Client 通訊流程

以下 ShengYu 講解 Python UDP Server 端與 UDP Client 端的程式流程以及會如何使用這些 socket API,
UDP Server 的流程分為以下幾大步驟:

  1. 建立socket:s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM),指定 socket.AF_INET (Internet Protocol) family 的通訊協定,類型使用 socket.SOCK_DGRAM (Datagram Socket) 也就是 UDP 傳輸方式
  2. 綁定 socket 到本地 IP 與 port:s.bind()
  3. 接收客戶端傳來的資料:s.recvfrom()
  4. 傳送給對方發送資料:s.sendto()

跟 TCP 不同的是 UDP Client 不需要 listen()accept(),傳輸完畢後也不需要關閉連線。

UDP Client 的流程分為以下幾大步驟:

  1. 建立 socket:s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  2. 傳送資料:s.sendto()
  3. 接收資料:s.recvfrom()

跟 TCP 不同的是 UDP Client 不需要 connect(),傳輸完畢後也不需要關閉連線。

以上是 UDP Server/Client 通訊的重點流程,實際的 Python socket API 用法與範例詳見下列章節,接下來就來看看怎麼寫 UDP Server/Client 通訊程式吧!

Python UDP Server 伺服器端程式 (Echo Sever)

這邊 ShengYu 就開始介紹怎麼寫 Python UDP Server 程式,下列範例這是一個典型的 Echo Server,Echo Server 就是收到什麼資料就回覆什麼資料,很簡單吧!
跟網路上其他範例不同的是此範例建立連線後不是傳輸一次資料就關閉連線,而是使用迴圈可以一直傳輸資料直到客戶端不想傳關閉連線為止,並且伺服器端再次地等待新的客戶端連線來服務。

如下例所示,伺服器端一開始建立 socket,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000
跟 TCP 不同的是 UDP 不需使用 listen()accept(),直接使用 recvfrom 來接收任何一個 socket 地址的客戶端資料,以及 sendto 傳送資料給指定 socket 位址的客戶端
這邊用一個迴圈不斷地重複 recvfrom 接收資料與 sendto 傳送資料,這樣便是一個完整的 Python UDP 伺服器程式。

本篇範例是以 Python 3 作為示範,另外補充一下 Python 2 與 Python 3 的差異處,
Python 2 的 recvfrom()sendto() 傳入的參數類型為 str
到了 Python 3 的 recvfrom()sendto() 傳入的參數類型改為 byte
所以 str 要轉成 byte 要透過 decode(),以及使用 decode() 轉回 str

python3-udp-socket-server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((HOST, PORT))

print('server start at: %s:%s' % (HOST, PORT))
print('wait for connection...')

while True:
indata, addr = s.recvfrom(1024)
print('recvfrom ' + str(addr) + ': ' + indata.decode())

outdata = 'echo ' + indata.decode()
s.sendto(outdata.encode(), addr)
s.close()

如果 Server 伺服器端不正常關閉後再次啟動時可能會遇到 socket.error: [Errno 98] Address already in use 這種錯誤訊息的話,那麼你可以在 bind() 之前設定 REUSEADDR 可以解決這個問題,

1
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

Python UDP Client 客戶端程式 (傳送使用者的輸入)

先用一個終端機來啟動前述的 UDP 伺服器端的程式,接著再用另一個終端機執行 UDP 客戶端的程式。Python UDP Client 範例如下,這邊要示範的是傳送使用者的輸入訊息,將使用者的輸入訊息傳送給伺服器端,通常應用於一般聊天軟體上,學習之後就可以寫一個簡單的聊天軟體了。

如下例所示,客戶端一開始建立 socket,跟 TCP 不同的是 UDP 不需要 connect() 而是直接用 sendto() 將資料送往指定的主機 host 與 port,
之後進入主迴圈,不斷地傳送使用者的輸入,需要注意的是要取得使用者輸入的函式 Python 2 裡是使用 raw_input(),而 Python 3 是使用 input()
使用者輸入完後按下 Enter 便會將資料發送給伺服器端,接著等待伺服器端傳送資料,接收到來自伺服器端的資料就把它印出來,

python3-udp-socket-client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

HOST = '0.0.0.0'
PORT = 7000
server_addr = (HOST, PORT)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
outdata = input('please input message: ')
print('sendto ' + str(server_addr) + ': ' + outdata)
s.sendto(outdata.encode(), server_addr)

indata, addr = s.recvfrom(1024)
print('recvfrom ' + str(addr) + ': ' + indata.decode())

以下示範一下程式的啟動過程,過程中我在客戶端輸入了兩次的訊息,最後按 ctrl+c 結束了程式,
客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
$ python3 python3-udp-socket-client.py 
please input message: hello
sendto ('0.0.0.0', 7000): hello
recvfrom ('127.0.0.1', 7000): echo hello
please input message: hello udp
sendto ('0.0.0.0', 7000): hello udp
recvfrom ('127.0.0.1', 7000): echo hello udp
please input message: ^CTraceback (most recent call last):
File "python3-udp-socket-client.py", line 12, in <module>
outdata = input('please input message: ')
KeyboardInterrupt

伺服器端輸出結果如下,

server
1
2
3
4
5
$ python3 python3-udp-socket-server.py 
server start at: 0.0.0.0:7000
wait for connection...
recvfrom ('127.0.0.1', 36042): hello
recvfrom ('127.0.0.1', 36042): hello udp

Python UDP Client 客戶端程式 (定時傳送資料)

前一章節示範了 Echo Sever 與 Client 通訊程式,這時可以打鐵趁熱,除了前一章節 UDP Client 使用者手動輸入的情形之外,這邊也介紹另一種客戶端會定時地傳送資料給伺服器端,同時這也適用於各種通訊情形。

步驟跟前一章節 UDP Client 幾乎相同,傳輸字串為 'heartbeat',這邊傳送後使用 time.sleep(1) 來讓程式睡眠1秒,之後再繼續傳送資料,進而達成定時傳送的功能,

python3-udp-socket-client-heartbeat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import time

HOST = '0.0.0.0'
PORT = 7000
server_addr = (HOST, PORT)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
outdata = 'heartbeat'
print('sendto ' + str(server_addr) + ': ' + outdata)
s.sendto(outdata.encode(), server_addr)

indata, addr = s.recvfrom(1024)
print('recvfrom ' + str(addr) + ': ' + indata.decode())

time.sleep(1)

客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python3 python3-udp-socket-client-heartbeat.py 
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
sendto ('0.0.0.0', 7000): heartbeat
recvfrom ('127.0.0.1', 7000): echo heartbeat
^CTraceback (most recent call last):
File "python3-socket-udp-client-heartbeat.py", line 20, in <module>
time.sleep(1)
KeyboardInterrupt

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
8
$ python3 python3-udp-socket-server.py 
server start at: 0.0.0.0:7000
wait for connection...
recvfrom ('127.0.0.1', 58488): heartbeat
recvfrom ('127.0.0.1', 58488): heartbeat
recvfrom ('127.0.0.1', 58488): heartbeat
recvfrom ('127.0.0.1', 58488): heartbeat
recvfrom ('127.0.0.1', 58488): heartbeat

Python UDP 常見問題

在 UDP 的傳輸裡,為什麼伺服器還要回傳給客戶端?
因為這只是個示範用的通訊程式,讓你了解通訊的過程,就像打電話或者跟別人對話一樣,你一句我一句的來回互動,你可以根據實際的需求而修改程式,你也可以改成一直傳,例如客戶端一直傳送,伺服器一直接收。

為什麼 recvfrom 還沒收到資料前會卡住一直等?
因為預設是 blocking 非阻塞模式,recvfrom 還沒收到資料前會卡住一直等,沒法做其他事情,直到 recv 接收到資料才會從 recvfrom 函式返回,解決辦法是改用 Non-blocking 非阻塞模式,Non-blocking 模式是這次沒接收到資料就會從 recvfrom 函式返回,接著繼續往下執行;另一個解決方式是另外建立執行緒去做其他事情。

以上就是 Python UDP Server/Client 網路通訊教學介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其他參考
socket — Low-level networking interface — Python 3 documentation
https://docs.python.org/3/library/socket.html

其它相關文章推薦
下一篇將會介紹如何用 Python 3 提供的 socketserver 來撰寫 server 端的程式,
Python socketserver 伺服器端網路通訊程式