Python 模數實踐:如何使用 % 運算符(python是什么意思)
目錄
數學中的模數
Python 模運算符基礎知識
帶 int 的模運算符
帶浮點數的模運算符
帶負操作數的模運算符
模運算符和 divmod()
模運算符優先級
實踐中的 Python 模運算符
如何檢查一個數是偶數還是奇數
如何在循環中以特定間隔運行代碼
如何創建循環迭代
如何轉換單位
如何確定一個數是否為質數
如何實現密碼
Python 模運算符高級用途
使用帶有 decimal.Decimal 的 Python 模運算符
在自定義類中使用 Python 模運算符
結論
Python 支持多種算術運算符,您可以在代碼中處理數字時使用這些運算符。這些運算符之一是模運算符(?%),它返回兩個數字相除的余數。
在本教程中,您將學習:
如何模工作在數學
如何使用具有不同數字類型的 Python 模運算符
Python 如何計算模運算的結果
如何.__mod__()在您的類中覆蓋以將它們與模運算符一起使用
如何使用 Python 模運算符解決實際問題
Python 模運算符有時會被忽略。但是對這個運算符有一個很好的理解會給你在你的 Python 工具帶中提供一個非常寶貴的工具。
數學中的模數
術語模來源于數學叫的一個分支模運算。模算術處理具有一組固定數字的圓形數軸上的整數算術。在此數軸上執行的所有算術運算在達到某個稱為模數的數字時都會回繞。
模算術中模數的一個典型例子是十二小時制。十二小時制有一組固定的值,從 1 到 12。在對十二小時制計數時,您數到模數 12,然后返回到 1。十二小時制可以歸類為“ modulo 12”,有時縮寫為“mod 12”。
當您想要將數字與模數進行比較并獲得限制在模數范圍內的等效數時,將使用模運算符。
例如,假設您要確定上午 8:00 之后九小時的時間 在十二小時制中,您不能簡單地將 9 與 8 相加,因為您會得到 17。您需要取結果,17 , 并用于mod在 12 小時上下文中獲得其等效值:
8 o'clock + 9 = 17 o'clock 17 mod 12 = 5
17 mod 12返回5。這意味著上午 8:00 后 9 小時是下午 5:00 您通過獲取數字17并將其應用于mod 12上下文來確定這一點。
現在,如果你想想看,17和5在一個相當的mod 12范圍內。如果你在 5:00 和 17:00 看時針,它會在相同的位置。模算術有一個方程來描述這種關系:
a ≡ b (mod n)
這個等式讀作“a并且b是全等模n。”?這意味著a和b是等價的,mod n因為它們除以 時的余數相同n。在上述等式中,n是模數為兩個a和b。使用值17和5從之前的公式是這樣的:
17 ≡ 5 (mod 12)
這讀作“17并且5是全等模12。”?17并且除以 時的5余數相同。所以在 中,數字和是等價的。512mod 12175
您可以使用除法確認這一點:
17 / 12 = 1 R 5 5 / 12 = 0 R 5
這兩個操作具有相同的余數5,因此它們是等效的模12。
現在,對于 Python 運算符來說,這似乎是很多數學運算,但掌握這些知識將使您準備好在本教程后面的示例中使用模運算符。在下一節中,您將了解將 Python 模運算符與數字類型int和float.
Python 模運算符基礎知識
模運算符與其他算術運算符一樣,可以與數字類型int和 一起使用float。正如你看到以后,它也可以與其他類型,如使用math.fmod(),decimal.Decimal和你自己的類。
模運算符?int
大多數情況下,您將使用帶整數的模運算符。模運算符與兩個正整數一起使用時,將返回標準歐幾里得除法的余數:
>>>
>>> 15 % 4 3 >>> 17 % 12 5 >>> 240 % 13 6 >>> 10 % 16 10
當心!就像除法運算符 (?/) 一樣,ZeroDivisionError如果您嘗試使用除數為 的模運算符,Python 將返回 a?0:
>>>
>>> 22 % 0 ZeroDivisionError: integer division or modulo by zero
接下來,您將了解如何將模運算符與float.
模運算符?float
與 類似int,與 a 一起使用的模運算符float將返回除法的余數,但作為一個float值:
>>>
>>> 12.5 % 5.5 1.5 >>> 17.0 % 12.0 5.0
將 afloat與模運算符一起使用的替代方法是使用math.fmod()對float值執行模運算:
>>>
>>> import math >>> math.fmod(12.5, 5.5) 1.5 >>> math.fmod(8.5, 2.5) 1.0
由于計算模運算結果的方式,官方 Python 文檔建議math.fmod()在處理float值時使用Python 模運算符math.fmod()。如果您使用負操作數,那么您可能會在math.fmod(x, y)和之間看到不同的結果x % y。您將在下一節中更詳細地探索使用帶負操作數的模運算符。
就像其他算術運算符一樣,模運算符 andmath.fmod()在處理浮點算術時可能會遇到舍入和精度問題:
>>>
>>> 13.3 % 1.1 0.09999999999999964 >>> import math >>> math.fmod(13.3, 1.1) 0.09999999999999964
如果保持浮點精度對您的應用程序很重要,那么您可以將模運算符與decimal.Decimal.?您將在本教程的稍后部分看到這一點。
帶負操作數的模運算符
到目前為止,您所看到的所有模運算都使用了兩個正操作數并返回了可預測的結果。當引入負操作數時,事情變得更加復雜。
事實證明,計算機確定具有負操作數的模運算結果的方式對于余數是否應采用被除數(被除數)或除數的符號(被除數的數)存在歧義。股息被分割)。不同的編程語言對此有不同的處理方式。
例如,在JavaScript 中,余數將采用被除數的符號:
8 % -3 = 2
此示例中的余數2為正,因為它采用被除數 的符號8。在 Python 和其他語言中,余數將采用除數的符號:
8 % -3 = -1
在這里,您可以看到余數-1,取除數 的符號-3。
您可能想知道為什么 JavaScript 中2的余數是,而 Python 中的余數是-1。這與不同語言如何確定模運算的結果有關。余數采用被除數符號的語言使用以下等式來確定余數:
r = a - (n * trunc(a/n))
這個方程有三個變量:
r?是余數。
a?是股息。
n?是除數。
trunc()在這個等式中意味著它使用截斷除法,它總是將負數四舍五入到零。有關更多說明,請參閱下面使用8作為被除數和-3作為除數的模運算的步驟:
r = 8 - (-3 * trunc(8/-3)) r = 8 - (-3 * trunc(-2.666666666667)) r = 8 - (-3 * -2) # Rounded toward 0 r = 8 - 6 r = 2
在這里你可以看到像 JavaScript 這樣的語言是如何得到余數的2。Python 和其他語言的余數采用除數的符號使用以下等式:
r = a - (n * floor(a/n))
floor()在這個等式中意味著它使用樓層劃分。對于正數,樓層除法將返回與截斷除法相同的結果。但是對于負數,地板除法會將結果向下舍入,遠離零:
r = 8 - (-3 * floor(8/-3)) r = 8 - (-3 * floor(-2.666666666667)) r = 8 - (-3 * -3) # Rounded away from 0 r = 8 - 9 r = -1
在這里你可以看到結果是-1。
既然您了解了余數的差異從何而來,您可能想知道如果您只使用 Python,為什么這很重要。好吧,事實證明,并非 Python 中的所有模運算都是相同的。雖然與int和float類型一起使用的模將采用除數的符號,但其他類型不會。
當你比較的結果,你可以看到這樣的例子8.0 % -3.0和math.fmod(8.0, -3.0):
>>>
>>> 8.0 % -3 -1.0 >>> import math >>> math.fmod(8.0, -3.0) 2.0
math.fmod()使用截斷除法float取被除數的符號,而使用除數的符號。在本教程的后面,您將看到另一種使用被除數符號的 Python 類型,decimal.Decimal.
模運算符和?divmod()
Python 有內置函數divmod(),它內部使用模運算符。divmod()接受兩個參數并返回一個包含使用提供的參數進行除法和取模的結果的元組。
下面是一個使用的例子divmod()與37和5:
>>>
>>> divmod(37, 5) (7, 2) >>> 37 // 5 7 >>> 37 % 5 2
您可以看到divmod(37, 5)返回 tuple?(7, 2)。該7是地板相除的結果37和5。這2是37modulo的結果5。
下面是第二個參數為負數的示例。如上一節所述,當模運算符與 an 一起使用時int,余數將采用除數的符號:
>>>
>>> divmod(37, -5) (-8, -3) >>> 37 // -5 -8 >>> 37 % -5 -3 # Result has the sign of the divisor
既然您已經有機會看到在多個場景中使用的模運算符,那么看看 Python 在與其他算術運算符一起使用時如何確定模運算符的優先級很重要。
模運算符優先級
與其他 Python 運算符一樣,模運算符有特定的規則來確定它在計算表達式時的優先級。模運算符 (?%) 與乘法 (?*)、除法 (?/) 和除法 (?//) 運算符具有相同的優先級。
看看下面的模運算符優先級的示例:
>>>
>>> 4 * 10 % 12 - 9 -5
乘法和模運算符具有相同的優先級,因此 Python 將從左到右計算它們。以下是上述操作的步驟:
4 * 10被評估,導致40 % 12 - 9。
40 % 12被評估,導致4 - 9。
4 - 9被評估,導致-5。
如果要覆蓋其他運算符的優先級,則可以使用括號將要首先計算的操作括起來:
>>>
>>> 4 * 10 % (12 - 9) 1
在這個例子中,(12 - 9)首先被評估,然后是4 * 10和最后40 % 3,它等于1。
實踐中的 Python 模運算符
現在您已經了解了 Python 模運算符的基礎知識,您將查看一些使用它來解決實際編程問題的示例。有時,很難確定何時在代碼中使用模運算符。下面的示例將使您了解它的多種使用方式。
如何檢查一個數是偶數還是奇數
在本節中,您將看到如何使用模運算符來確定數字是偶數還是奇數。使用模數為 的模運算符2,您可以檢查任何數字以查看它是否可以被 整除2。如果它是可整除的,那么它就是偶數。
看看is_even()which檢查num參數是否為偶數:
def is_even(num): return num % 2 == 0
這里num % 2將等于0ifnum是偶數和1ifnum是奇數。檢查對0將返回一個布爾值的True或False基于是否num為偶數。
檢查奇數非常相似。要檢查奇數,請反轉相等檢查:
def is_odd(num): return num % 2 != 0
True如果num % 2不等于0,則此函數將返回,這意味著有余數證明num是奇數。現在,您可能想知道是否可以使用以下函數來確定是否num為奇數:
def is_odd(num): return num % 2 == 1
這個問題的答案是肯定的和否定的。從技術上講,這個函數將使用 Python 計算整數模的方式。也就是說,您應該避免將模運算的結果與1Python 中的所有模運算的結果進行比較,因為并非所有的模運算都會返回相同的余數。
您可以在以下示例中了解原因:
>>>
>>> -3 % 2 1 >>> 3 % -2 -1
在第二個示例中,余數采用負除數的符號并返回-1。在這種情況下,布爾檢查3 % -2 == 1將返回False。
但是,如果您將模運算與 進行比較0,那么哪個操作數為負都沒有關系。結果將始終是True偶數:
>>>
>>> -2 % 2 0 >>> 2 % -2 0
如果您堅持將 Python 模運算與 進行比較0,那么檢查代碼中的偶數和奇數或任何其他數字的倍數應該不會有任何問題。
在下一節中,您將了解如何使用帶循環的模運算符來控制程序流程。
如何在循環中以特定間隔運行代碼
使用 Python 模運算符,您可以在循環內以特定時間間隔運行代碼。這是通過使用循環的當前索引和模數執行模運算來完成的。模數確定特定于區間的代碼在循環中運行的頻率。
下面是一個例子:
def split_names_into_rows(name_list, modulus=3): for index, name in enumerate(name_list, start=1): print(f"{name:-^15} ", end="") if index % modulus == 0: print() print()
這段代碼定義了split_names_into_rows(),它接受兩個參數。name_list是應拆分為行的名稱列表。modulus為操作設置一個模數,有效地確定每行中應該有多少個名字。split_names_into_rows()將循環name_list并在達到該modulus值后開始新的一行。
在更詳細地分解函數之前,先看看它的實際效果:
>>>
>>> names = ["Picard", "Riker", "Troi", "Crusher", "Worf", "Data", "La Forge"] >>> split_names_into_rows(names) ----Picard----- -----Riker----- -----Troi------ ----Crusher---- -----Worf------ -----Data------ ---La Forge----
如您所見,姓名列表已分為三行,每行最多三個姓名。modulus默認為3,但您可以指定任何數字:
>>>
>>> split_names_into_rows(names, modulus=4) ----Picard----- -----Riker----- -----Troi------ ----Crusher---- -----Worf------ -----Data------ ---La Forge---- >>> split_names_into_rows(names, modulus=2) ----Picard----- -----Riker----- -----Troi------ ----Crusher---- -----Worf------ -----Data------ ---La Forge---- >>> split_names_into_rows(names, modulus=1) ----Picard----- -----Riker----- -----Troi------ ----Crusher---- -----Worf------ -----Data------ ---La Forge----
現在您已經看到了運行中的代碼,您可以分解它的作用。首先,它用于enumerate()迭代name_list,將列表中的當前項分配給 ,并將name計數值分配給index。您可以看到startfor的可選參數enumerate()設置為1。這意味著index計數將從而1不是開始0:
for index, name in enumerate(name_list, start=1):
接下來,在循環內部,函數調用print()輸出name到當前行。endfor的參數print()是一個空字符串(?""),因此它不會在字符串末尾輸出換行符。一個f 字符串被傳遞給print(),它使用Python 提供的字符串輸出格式語法:
print(f"{name:-^15} ", end="")
在不涉及太多細節的情況下,:-^15語法告訴print()執行以下操作:
輸出至少15字符,即使字符串短于 15 個字符。
將字符串居中對齊。
用連字符 (?-)填充字符串右側或左側的任何空格。
現在名稱已打印到行,看看主要部分split_names_into_rows():
if index % modulus == 0: print()
此代碼采用當前迭代,index并使用模運算符將其與 進行比較modulus。如果結果等于0,則它可以運行特定于區間的代碼。在這種情況下,該函數調用print()添加一個換行符,它開始一個新行。
上面的代碼只是一個例子。使用該模式index % modulus == 0可以讓您在循環中以特定間隔運行不同的代碼。在下一節中,您將更深入地了解這個概念并查看循環迭代。
如何創建循環迭代
循環迭代描述了一種迭代,一旦到達某個點就會重置。通常,這種迭代方式用于將迭代的索引限制在一定范圍內。
您可以使用模運算符來創建循環迭代。看一個使用turtle庫繪制形狀的示例:
import turtle import random def draw_with_cyclic_iteration(): colors = ["green", "cyan", "orange", "purple", "red", "yellow", "white"] turtle.bgcolor("gray8") # Hex: #333333 turtle.pendown() turtle.pencolor(random.choice(colors)) # First color is random i = 0 # Initial index while True: i = (i + 1) % 6 # Update the index turtle.pensize(i) # Set pensize to i turtle.forward(225) turtle.right(170) # Pick a random color if i == 0: turtle.pencolor(random.choice(colors))
上面的代碼使用一個無限循環來繪制一個重復的星形。每六次迭代后,它會改變筆的顏色。筆的大小隨著每次迭代而增加,直到i重新設置回0。如果你運行代碼,那么你應該得到類似的東西:
這段代碼的重要部分在下面突出顯示:
import turtle import random def draw_with_cyclic_iteration(): colors = ["green", "cyan", "orange", "purple", "red", "yellow", "white"] turtle.bgcolor("gray8") # Hex: #333333 turtle.pendown() turtle.pencolor(random.choice(colors)) i = 0 # Initial index while True: i = (i + 1) % 6 # Update the index turtle.pensize(i) # Set pensize to i turtle.forward(225) turtle.right(170) # Pick a random color if i == 0: turtle.pencolor(random.choice(colors))
每次通過循環時,i都會根據 的結果進行更新(i + 1) % 6。這個新i值用于在.pensize每次迭代中增加。一旦i達到5,(i + 1) % 6將等于0,i并將重置回0。
您可以查看下面的迭代步驟以獲得更多說明:
i = 0 : (0 + 1) % 6 = 1 i = 1 : (1 + 1) % 6 = 2 i = 2 : (2 + 1) % 6 = 3 i = 3 : (3 + 1) % 6 = 4 i = 4 : (4 + 1) % 6 = 5 i = 5 : (5 + 1) % 6 = 0 # Reset
當i重置回 時0,.pencolor更改為新的隨機顏色,如下所示:
if i == 0: turtle.pencolor(random.choice(colors))
本節中的代碼6用作模數,但您可以將其設置為任意數字,以調整循環在重置 value 之前將迭代的次數i。
如何轉換單位
在本節中,您將了解如何使用模運算符來轉換單位。以下示例采用較小的單位并將它們轉換為較大的單位,而不使用小數。模運算符用于確定當較小的單位不能被較大的單位整除時可能存在的任何余數。
在第一個示例中,您將英寸轉換為英尺。模運算符用于獲取未均勻劃分為英尺的剩余英寸。樓層除法運算符 (?//) 用于將總英尺數向下取整:
def convert_inches_to_feet(total_inches): inches = total_inches % 12 feet = total_inches // 12 print(f"{total_inches} inches = {feet} feet and {inches} inches")
這是正在使用的函數的示例:
>>>
>>> convert_inches_to_feet(450) 450 inches = 37 feet and 6 inches
正如您從輸出中看到的那樣,450 % 12返回值6是未均勻劃分為英尺的剩余英寸。的結果450 // 12是37,這是英寸被平均劃分的總英尺數。
在下一個示例中,您可以更進一步。convert_minutes_to_days()取一個整數 ,total_mins代表分鐘數,并以天、小時和分鐘為單位輸出時間段:
def convert_minutes_to_days(total_mins): days = total_mins // 1440 extra_minutes = total_mins % 1440 hours = extra_minutes // 60 minutes = extra_minutes % 60 print(f"{total_mins} = {days} days, {hours} hours, and {minutes} minutes")
分解一下,您可以看到該函數執行以下操作:
用 確定可整除的總天數total_mins // 1440,其中1440是一天中的分鐘數
計算任何extra_minutes剩余的total_mins % 1440
使用extra_minutes來獲得可整除的hours和任何額外的minutes
你可以在下面看到它是如何工作的:
>>>
>>> convert_minutes_to_days(1503) 1503 = 1 days, 1 hours, and 3 minutes >>> convert_minutes_to_days(3456) 3456 = 2 days, 9 hours, and 36 minutes >>> convert_minutes_to_days(35000) 35000 = 24 days, 7 hours, and 20 minutes
雖然上面的例子只涉及將英寸轉換為英尺和分鐘轉換為天,但您可以使用任何類型的單位和模運算符將較小的單位轉換為較大的單位。
注意:上面的兩個例子都可以修改使用divmod(),使代碼更簡潔。如果您還記得的話,divmod()返回一個包含使用提供的參數進行除法和取模的結果的元組。
下面,地板除法和模運算符已替換為divmod():
def convert_inches_to_feet_updated(total_inches): feet, inches = divmod(total_inches, 12) print(f"{total_inches} inches = {feet} feet and {inches} inches")
如您所見,divmod(total_inches, 12)返回一個元組,它被解包到feetand 中inches。
如果您嘗試這個更新的函數,那么您將收到與以前相同的結果:
>>>
>>> convert_inches_to_feet(450) 450 inches = 37 feet and 6 inches >>> convert_inches_to_feet_updated(450) 450 inches = 37 feet and 6 inches
您會收到相同的結果,但現在代碼更加簡潔。你也可以更新convert_minutes_to_days():
def convert_minutes_to_days_updated(total_mins): days, extra_minutes = divmod(total_mins, 1440) hours, minutes = divmod(extra_minutes, 60) print(f"{total_mins} = {days} days, {hours} hours, and {minutes} minutes")
使用divmod(),該函數比以前的版本更容易閱讀并返回相同的結果:
>>>
>>> convert_minutes_to_days(1503) 1503 = 1 days, 1 hours, and 3 minutes >>> convert_minutes_to_days_updated(1503) 1503 = 1 days, 1 hours, and 3 minutes
divmod()并非在所有情況下都需要使用,但在此處有意義,因為單位轉換計算同時使用樓層除法和模數。
現在您已經了解了如何使用模運算符來轉換單位,在下一節中,您將了解如何使用模運算符來檢查素數。
如何確定一個數是否為質數
在下一個示例中,您將了解如何使用 Python 模運算符來檢查數字是否為質數。質數是任何只包含兩個因數1和它本身的數。素數的一些例子是2,3,5,7,23,29,59,83,和97。
下面的代碼是使用模運算符確定數字素數的實現:
def check_prime_number(num): if num < 2: print(f"{num} must be greater than or equal to 2 to be prime.") return factors = [(1, num)] i = 2 while i * i <= num: if num % i == 0: factors.append((i, num//i)) i += 1 if len(factors) > 1: print(f"{num} is not prime. It has the following factors: {factors}") else: print(f"{num} is a prime number")
這段代碼定義了check_prime_number(),它接受參數num并檢查它是否是素數。如果是,則會顯示一條消息,說明這num是一個質數。如果它不是質數,則會顯示一條消息,其中包含該數字的所有因數。
注意:上面的代碼不是檢查素數的最有效方法。如果你有興趣在更深的挖掘,然后檢查出的埃拉托色尼的篩和篩的阿特金為尋找素數的更好的性能算法的例子。
在您更仔細地查看函數之前,以下是使用一些不同數字的結果:
>>>
>>> check_prime_number(44) 44 is not prime. It has the following factors: [(1, 44), (2, 22), (4, 11)] >>> check_prime_number(53) 53 is a prime number >>> check_prime_number(115) 115 is not prime. It has the following factors: [(1, 115), (5, 23)] >>> check_prime_number(997) 997 is a prime number
深入研究代碼,您可以看到它從檢查 ifnum小于開始2。質數只能大于或等于2。如果num小于2,則函數不需要繼續。它將print()一條消息和return:
if num < 2: print(f"{num} must be greater than or equal to 2 to be prime.") return
如果num大于2,則函數檢查是否num為素數。為了檢查這一點,該函數迭代2和 的平方根之間的所有數字,num以查看是否有任何數字均分為num。如果其中一個數被整除,則已找到一個因數,并且num不能是質數。
這是函數的主要部分:
factors = [(1, num)] i = 2 while i * i <= num: if num % i == 0: factors.append((i, num//i)) i += 1
這里有很多東西要解壓,所以讓我們一步一步來。
首先,factors使用初始因子創建一個列表,(1, num)。此列表將用于存儲找到的任何其他因素:
factors = [(1, num)]
接下來,從 開始2,代碼遞增,i直到達到 的平方根num。在每次迭代中,它都會num與i進行比較,看看它是否可以被均勻整除。代碼只需要檢查并包括平方根,num因為它不包含任何高于此的因素:
i = 2 while i * i <= num: if num % i == 0: factors.append((i, num//i)) i += 1
num該函數沒有嘗試確定 的平方根,而是使用while循環來查看i * i <= num。只要i * i <= num,循環還沒有達到 的平方根num。
在while循環內部,模運算符檢查是否可以num被 整除i:
factors = [(1, num)] i = 2 # Start the initial index at 2 while i * i <= num: if num % i == 0: factors.append((i, num//i)) i += 1
如果 可num被 整除i,則i是 的因子num,并將因子的元組添加到factors列表中。
一旦while循環完成,看到代碼檢查,如果發現任何其他因素:
if len(factors) > 1: print(f"{num} is not prime. It has the following factors: {factors}") else: print(f"{num} is a prime number")
如果factors列表中存在多個元組,則num不能是質數。對于非質數,因子被打印出來。對于素數,該函數會打印一條消息,說明它num是一個素數。
如何實現密碼
Python 模運算符可用于創建密碼。密碼是一種用于對輸入(通常是文本)執行加密和解密的算法。在本節中,您將了解兩種密碼,即凱撒密碼和維吉尼亞密碼。
您將看到的第一個密碼是Caesar 密碼,它以 Julius Caesar 的名字命名,他用它來秘密地傳遞信息。它是一種使用字母替換來加密文本字符串的替換密碼。
凱撒密碼的工作原理是將一個要加密的字母在字母表中向左或向右移動一定數量的位置。位于該位置的任何字母都用作加密字符。這個相同的移位值適用于字符串中的所有字符。
例如,如果移位為5,則將A向上移位五個字母以變為F、B將變為G,依此類推。您可以在下面看到REALPYTHON移位為 的文本的加密過程5:
得到的密碼是WJFQUDYMTS。
解密密碼是通過反轉移位來完成的。加密和解密過程都可以用以下表達式來描述,其中char_index是字母表中字符的索引:
encrypted_char_index = (char_index + shift) % 26 decrypted_char_index = (char_index - shift) % 26
此密碼使用模運算符來確保在移動字母時,如果到達字母表的末尾,索引將環繞。既然您知道此密碼的工作原理,請看一下實現:
import string def caesar_cipher(text, shift, decrypt=False): if not text.isascii() or not text.isalpha(): raise ValueError("Text must be ASCII and contain no numbers.") lowercase = string.ascii_lowercase uppercase = string.ascii_uppercase result = "" if decrypt: shift = shift * -1 for char in text: if char.islower(): index = lowercase.index(char) result += lowercase[(index + shift) % 26] else: index = uppercase.index(char) result += uppercase[(index + shift) % 26] return result
這段代碼定義了一個名為 的函數caesar_cipher(),它有兩個必需參數和一個可選參數:
text?是要加密或解密的文本。
shift?是移動每個字母的位置數。
decrypt是一個布爾值,用于設置是否text應該解密。
decrypt包括在內,以便可以使用單個函數來處理加密和解密。此實現只能處理字母字符,因此該函數首先檢查textASCII 編碼中的字母字符:
def caesar_cipher(text, shift, decrypt=False): if not text.isascii() or not text.isalpha(): raise ValueError("Text must be ASCII and contain no numbers.")
然后該函數定義了三個變量來存儲lowercaseASCII 字符、uppercaseASCII 字符以及加密或解密的結果:
lowercase = string.ascii_lowercase # "abcdefghijklmnopqrstuvwxyz" uppercase = string.ascii_uppercase # "ABCDEFGHIJKLMNOPQRSTUVWXYZ" result = ""
接下來,如果該函數被用來解密text,然后將其乘以shift通過-1使其移位向后:
if decrypt: shift = shift * -1
最后,caesar_cipher()循環遍歷 in 中的各個字符text并對每個 執行以下操作char:
檢查char是小寫還是大寫。
獲取index的char無論是在lowercase或uppercaseASCII名單。
添加 ashift以index確定要使用的密碼字符的索引。
用于% 26確保班次將回繞到字母表的開頭。
將密碼字符附加到result字符串。
循環完成對text值的迭代后,result返回:
for char in text: if char.islower(): index = lowercase.index(char) result += lowercase[(index + shift) % 26] else: index = uppercase.index(char) result += uppercase[(index + shift) % 26] return result
這是完整的代碼:
import string def caesar_cipher(text, shift, decrypt=False): if not text.isascii() or not text.isalpha(): raise ValueError("Text must be ASCII and contain no numbers.") lowercase = string.ascii_lowercase uppercase = string.ascii_uppercase result = "" if decrypt: shift = shift * -1 for char in text: if char.islower(): index = lowercase.index(char) result += lowercase[(index + shift) % 26] else: index = uppercase.index(char) result += uppercase[(index + shift) % 26] return result
現在使用meetMeAtOurHideOutAtTwo移位為的文本在 Python REPL 中運行代碼10:
>>>
>>> caesar_cipher("meetMeAtOurHideOutAtTwo", 10) woodWoKdYebRsnoYedKdDgy
加密結果為woodWoKdYebRsnoYedKdDgy。使用此加密文本,您可以運行解密以獲取原始文本:
>>>
>>> caesar_cipher("woodWoKdYebRsnoYedKdDgy", 10, decrypt=True) meetMeAtOurHideOutAtTwo
在介紹密碼學時,使用凱撒密碼很有趣。雖然凱撒密碼很少單獨使用,但它是更復雜的替換密碼的基礎。在下一節中,您將了解 Caesar 密碼的一個后代,即 Vigenère 密碼。
的V @ genere加密是一個多碼替代密碼。為了執行加密,它對輸入文本的每個字母使用不同的凱撒密碼。Vigenère 密碼使用關鍵字來確定應該使用哪種凱撒密碼來查找密碼字母。
您可以在下圖中看到加密過程的示例。在此示例中,輸入文本REALPYTHON使用關鍵字加密MODULO:
對于輸入文本的每個字母 ,REALPYTHON關鍵字中的一個字母MODULO用于確定應選擇哪個凱撒密碼列。如果關鍵字比輸入文本短,例如MODULO,則重復關鍵字的字母,直到輸入文本的所有字母都已加密。
下面是 Vigenère 密碼的實現。如您所見,模運算符在函數中使用了兩次:
import string def vigenere_cipher(text, key, decrypt=False): if not text.isascii() or not text.isalpha() or not text.isupper(): raise ValueError("Text must be uppercase ASCII without numbers.") uppercase = string.ascii_uppercase # "ABCDEFGHIJKLMNOPQRSTUVWXYZ" results = "" for i, char in enumerate(text): current_key = key[i % len(key)] char_index = uppercase.index(char) key_index = uppercase.index(current_key) if decrypt: index = char_index - key_index + 26 else: index = char_index + key_index results += uppercase[index % 26] return results
您可能已經注意到 for 的簽名vigenere_cipher()與上caesar_cipher()一節非常相似:
def vigenere_cipher(text, key, decrypt=False): if not text.isascii() or not text.isalpha() or not text.isupper(): raise ValueError("Text must be uppercase ASCII without numbers.") uppercase = string.ascii_uppercase results = ""
主要區別在于,不是shift參數,而是參數,vigenere_cipher()該key參數是加密和解密過程中要使用的關鍵字。另一個區別是添加了text.isupper().?基于此實現,vigenere_cipher()只能接受全部大寫的輸入文本。
像caesar_cipher(),vigenere_cipher()遍歷輸入文本的每個字母來加密或解密它:
for i, char in enumerate(text): current_key = key[i % len(key)]
在上面的代碼中,可以看到函數第一次使用模運算符:
current_key = key[i % len(key)]
此處,該current_key值是根據從 返回的索引確定的i % len(key)。此索引用于從key字符串中選擇一個字母,例如Mfrom?MODULO。
模運算符允許您使用任何長度的關鍵字,而不考慮text要加密的長度。一旦 index?i,即當前被加密的字符的索引,等于關鍵字的長度,它將從關鍵字的開頭重新開始。
對于輸入文本的每個字母,有幾個步驟決定了如何對其進行加密或解密:
確定的char_index基礎上的指標char內uppercase。
確定的key_index基礎上的指標current_key內uppercase。
使用char_index和key_index獲取加密或解密字符的索引。
看看下面代碼中的這些步驟:
char_index = uppercase.index(char) key_index = uppercase.index(current_key) if decrypt: index = char_index - key_index + 26 else: index = char_index + key_index
您可以看到解密和加密的索引計算方式不同。這就是為什么decrypt在這個函數中使用。這樣,您就可以使用該函數進行加密和解密。
在之后index確定,你會發現函數的第二個使用模運算:
results += uppercase[index % 26]
index % 26確保index字符的 不超過25,從而確保它留在字母表內。使用此索引,從 中選擇加密或解密字符uppercase并將其附加到results。
這是 Vigenère 密碼的完整代碼:
import string def vigenere_cipher(text, key, decrypt=False): if not text.isascii() or not text.isalpha() or not text.isupper(): raise ValueError("Text must be uppercase ASCII without numbers.") uppercase = string.ascii_uppercase # "ABCDEFGHIJKLMNOPQRSTUVWXYZ" results = "" for i, char in enumerate(text): current_key = key[i % len(key)] char_index = uppercase.index(char) key_index = uppercase.index(current_key) if decrypt: index = char_index - key_index + 26 else: index = char_index + key_index results += uppercase[index % 26] return results
現在繼續并在 Python REPL 中運行它:
>>>
>>> vigenere_cipher(text="REALPYTHON", key="MODULO") DSDFAMFVRH >>> encrypted = vigenere_cipher(text="REALPYTHON", key="MODULO") >>> print(encrypted) DSDFAMFVRH >>> vigenere_cipher(encrypted, "MODULO", decrypt=True) REALPYTHON
好的!您現在有一個用于加密文本字符串的有效 Vigenère 密碼。
Python 模運算符高級用途
在最后一部分中,您將通過將模運算符與decimal.Decimal.?您還將了解如何添加.__mod__()到自定義類中,以便它們可以與模運算符一起使用。
使用 Python 模運算符?decimal.Decimal
在本教程的前面部分,您看到了如何將模運算符用于數字類型,例如int和float以及math.fmod()。您還可以使用Decimal來自decimal模塊的模運算符。decimal.Decimal當您希望對浮點算術運算的精度進行離散控制時使用。
以下是使用整數decimal.Decimal和模運算符的一些示例:
>>>
>>> import decimal >>> decimal.Decimal(15) % decimal.Decimal(4) Decimal('3') >>> decimal.Decimal(240) % decimal.Decimal(13) Decimal('6')
下面是一些與decimal.Decimal模運算符一起使用的浮點數:
>>>
>>> decimal.Decimal("12.5") % decimal.Decimal("5.5") Decimal('1.5') >>> decimal.Decimal("13.3") % decimal.Decimal("1.1") Decimal('0.1')
decimal.Decimal除非操作數之一為負數,否則所有模運算都返回與其他數值類型相同的結果。與int和不同float,但與 一樣math.fmod(),結果decimal.Decimal使用被除數的符號。
看看下面的示例,比較使用模運算符與標準int和float值以及與 的結果decimal.Decimal:
>>>
>>> -17 % 3 1 # Sign of the divisor >>> decimal.Decimal(-17) % decimal.Decimal(3) Decimal(-2) # Sign of the dividend >>> 17 % -3 -1 # Sign of the divisor >>> decimal.Decimal(17) % decimal.Decimal(-3) Decimal("2") # Sign of dividend >>> -13.3 % 1.1 1.0000000000000004 # Sign of the divisor >>> decimal.Decimal("-13.3") % decimal.Decimal("1.1") Decimal("-0.1") # Sign of the dividend
與 相比math.fmod(),decimal.Decimal符號相同,但精度不同:
>>>
>>> decimal.Decimal("-13.3") % decimal.Decimal("1.1") Decimal("-0.1") >>> math.fmod(-13.3, 1.1) -0.09999999999999964
從上面的示例中可以看出,使用decimal.Decimal和 模運算符類似于使用其他數字類型。您只需要記住在使用負操作數時它如何確定結果的符號。
在下一節中,您將了解如何覆蓋類中的模運算符以自定義其行為。
在自定義類中使用 Python 模運算符
Python數據模型允許您覆蓋 Python 對象中的內置方法以自定義其行為。在本節中,您將了解如何進行覆蓋,.__mod__()以便您可以在自己的類中使用模運算符。
對于此示例,您將使用一個Student類。本課程將跟蹤學生學習的時間。這是初始Student類:
class Student: def __init__(self, name): self.name = name self.study_sessions = [] def add_study_sessions(self, sessions): self.study_sessions += sessions
在Student類初始化為name參數,并開始與一個空表,study_sessions,將舉行代表每節研究分鐘整數列表。還有.add_study_sessions(),它需要一個sessions參數,該參數應該是要添加到study_sessions.
現在,如果您還記得上面的轉換單位部分,convert_minutes_to_day()使用 Python 模運算符轉換total_mins為天、小時和分鐘。您現在將實現該方法的修改版本,以了解如何將自定義類與模運算符一起使用:
def total_study_time_in_hours(student, total_mins): hours = total_mins // 60 minutes = total_mins % 60 print(f"{student.name} studied {hours} hours and {minutes} minutes")
您可以在Student課堂上使用此功能來顯示 aStudent學習的總小時數。結合Student上面的類,代碼將如下所示:
class Student: def __init__(self, name): self.name = name self.study_sessions = [] def add_study_sessions(self, sessions): self.study_sessions += sessions def total_study_time_in_hours(student, total_mins): hours = total_mins // 60 minutes = total_mins % 60 print(f"{student.name} studied {hours} hours and {minutes} minutes")
如果你在 Python REPL 中加載這個模塊,那么你可以像這樣使用它:
>>>
>>> jane = Student("Jane") >>> jane.add_study_sessions([120, 30, 56, 260, 130, 25, 75]) >>> total_mins = sum(jane.study_sessions) >>> total_study_time_in_hours(jane, total_mins) Jane studied 11 hours and 36 minutes
上面的代碼打印出jane學習的總小時數。此版本的代碼有效,但它需要額外的求和步驟study_sessions才能total_mins在調用total_study_time_in_hours().
以下是修改Student類以簡化代碼的方法:
class Student: def __init__(self, name): self.name = name self.study_sessions = [] def add_study_sessions(self, sessions): self.study_sessions += sessions def __mod__(self, other): return sum(self.study_sessions) % other def __floordiv__(self, other): return sum(self.study_sessions) // other
通過覆蓋.__mod__()and?.__floordiv__(),您可以使用Student帶有模運算符的實例。計算sum()ofstudy_sessions也包含在Student類中。
通過這些修改,您可以Student直接在total_study_time_in_hours().?由于total_mins不再需要,您可以將其刪除:
def total_study_time_in_hours(student): hours = student // 60 minutes = student % 60 print(f"{student.name} studied {hours} hours and {minutes} minutes")
修改后的完整代碼如下:
class Student: def __init__(self, name): self.name = name self.study_sessions = [] def add_study_sessions(self, sessions): self.study_sessions += sessions def __mod__(self, other): return sum(self.study_sessions) % other def __floordiv__(self, other): return sum(self.study_sessions) // other def total_study_time_in_hours(student): hours = student // 60 minutes = student % 60 print(f"{student.name} studied {hours} hours and {minutes} minutes")
現在,調用 Python REPL 中的代碼,您可以看到它更加簡潔:
>>>
>>> jane = Student("Jane") >>> jane.add_study_sessions([120, 30, 56, 260, 130, 25, 75]) >>> total_study_time_in_hours(jane) Jane studied 11 hours and 36 minutes
通過覆蓋.__mod__(),您可以讓自定義類的行為更像 Python 的內置數字類型。
結論
乍一看,Python 模運算符可能不會引起您的注意。然而,正如您所看到的,這個不起眼的運營商有很多東西。從檢查偶數到使用密碼加密文本,您已經看到了模運算符的許多不同用途。
在本教程中,您學習了如何:
使用模運算符與int,float,math.fmod(),divmod(),和decimal.Decimal
計算模運算的結果
使用模運算符解決實際問題
.__mod__()在您自己的類中覆蓋以將它們與模運算符一起使用
憑借在本教程中獲得的知識,您現在可以開始在自己的代碼中使用模運算符并取得巨大成功。快樂的python!
Python 面向對象編程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。