從 JavaScript 角度學 Python(12) - 運算子

前言

運算子是一個非常常用的方法,因此在基礎觀念中也是絕對閃不了的。

運算子

最基本的運算子不外乎就是 + (加)、- (減)、* (乘)、/ (除) 這幾個運算子,而這幾個運算子又稱之為算數運算子,在 JavaScript 中的寫法也不會與 Python 有太大的差異。

JavaScript:

1
2
3
4
console.log(1 + 1); // 2
console.log(2 * 2); // 4
console.log(8 - 2); // 6
console.log(16 / 2); // 8

Python:

1
2
3
4
print(1 + 1) # 2
print(2 * 2) # 4
print(8 - 2) # 6
print(16 / 2) # 8.0

而上面又稱之為四則運算,但是唯獨除法你可以看到 Python 是輸出一個浮點數(float)。

疑?你注意到了嗎?Python 的除法竟然輸出一個浮點數

WT...

如果你寫過 Python2 的人可能會更 WT…

所以這邊先讓我插入一個小片段說明一下 Python2 與 Python3 之間的差異。

Python2 與 Python3 的除法運算子比較

這是一個比較特別的的地方,在原本的 Python2 的除法會是一個整數:

1
2
# Python2
print(16 / 2) # 8

雖然看起來沒有什麼太大問題,但是如果你要做較精準的系統開發時,其實這就會有問題,因為 Python2 的除法會無條件捨去小數點,因此 Python2 的又稱之為 floor division 或 integer division(整數除法?應該是這樣翻,有錯的話再跟我說一下)。

那就如同前面所言,如果我們要做比較精準處理時,就會有一些問題:

1
2
# Python2
print(16 / 3 == 16.0 / 3.0) # False

嚇到了嗎?如果沒有嚇到的話,我倒是嚇到了,畢竟這兩者應該是相同的才對,這個原因是因為 Python2 的除法運算子本身具有兩種能力,也就是 Floating point division 與 integer division 的能力,只要你針對數字加上小數點就會被轉換成浮點數除法。

那麼為了解決這個問題,Python3 之後將 / 運算子只保留 Floating point division 的能力也就是浮點數除法,所以在 Python3 結果就會變成以下:

1
2
# Python3
print(16 / 3) # 5.333333333333333

那麼因為這個修正的關係,我們在做較精準的計算上就可以比較安全:

1
2
# Python3
print(16 / 3 == 16.0 / 3.0) # True

如果你想更深入了解的話,建議你可以閱讀這一篇 「PEP 238 – Changing the Division Operator」官方文件。

好吧,這時候你應該會想說「如果我就是想要整數除法的話,該怎麼做呢?」

所以就讓我們繼續接著看下去吧~

讓我們接著看下去

// operator

由於 Python3 的修正關係,因此如果你依然想要做到整數除法的話,你可以使用 // 運算子:

1
print(16 // 3) # 5

雖然 // 運算子可以做到原本 Python2 類似的整數除法效果:

1
2
print(16 // 2) # 8
print(type(16 // 2)) # int

但是這邊問題來了,你可以試著思考一下以下程式碼結果為何:

1
2
3
print(100 / 3, type(100 / 3)) # ?, ?
print(100 // 3, type(100 // 3)) # ?, ?
print(100. // 3, type(100. // 3)) # ?, ?

緊張刺激了嗎?讓我們來看看結果吧!

1
2
3
print(100 / 3, type(100 / 3)) # 33.333333333333336, float
print(100 // 3, type(100 // 3)) # 33, int
print(100. // 3, type(100. // 3)) # 33.0, float

盔甲中箭

簡單來講 // 具備與 Python2 相同的能力,同時具有整數除法浮點數除法的能力,所以在使用上就要多加小心。

合併串列

這邊也額外提一下關於加號運算子的部分,加號運算子其實滿特別的。

為什麼這樣講呢?舉例來講如果你想把兩個陣列合併的話在 JavaScript 中最簡單的就是使用 concat() 函式:

1
2
3
4
var a = [1, 2, 3];
var b = [4, 5, 6];
var c = a.concat(b);
console.log(c); // [ 1, 2, 3, 4, 5, 6 ]

另一種比較進階的作法則是使用 ES6 展開運算子:

1
2
3
4
var a = [1, 2, 3];
var b = [4, 5, 6];
var c = [...a, ...b];
console.log(c); // [ 1, 2, 3, 4, 5, 6 ]

如果你用加號運算子就會是另一種結果:

1
2
3
4
var a = [1, 2, 3];
var b = [4, 5, 6];
var c = a + b;
console.log(c); // "1,2,34,5,6"

至於原因的話,可以詳見我先前寫的 筆記 有提到此觀念,所以這邊就不多述了。

那麼 Python 呢?Python 就比較特別一點,Python 可以使用 加號運算子 快速達到這個陣列合併這個需求:

1
2
3
4
a = [1, 2 ,3]
b = [4, 5, 6]
c = a + b
print(c) # [1, 2, 3, 4, 5, 6]

超簡單的對吧!

這邊也簡單提一下另一種合併陣列的方式,也就是前一章節所提到的 extend(),但是這邊要注意 extend() 會修改原有的陣列,因此並不會回傳結果到新的變數上,這一點一定要注意一下:

1
2
3
4
5
a = [1, 2 ,3]
b = [4, 5, 6]
c = a.extend(b)
print(c) # None
print(a) # [1, 2, 3, 4, 5, 6]

最後這邊還有另一種更簡短加號運算子寫法,但是它與 extend 的效果是一樣的,它是修改原始的串列在賦予回去:

哦對了!Python 還有一些好玩的地方,例如你可以使用乘法運算子讓字串加倍:

1
print('Ray' * 4) # RayRayRayRay

甚至是串列與元組的加倍也可以:

1
2
3
4
print(('https://www.facebook.com/israynotarray', '/') * 4) # ('https://www.facebook.com/israynotarray', '/', 'https://www.facebook.com/israynotarray', '/', 'https://www.facebook.com/israynotarray', '/', 'https://www.facebook.com/israynotarray', '/')

# 元組
print(['https://www.facebook.com/israynotarray', '/'] * 4) # ['https://www.facebook.com/israynotarray', '/', 'https://www.facebook.com/israynotarray', '/', 'https://www.facebook.com/israynotarray', '/', 'https://www.facebook.com/israynotarray', '/']

那麼在四則運算中,其實還有一些比較進階一點的用法,例如次方:

1
2
print(10 ** 2) # 100
print(12 ** 2) # 144

其他還有常見的運算子,例如:大於(>)、小於(<)、大於等於(>=)、小於等於(<=)等等,這邊就只是列出四則運算多部份而已。

運算子的優先性與相依性問題

最後我想聊一個在 JavaScript 面試考題很常出現的問題,在 JavaScript 我們知道有所謂的優先性與相依性的觀念,如果你不清楚的話,我會建議可以參考這一篇 JavaScript 核心觀念(16)-運算子、型別與文法-優先性及相依性 文章。

那麼以 JavaScript 中很經典的題目就是 console.log(1 < 2 < 3); // true 但是反之若變成了 console.log(3 > 2 > 1); // false 結果就不同了。

所以這邊也想探討看看 Python 也會有這個問題嗎?其實我們可以實際試試看:

1
2
print(1 < 2 < 3) # True
print(3 > 2 > 1) # True

可以看到結果都是 True,這樣代表 Python 沒有優先性與相依性的問題嗎?

其實是有的。

Python 文件 中有講到這句話

请注意比较、成员检测和标识号检测均为相同优先级,并具有如 比较运算 一节所描述的从左至右串连特性。

因此你也可以在上面連結中看到底下這一張圖,最上方優先性最高,最下方則是優先性最低:

運算子的優先序

所以前面的範例 print(3 > 2 > 1) # True 範例程式碼它到底是怎麼運作的呢?

我們可以依照官方文件的解釋來推敲一二:

例如 x < y <= z 等价于 x < y and y <= z,除了 y 只被求值一次(但在两种写法下当 x < y 值为假时 z 都不会被求值)。

透過上面的解釋,我們可以得知範例程式碼在運作的模式其實是 3 > 22 > 1,這邊要注意中間是 & 符號,代表著兩邊若有一邊是 False 就會整個回傳 False,例如:

1
print(3 > 2 > 4) # False

如果用比較白話一點方式來看的話,結果就會像是 3 > 2 & 2 > 4

那我們今天運算子的章節就先到這邊結束囉~

參考文獻

作者的話

上網找到了一篇「絕對不會失敗的溏心蛋」,結果實作之後還是失敗了 Orz,實在太過不熟…但是不得不說有機雞蛋的蛋腥味真的還好呢!

關於兔兔們

兔法無邊