Try   HackMD

L7 Operator (運算子)

tags: Python operator

目錄

• Definition (定義)
• Unary Operator (一元運算子)
• Binary Operator (二元運算子)
• Assignment Operator (指派運算子)
• Arithmetic Operator (算術運算子)
• + (加法)
• - (減法)
• * (乘法)
• / (浮點數除法)
• // (整數除法)
• % (模數)(取餘數)
• ** (指數)
• Compound Assignment Operator (複合指定運算子)
• Relational Operator (關係運算子)
• Logical Operator (邏輯運算子)
• not (非)
• and (且)
• or (或)
• combination (結合使用)
• Bitwise Operator (位元運算子)
• & (且)
• | (或)
• ^ (異或)
• ~ (非)
• Shift Operator (位移運算子)
• Membership Operator (成員運算子)
• Identity Operator (身分運算子)
• Operator Precedence (運算子優先序位)

• Definition (定義)

在寫程式的時候難免會需要做運算,這個時候基本的算術運算子就派上用場,當然還有其他用來做邏輯、關係判斷等等的運算子,讓我們先來看看下面的一個式子:

2 + 2

這是一個很基本的運算,其中的+就是一個運算子,而2就是運算元,這也是我們等等要講到一元和二元的關係。


• Unary Operator (一元運算子)

什麼是一元運算子呢? 它代表的就是只需要一個運算元就可以表示意義的運算子,舉例來說:

print(+6) # 6 print(-2) # -2

像我們用來判斷正負號的+-就是一元運算子。

在第一行的+代表數值為正數,但通常都會被省略掉,而第二行的-就賦予了數值負數的意義,而他們都只需要一個運算元就能有意義。


• Binary Operator (二元運算子)

像定義裡面的算式就是一個基本的二元運算子的運用,大部分的運算子也屬於這個範疇。

print(2 - 2) # 0

就是一個運算子要和兩個運算元配合才有意義


• Assignment Operator (指派運算子)

相信這個運算子大家應該都很熟悉了,就是我們在指派變數時所使用的=

int_num = 10 string = "Hello World" float_num = 12.5 boolean = False

像上面基本的指派變數大家應該已經得心應手了,這邊另外補充一個build-in function: id(),他可以用來查看物件在記憶體中的位址,這邊的物件和我們在L5 Print()講到的是一樣的。

id(object) -> int

舉例來說:

a = 1.0 b = 2 print(id(1.0), id(2)) # 1590326436144 1590325412112 print(id(a), id(b)) # 1590326436144 1590325412112

這邊的位址每個人的環境系統不同結果也會不同,是正常的,像上面的結果你可以看到,直接輸入或者變數名稱都會得到同樣的位址,所以這個時候我希望你們有個觀念,在指派變數的時候,我們的變數會去指向那個值在記憶體中的位址

再看一個範例:

a = "Hello" b = a print(id(a), id(b)) # 3095031449584 3095031449584

在上面我將Hello指派給a,再將a指派給b,因此這個時候ba都會指向Hello在記憶體中的位址。

以上是關於id()的簡介,接下來我們這邊的重點是值的交換

a = 10 b = 20 print("Before Swap:", a, b) # Before Swap: 10, 20 print(id(a), id(b)) # 2328313987600 2328313987920 a, b = b, a # Swap print("After Swap:", a, b) # After Swap: 20, 10 print(id(a), id(b)) # 2328313987920 2328313987600

可以看到Python交換變數的值只需要一行就能完成,第五行的意思是同時將b指派給a,又將a指派給b= 兩邊是對等的,所以這時候你可以發現ab他們指向的位址做了交換。

這也是Python和其他程式語言不同的地方,舉C++為範例:

int a = 10; int b = 20; int temp; // hold temporary value temp = b; // assign 20 to temp b = a; // assign 10 to b, if we didn't use temp to store 20, we will lose it a = temp; // assign 20 we have stored before swap to a

上面的程式碼各位可以不用太了解和Python不一樣的部分,但邏輯應該可以看懂,可以注意到它如果想要交換變數的的話是需要先用一個暫時的變數來去儲存其中一個值,因為在另一個被指派到先前儲存起來的變數的時,會覆蓋掉原本的值,假如我們沒有先將其儲存保留起來,我們就會失去那個

所以像Python這樣的功能是很方便的,其中的原理在之後講到Tuple (元組)的時候會提到。


• Arithmetic Operator (算術運算子)

算術運算子,顧名思義就是讓我們在做計算時可以使用的運算子,像我們平常會使用的 + - * / 就是很常見的算術運算子。


• + (加法)

加法可以用在數值或字串型態,在數值上使用它就會表示數學上的相加,用在字串上他就會表示文字的串接

num1 = 10 num2 = 20 addition = num1 + num2 print(addition) # 30 str1 = "Hello " str2 = "World!" concatenation = str1 + str2 print(concatenation) # Hello World!

• - (減法)

減法就目前講到的就是用在數值上表示數學上的相減

int_num1 = 10 int_num2 = 20 substraction = int_num1 - int_num2 print(substraction) # -10 float_num1 = 10.5 float_num2 = 5.3 substraction = float_num1 - float_num2 print(substraction) # 5.2

• / (浮點數除法)

在Python中比較特別的是它有兩種除法運算子,這邊講的是浮點數除法,顧名思義,他回傳值的型態為浮點數。

int_num1 = 20 int_num2 = 10 float_division = int_num1 / int_num2 print(float_division) # 2.0 int_num1 = 25 float_num2 = 0.6 float_division = int_num1 / float_num2 print(float_division) # 41.66666666666667

在上述的程式碼中可以看到不論你是用純整數還是整數及浮點數做運算,回傳的結果都會是浮點數,這就是浮點數除法的關鍵。


• // (整數除法)

剛剛講了浮點數除法,現在有一個新的運算子由兩個/組成,他代表的是整數除法,意思就是計算的結果會是捨去小數部分並向下取整的數值,但是這邊要注意的是和浮點數除法不同,假如其中一個運算元為浮點數型態,回傳的結果就會是浮點數,而非整數,但重點來了,假如兩個運算元皆為整數型態,就算除法不能整除,結果也會捨去小數點部分。

舉例來說:

int_num1 = 24 int_num2 = 7 floor_division = int_num1 // int_num2 print(floor_division) # 3 int_num1 = 24 float_num2 = 7.0 floor_division = int_num1 // float_num2 print(floor_division) # 3.0

在上述的程式碼中可以看到如果兩個運算元皆為整數型態,那麼結果就會是做完除法後的部分,而型態也為整數,但是只要其中一個或兩個運算元為浮點數型態,那麼結果雖然也會是的部分,並不會包含小數部分,但會是浮點數型態


• % (模數)(取餘數)

這邊這個也是一個新的運算子,它代表著取餘數,這樣講起來比較抽象,我們可以直接來看範例:

int_num1 = 14 int_num2 = 5 modulus = int_num1 % int_num2 print(modulus) # 4 float_num1 = 16.4 int_num2 = 6 modulus = float_num1 % int_num2 print(modulus) # 4.399999999999999

在上述的程式碼中可以看到當兩個運算元為整數型態時,做餘數運算的時候,就會在做完除法後,將餘數的部分做為回傳的結果,所以在第一部分的運算時

14 / 5 = 2 ... 4

得到的結果就是餘數,也就是 4

而第二部分的運算使用到浮點數

16.4 / 6 = 2 ... 4.4

至於結果顯示為4.399999999999999是因為當進行浮點數運算時,浮點數會有精度的問題,由於不能精確顯示4.4,所以就使用有限的位數來表示該浮點數,有誤差是正常的。

這邊的 / // % 都有一個特性,就是第二個運算元不可為0,用數學的觀點來看就是分母不可為0,否則就會產生ZeroDivisionError


• ** (指數)

在Python中我們可以直接使用**來表示次方,舉例來說:

print(2 ** 3) # 8 print(3 ** 4) # 81 print(2.5 ** 3.5) # 24.705294220065465

在上述的程式碼中,在**左邊的為底數,右邊的為次方,結果就如普通數學運算一樣,比較特別的是這邊可以接受底數或次方為浮點數。

這邊需要注意的是**和其他大部分運算子不同,他是right-left associativity,也就是從右看到左,怎麼說呢,像其他運算子我們就是從左開始往右做運算,但**就必須反過來,舉例來說:

print(2 ** 3 ** 2) # 512 print(2 ** (3 ** 2))

在上述的程式碼中如果我們從左到右來做運算的話

2 ** 3 ** 2 --> 8 ** 2 --> 64 

就會是錯誤的結果,因此正確的順序應該是像第二行那樣,先算右邊的部分再算左邊的部分

從數學的觀點來看就是要讀成: x(yz) 而非 (xy)z


• Compound Assignment Operator (複合指定運算子)

甚麼是複合指定運算子呢,他是一種結合了指定運算子和其他運算子的新用法。舉例來說:

num = 10 num = num + 1 print(num) # 11

在上述的程式碼中,我先指派10num,然後我希望他可以+1後重新指派回原本的變數,所以第二行的意思是先將num的值+1,再將新的值重新指派回num,所以這時候num的值就會是11

但是這種寫法我們會重複寫變數的名稱,當數量多起來的時候就會相當繁瑣,因此我們就會使用複合指定運算子

num = 10 num += 1 print(num) # 11

上面的num += 1num = num + 1是相同的意思,但前者寫起來就比較簡潔。

當然他也不只能用在加法中,也能活用在其他運算子中。

num = 5 num -= 2 print(num) # 3 num *= 10 print(num) # 30 num /= 2 print(num) # 15.0 num //= 2 print(num) # 7.0 num %= 4 print(num) # 3.0 num **= 3 print(num) # 27.0

• Relational Operator (關係運算子)

關係運算子是Python中用來比較兩個之間關係的運算子,包括== != > < >= <=這六種運算子。

由於他們的做比較產生的結果只會有成立或者不成立,因此輸出的結果只會是TrueFalse

以下是範例:

a = 5 b = 7 # 等於運算子(==) print(a == b) # False # 不等於運算子(!=) print(a != b) # True # 大於運算子(>) print(a > b) # False # 小於運算子(<) print(a < b) # True # 大於等於運算子(>=) print(a >= b) # False # 小於等於運算子(<=) print(a <= b) # True

上述範例中,我們先使用變數指派的方式分別將a設為5b設為7。接著,我們使用各種不同的關係運算子來比較a和b之間的大小關係,並將結果輸出。

例如,!=用來比較a和b是否不相等,因為a不等於b,所以結果為True。另一方面,<用來比較a是否小於b,因為a比b小,所以結果為True


• Logical Operator (邏輯運算子)

邏輯運算子就可以將前面的運算子結合在一起變成運算式做判斷,其中分別有not and or


• not (非)

not就是會回傳當前結果的相反布林值,舉例來說:

a = 5 b = 10 c = 0 print(not a < b) # False print(not c) # True

在上述的程式碼中,因為not的序位較低,因此會先做運算式在做邏輯判斷,像是第1行就會是

print(not True) # False

第2行比較特別,還記得L3 Type casting中講到,只要不為0或空,轉換成布林值的結果就會是True,反之為False,所以not c的結果就會是not False,所以得到的結果就會是True


• and (且)

and就是在所有運算的結果皆成立的情況下,就會回傳True,否則回傳False

舉例來說:

a = 5 b = 10 print(a >= 5 and b * 2 < 30) # True print(a < b and a ** 2 == 25 and a < 1) # False

在上述的程式碼中,由於and的判斷序位較其他運算子低,所以電腦會先做其他運算在做出邏輯判斷,就第3行而言

a >= 5 --> True
b * 2 < 30 --> True

他會先判斷好運算條件在判斷在邏輯運算子中應該產生甚麼結果,因此會變成

print(True and True) # True

而在第4行中

a < b --> True
a ** 2 == 25 --> True
a < 1 # False

結果就會變成

print(True and True and False) # False

這邊要另外補充運算元皆為數值型態時的情況

print(1 and 5 and 7) # 7 print(1 and 0 and 2) # 0

在第1行中,假如沒有任何運算式的結果為False,那麼and得到的結果就會回傳最後一個運算元。

這個原理是因為and需要做到最後一個運算式的判斷才能確認結果,除非先碰到0,就會停止繼續判斷並回傳0,就像第2行一樣。

只要碰到一個運算式的結果為False,則停止繼續判斷接下來的運算式並回傳False


• or (或)

or就是只要其中一個運算的結果成立的情況下,就會回傳True,如果所有運算的結果皆不成立則回傳False

舉例來說:

a = 5 b = 10 print(a >= 5 or b * 2 == 30) # True print(a >= b or a ** 2 != 25 or a < 1) # False

在上述的程式碼中,由於or的判斷序位較其他運算子低,所以電腦會先做其他運算在做出邏輯判斷,就第3行而言

print(True or False) # True

而在第4行中

print(False or False or False) # False

這邊也另外補充運算元皆為數值型態時的情況

print(0 or 0 or 0) # 0 print(0 or 3 or 2) # 3

在第1行中,假如所有運算式的結果皆為False,那麼or得到的結果就會回傳0

其中原理是因為or要碰到非0的值才能確認結果,假如先碰到第一個非0的值,就會停止繼續判斷並回傳那個值,就像第2行一樣。

只要碰到一個運算式的結果為True,則停止繼續判斷接下來的運算式並回傳True


• combination (結合使用)

現在我們要知道當不同邏輯運算子在同一個運算式時該如何判斷以及他們的順序: not > and > or,當我們做判斷時會以這個順序去判斷。

舉例來說:

x = 4 y = 3 print(x >= y or x != y and not x ** 2 > y) # True

我們可以這樣分解

print(x >= y or x != y and not True) print(x >= y or x != y and False) print(x >= y or True and False) print(x >= y or False) print(True or False) print(True)

一步步按照順序去判斷每一個運算式的結果。


• Bitwise Operator (位元運算子)

我們的資料儲存在電腦中就是以位元的形式存在,而我們也可以透過位元運算子針對數值的位元去做運算,而操作的部分是用二進位來表示,如果不會進位法的一樣可以去看我之前IG的文章:


• & (且)

這邊的&雖然也是and的意思,但並不是針對邏輯而是針對位元作運算,舉例來說:

print(5 & 3) # 1

這邊讓我們換成二進位來看:

   0101 --> 5
&  0011 --> 3
--------------
   0001 --> 1

&會去看數值二進位中每一位數,只有兩者皆為1,傳出的位元才會是1,於是就產生了上面的結果。


• | (或)

這邊的|雖然也是or的意思,但並不是針對邏輯而是針對位元作運算,舉例來說:

print(5 | 3) # 7

這邊讓我們換成二進位來看:

   0101 --> 5
|  0011 --> 3
--------------
   0111 --> 7

|會去看數值二進位中每一位數,只要其中一個為1,傳出的位元就會是1,於是就產生了上面的結果。


• ^ (異或)

這邊的^是比較特別的新運算子,舉例來說:

print(5 ^ 3) # 6

這邊讓我們換成二進位來看:

   0101 --> 5
^  0011 --> 3
--------------
   0110 --> 6

^會去看數值二進位中每一位數,只要其中兩邊的位元不同,就會回傳1,反之兩邊相同傳出的位元就會是0,於是就產生了上面的結果。


• ~ (非)

這邊這個~比較特別,他會去取數值的1補數,如果不知道補數是甚麼的話可以試著上網查詢相關資料。

print(~5) # -6

這邊讓我們換成二進位來看:

   0101 --> 5
~  1010 --> -6

換成1補數的方法就是將其位元翻轉,如果實在不知道該怎麼算的話可以這樣記-(x + 1)。因為這運算子我自己在寫的時候幾乎也不會用到,所以影響不大。


• Shift Operator (位移運算子)

這邊和位元運算子有點相似,但是本身是運用在位元向右或向左推移的情況,舉例來說:

print(6 >> 1) # 3

這邊讓我們換成二進位來看:

      0110 --> 6
>> 1  0011 --> 3

上述的程式碼就是將6換成二進位後將每個位元往右推移一單元,而多出來的部分則捨去,於是就得到了3

左移也是同理

print(6 << 2) # 24

這邊同樣讓我們換成二進位來看:

      0000 0110 --> 6
<< 2  0001 1000 --> 24

這邊就是將其位元向左推移兩單元,於是得到24

但是很顯然的,當數字越來越大的時後,我們總不能每次都把它換成位元來計算,所以大家可以想想二進位的通性,就會知道當我們在左移和右移的時候到底做了怎樣的運算

6 >> 4 --> 6 // 2 ** 4
5 << 2 --> 5 * 2 ** 2

從上面兩個範例可以知道,右移就是base // 2 ** n,而左移就是base * 2 ** n,掌握這個算法當數字很大的時候也可以直接計算了。

關於位元的運算幾乎都是對整數做操作,而非浮點數


• Membership Operator (成員運算子)

成員運算子顧名思義就是用來判斷某個元素是否在另一個元素中,主要使用的為in,在邏輯上看起來也符合平常使用的語法,較淺顯易懂,舉例來說

print("M" in "Matcha") # True print("m" in "Matcha") # False print("m" not in "Matcha") # True

上述的程式碼中第1行會判斷字串M是否在字串Matcha中,因條件成立,所以結果為True

相對的第2行m並不在字串Matcha中,所以結果為False

而第3行我們將其與not結合使用,判斷m是否不在字串Matcha中,所以結果為True


• Identity Operator (身分運算子)

這邊的身分運算子is就要和關係運算子中的==做比較了,我們這邊的重點會放在他們兩個的差別,

首先,is比較的是兩個物件的記憶體位址,這時候請想起id()這個內建函式

num1 = 1.0 num2 = 2.0 print(id(num1), id(num2)) # 1948752781616 1948751772944 print(num1 is num2) # False print(num1 == num2) # False

在上述的程式碼中,num1num2無論是值還是記憶體位址皆不同,所以用is或是==的結果皆為False

但如果我們換一個情況

num1 = 3.0 num2 = 3 print(id(num1), id(num2)) # 1913265234224 1913264210224 print(num1 is num2) # False print(num1 == num2) # True

在上述的程式碼中,num1num2雖然一個是整數,一個是浮點數,但是在做值的判斷時會是相同的,然而整數和浮點數的記憶體位址是不同的,所以這時候is就可以判定出來num1num2並不相同,他們並非同一個物件

反之,如果我們使用完全相同的值

num1 = 3 num2 = 3 print(id(num1), id(num2)) # 2893016924464 2893016924464 print(num1 is num2) # True print(num1 == num2) # True

兩個變數指向的記憶體位址相同值也相同,所以在is==的結果皆為True

如果需要嚴格的判定時可以考慮使用is而非==


• Operator Precedence (運算子優先序位)

這邊提供運算子優先序位的表格

Precedence Operator
1 ()
2 **
3 +x, -x, ~x
4 *, /, //, %
5 +, -
6 >>, <<
7 &
8 ^
9 |
10 ==, !=, >=, <=, >, <, is, is not, in, not in
11 not
12 and
13 or

其實不用特別去記,用久了就會習慣了,如果真的不知道誰先誰後就用()把要先做的括起來。


• 希望這些筆記可以幫到你 •

如果有興趣了解更多歡迎追蹤我的
Intagram
Youtube
Github

也可以幫我按個讚賞

上一篇: L6 Input() (輸入)