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
余数相同。所以在 中,数字和是等价的。5
12
mod 12
17
5
您可以使用除法确认这一点:
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
是37
modulo的结果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
将等于0
ifnum
是偶数和1
ifnum
是奇数。检查对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 计算整数模的方式。也就是说,您应该避免将模运算的结果与1
Python 中的所有模运算的结果进行比较,因为并非所有的模运算都会返回相同的余数。
您可以在以下示例中了解原因:
>>> -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
。您可以看到start
for的可选参数enumerate()
设置为1
。这意味着index
计数将从而1
不是开始0
:
for index, name in enumerate(name_list, start=1):
接下来,在循环内部,函数调用print()
输出name
到当前行。end
for的参数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)
返回一个元组,它被解包到feet
and 中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
包括在内,以便可以使用单个函数来处理加密和解密。此实现只能处理字母字符,因此该函数首先检查text
ASCII 编码中的字母字符:
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
ASCII 字符、uppercase
ASCII 字符以及加密或解密的结果:
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
或uppercase
ASCII名单。 - 添加 a
shift
以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
字符串中选择一个字母,例如M
from 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!
- 点赞
- 收藏
- 关注作者
评论(0)