ในไพธอนเวลาที่จะสร้างหรือเรียบเรียงสายอักขระนั้นมี ๓ วิธีที่ใช้ได้คือ
- '%s%s'%(a,b) เป็นวิธีดั้งเดิม ใช้รูปแบบเหมือน printf ของภาษาซี มีใช้ในหลายภาษา
- '{}{}'.format(a,b) เป็นรูปแบบเฉพาะของภาษาไพธอน แย่การเขียนค่อนข้างเยิ่นเย้อ
- f'{a}{b}' วิธีนี้มีชื่อเรียกเฉพาะว่า f-string เป็นวิธีใหม่ที่เพิ่มเข้ามาในไพธอน 3.6 ไม่สามารถใช้ในไพธอนรุ่นเก่าได้
ทั้ง ๓ วิธีนั้นก็มีข้อดีข้อเสียต่างกันไป จะเลือกวิธีไหนก็อาจแล้วแต่คนถนัด
ในที่นี้จะขอพิจารณาเทียบในเรื่องของความเร็วดูว่าถ้าใช้ ๓ วิธีนี้ทำสิ่งเดียวกัน อย่างไหนจะดีกว่าในแง่ประสิทธิภาพ
ผลการเปรียบเทียบจะต่างกันออกไปขึ้นอยู่กับชนิดข้อมูลที่ใช้แปลง ในที่นี่ลองสอดสอบกรณีดังนี้
- เอาสายอักขระมาต่อเฉยๆ (%s, {})
- เอา int มาแปลงเป็นสายอักขระ (%d, {})
- เอา int เต็มมาแปลงเป็นสายอักขระโดยมีการเติม 0 ตามจำนวนที่กำหนด (%010d, {:010})
- เอา float มาแปลงเป็นสายอักขระโดยเขียนแบบเอ็กซ์โพเนนเชียลโดยไม่กำหนดความยาวเลขทศนิยม (%e, {:e})
- เอา float มาแปลงเป็นสายอักขระโดยเขียนแบบเอ็กซ์โพเนนเชียลโดยกำหนดความยาวเลขทศนิยม (%.10e, {:.e})
- เอา float มาแปลงเป็นสายอักขระโดยไม่กำหนดความยาวเลขทศนิยม (%f, {:f})
- เอา float มาแปลงเป็นสายอักขระโดยกำหนดความยาวเลขทศนิยม (%.10f, {:.f})
- เอา float มาแปลงเป็นสายอักขระโดยไม่ระบุชัด (%s, {})
- เอา None มาแปลงเป็นสายอักขระ
- เอา list แปลงเป็นสายอักขระ
โค้ดสำหรับทดสอบเขียนดังนี้
import time
import numpy as np
import matplotlib.pyplot as plt
tt =[]
n = 100000
t = [[],[],[]]
for j in range(5):
a = 'กข'
b = 'คง'
t0 = time.time()
for i in range(n):
'%s%s'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{}{}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a}{b}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = 10
b = 200
t0 = time.time()
for i in range(n):
'%d%d'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{}{}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a}{b}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = 10
b = 200
t0 = time.time()
for i in range(n):
'%010d%010d'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{:010}{:010}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a:010}{b:010}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = 1.522555
b = 3.125
t0 = time.time()
for i in range(n):
'%e%e'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{:e}{:e}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a:e}{b:e}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = 1.522555
b = 3.125
t0 = time.time()
for i in range(n):
'%.10e%.10e'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{:.10e}{:.10e}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a:.10e}{b:.10e}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = 1.522555
b = 3.125
t0 = time.time()
for i in range(n):
'%f%f'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{:f}{:f}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a:f}{b:f}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = 1.522555
b = 3.125
t0 = time.time()
for i in range(n):
'%.10f%.10f'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{:.10f}{:.10f}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a:.10f}{b:.10f}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = 1.522555
b = 3.125
t0 = time.time()
for i in range(n):
'%s%s'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{}{}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a}{b}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = None
b = None
t0 = time.time()
for i in range(n):
'%s%s'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{}{}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a}{b}'
t[2].append(time.time()-t0)
tt.append(t)
t = [[],[],[]]
for j in range(5):
a = list(range(1,6))
b = list(range(11,16))
t0 = time.time()
for i in range(n):
'%s%s'%(a, b)
t[0].append(time.time()-t0)
t0 = time.time()
for i in range(n):
'{}{}'.format(a, b)
t[1].append(time.time()-t0)
t0 = time.time()
for i in range(n):
f'{a}{b}'
t[2].append(time.time()-t0)
tt.append(t)
tt = np.array(tt)/n*1e6
plt.figure(figsize=[6.5,6])
plt.subplot(211)
for i,c in enumerate(['#ffaaaa','#aaffaa','#aaaaff']):
plt.bar(np.arange(10)+(i-1)/4.,tt[:,i].mean(1),width=0.25,yerr=tt[:,i].std(1),capsize=4,color=c,ecolor='#332255')
plt.legend(['ใช้ %','ใช้ format','ใช้ f-string'],prop={'family':'Tahoma'})
plt.ylabel('เวลา (ไมโครวินาที)',family='Tahoma')
plt.xticks([])
plt.subplot(212)
for i,c in enumerate(['#ffaaaa','#aaffaa','#aaaaff']):
plt.bar(np.arange(10)+(i-1)/4.,(tt[:,i]/tt[:,0]).mean(1),width=0.25,yerr=(tt[:,i]/tt[:,0]).std(1),capsize=5,color=c,ecolor='#332211')
plt.ylabel('จำนวนเท่าของแบบใช้ %',family='Tahoma')
plt.xticks(np.arange(10),['s','d','010d','e','.10e','f','.10f','f>>s','None','list'])
plt.tight_layout()
plt.show()
ผลที่ได้
สรุปได้ว่าโดยรวมแล้ววิธีการดั้งเดิมอย่างการใช้ % นั้นเร็วที่สุด ส่วน .format นั้นยังไงก็ช้ากว่ากว่า % ทุกกรณี
ส่วน f-string นั้นจะเร็วมากในกรณีที่ป้อนสายอักขระโดยตรง แต่พอแปลง int กลับจะเร็วพอๆกันกับการใช้ %
แต่เมื่อมีการกำหนดให้เติมเลข 0 แบบนี้ f-string จะทำงานช้าลงมาก ช้ายิ่งกว่า .format เสียอีก
สำหรับการแปลง float นั้น f-string ช้าสุดในทุกกรณี
ดังนั้นหากคำนึงถึงความเร็ว การใช้ % ยังเร็วที่สุด
แต่ f-string อาจเหมาะเวลาที่ต้องการป้อนข้อมูลที่เดิมทีเป็นสายอักขระ เพราะเขียนได้กระชับสั้นที่สุดและยังเร็วที่สุด
อีกทั้ง อันนี้แค่เทียบในแง่ความเร็วเป็นหลัก แต่หากพูดถึงความสามารถ f-string สามารถทำอะไรบางอย่างที่ไม่สามารถใช้ % ทำได้ เช่น แทรกจุลภาคลงใประหว่างตัวเลข
a = 123456789
print(f'{a:,}') # ได้ 123,456,789
ยังไงก็อาจแยกใช้ตามความเหมาะสม ขึ้นอยู่กับว่าต้องการใช้ทำอะไร