Python 模数实践:如何使用 % 运算符

举报
Yuchuan 发表于 2021/11/25 09:07:10 2021/11/25
【摘要】 乍一看,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上下文来确定这一点。

现在,如果你想想看,175在一个相当的mod 12范围内。如果你在 5:00 和 17:00 看时针,它会在相同的位置。模算术有一个方程来描述这种关系:

a ≡ b (mod n)

这个等式读作“a并且b是全等模n。” 这意味着ab是等价的,mod n因为它们除以 时的余数相同n。在上述等式中,n模数为两个ab。使用值175从之前的公式是这样的:

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 模运算符与数字类型intfloat.

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))

这个方程有三个变量:

  1. r 是余数。
  2. a 是股息。
  3. 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 中的所有模运算都是相同的。虽然与intfloat类型一起使用的模将采用除数的符号,但其他类型不会。

当你比较的结果,你可以看到这样的例子8.0 % -3.0math.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()375

>>>
>>> divmod(37, 5)
(7, 2)

>>> 37 // 5
7

>>> 37 % 5
2

您可以看到divmod(37, 5)返回 tuple (7, 2)。该7是地板相除的结果375。这237modulo的结果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 将从左到右计算它们。以下是上述操作的步骤:

  1. 4 * 10被评估,导致40 % 12 - 9
  2. 40 % 12被评估,导致4 - 9
  3. 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将返回一个布尔值TrueFalse基于是否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。如果你运行代码,那么你应该得到类似的东西:

使用 Python mod (%) 运算符的循环迭代示例

这段代码的重要部分在下面突出显示:

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将等于0i并将重置回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 // 1237,这是英寸被平均划分的总英尺数。

在下一个示例中,您可以更进一步。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")

分解一下,您可以看到该函数执行以下操作:

  1. 用 确定可整除的总天数total_mins // 1440,其中1440是一天中的分钟数
  2. 计算任何extra_minutes剩余的total_mins % 1440
  3. 使用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和它本身的数。素数的一些例子是235723295983,和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。在每次迭代中,它都会numi进行比较,看看它是否可以被均匀整除。代码只需要检查并包括平方根,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向上移位五个字母以变为FB将变为G,依此类推。您可以在下面看到REALPYTHON移位为 的文本的加密过程5

使用 Python mod (%) 运算符的凯撒密码

得到的密码是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

  1. 检查char是小写还是大写。
  2. 获取indexchar无论是在lowercaseuppercaseASCII名单。
  3. 添加 ashiftindex确定要使用的密码字符的索引。
  4. 用于% 26确保班次将回绕到字母表的开头。
  5. 将密码字符附加到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

使用 Python mod (%) 运算符的 Vigenère Cipher

对于输入文本的每个字母 ,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,即当前被加密的字符的索引,等于关键字的长度,它将从关键字的开头重新开始。

对于输入文本的每个字母,有几个步骤决定了如何对其进行加密或解密:

  1. 确定的char_index基础上的指标charuppercase
  2. 确定的key_index基础上的指标current_keyuppercase
  3. 使用char_indexkey_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

在本教程的前面部分,您看到了如何将模运算符用于数字类型,例如intfloat以及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使用被除数的符号。

看看下面的示例,比较使用模运算符与标准intfloat值以及与 的结果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 模运算符可能不会引起您的注意。然而,正如您所看到的,这个不起眼的运营商有很多东西。从检查偶数到使用密码加密文本,您已经看到了模运算符的许多不同用途。

在本教程中,您学习了如何:

  • 使用模运算符intfloatmath.fmod()divmod(),和decimal.Decimal
  • 计算模运算的结果
  • 使用模运算符解决实际问题
  • .__mod__()在您自己的类中覆盖以将它们与模运算符一起使用

凭借在本教程中获得的知识,您现在可以开始在自己的代码中使用模运算符并取得巨大成功。快乐的python!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。