- Regular Expressions
- Case Sensitivity
- Cleaning Up User Input
- Extracting User Input
- Summing Up
Regular Expressions
- 正则表达式或“regex”将使我们能够检查代码中的模式。例如,我们可能希望验证电子邮件地址的格式是否正确。正则表达式将使我们能够以这种方式检查表达式。
- 要开始,请键入code validate.py 然后在文本编辑器中编写如下代码:
email = input("What's your email? ").strip()
if "@" in email:
print("Valid")
else:
print("Invalid")
Copy
请注意,strip 会移除输入开头或结尾的空白。执行这个代码,你会看到只要出现@ 符号时,程序会将输入视为有效。
- 然而,你可以想象,一个人可以输入@@ ,并且输入可以被看作有效。我们可以将一个电子邮件地址视为至少有一个@ 和.。 请按如下方式修改代码:
email = input("What's your email? ").strip()
if "@" in email and "." in email:
print("Valid")
else:
print("Invalid")
请注意,虽然这可以按预期工作,但我们的用户可能是故意的,只需键入@. 将导致程序返回valid。
- 我们可以改进程序的逻辑,如下所示:
email = input("What's your email? ").strip()
username, domain = email.split("@")
if username and "." in domain:
print("Valid")
else:
print("Invalid")
请注意strip 方法用于确定username 存在,如果. 位于domain变量内。 运行这个程序,你输入的一个标准的电子邮件地址可以被认为是valid。键入malan@harvard 你会发现程序将此输入视为invalid。
- 我们可以更精确地修改代码,如下所示:
email = input("What's your email? ").strip()
username, domain = email.split("@")
if username and domain.endswith(".edu"):
print("Valid")
else:
print("Invalid")
请注意endswith 方法将检查是否domain 包含.edu。然而,恶意用户仍然可以破解我们的代码。例如,用户可以键入malan@.edu 并且它将被认为是有效的。
- 实际上,我们可以自己不断地迭代这段代码。python有一个名为re的已存在库, 它有许多内置函数,可以根据模式验证用户输入。
- re库中功能最多的函数之一 是search。
- search 库遵循格式re.search(pattern, string, flags=0)。根据这个格式,我们可以修改代码,如下所示:
import re
email = input("What's your email? ").strip()
if re.search("@", email):
print("Valid")
else:
print("Invalid")
请注意,这并没有增加程序的功能,实际上,这是一种倒退。
- 我们可以进一步提高程序的功能。但是,我们需要围绕validation提高我们的词汇量。事实证明,在正则表达式的世界中,有一些特定的符号允许我们识别模式。在这一点上,我们只检查了特定的文本片段,如@。为了进行验证,许多特殊的符号可以被传递给编译器。这些模式的非详尽列表如下:
. any character except a new line
* 0 or more repetitions
+ 1 or more repetitions
? 0 or 1 repetition
{m} m repetitions
{m, n} m-n repetitions
- 在我们的代码中实现这一点,请按如下所示修改你的代码:
import re
email = input("What's your email? ").strip()
if re.search(".+@.+", email):
print("Valid")
else:
print("Invalid")
请注意,我们不关心用户名或域是什么,我们关心的是模式。.+ 是用来判断电子邮件地址的左边和右边是否有任何内容。运行代码,键入malan@,你会注意到输入被视为invalid 正如我们所希望的那样。
- 如果我们使用正则表达式.*@.* 在上面代码中,你可以将其可视化为:
- 请注意正则表达式的state machine描述。 在左边,编译器开始从左到右计算语句。一旦我们达到q1 或者问题1,编译器会根据传递给它的表达式一次又一次地读取。然后,状态会发生变化,现在看q2 或者第二个问题被验证。同样,箭头指示如何基于我们的编程一次又一次地对表达式求值。然后,如双圆圈所示,到达状态机的最终状态。
- 考虑到我们在代码中使用的正则表达式,.+@.+,你可以将其可视化,如下所示:
- 请注意q1 是使用者提供的任何字符,包括'q2'(一个或多个重复的字符)。后面接着'@'符号。然后,q3 查找用户提供的任何字符,包括q4 作为字符的一次或多次重复。
- re 和re.search 函数和类似函数寻找模式。
- 我们继续对这段代码改进,我们可以改进我们的代码如下:
import re
email = input("What's your email? ").strip()
if re.search(".+@.+.edu", email):
print("Valid")
else:
print("Invalid")
但是,请注意,可以键入malan@harvard?edu 并且它可以被认为是有效的。为什么会是这种情况呢?你可能认识到,在验证语言中,. 表示任何字符!
- 我们可以修改代码如下:
import re
email = input("What's your email? ").strip()
if re.search(".+@.+\.edu", email):
print("Valid")
else:
print("Invalid")
请注意我们如何使用“转义符”或\ 作为一种看待. 作为字符串的一部分,而不是验证表达式。malan@harvard.edu 被视为有效,其中malan@harvard?edu 无效。
- 既然我们使用了转义字符,现在是介绍“原始字符串”的好时机了。在Python中,原始字符串 不要 特殊字符格式,相反,每个字符都是按其表面值来取。例如\n,我们在前面的一节课上已经看到,在一个常规字符串中,这两个字符是如何变成一个的:返回一个特殊的换行符。但是在原始字符串中,\n 不被视为\n,特殊字符,而是作为单\ 和单个n。将r放在字符串前面会告诉Python解释器将该字符串视为原始字符串,类似于将f 告诉Python解释器将字符串视为格式字符串:
import re
email = input("What's your email? ").strip()
if re.search(r".+@.+\.edu", email):
print("Valid")
else:
print("Invalid")
现在,我们已经确保Python解释器不会将\. 作为特殊字符。相反,只需作为\ 后面是.- 用正则表达式的术语来说,这意味着匹配文字“."。
- 你可以想象我们的用户会给我们制造什么样的问题!例如,你可以输入一个句子,比如My email address is malan@harvard.edu 这整个句子都被认为是有效的。我们可以在编码中做得更精确。
- 在验证中,我们可以使用更多的特殊符号:
^ matches the start of the string
$ matches the end of the string or just before the newline at the end of the string
- 我们可以使用添加的词汇表修改代码,如下所示:
import re
email = input("What's your email? ").strip()
if re.search(r"^.+@.+\.edu#34;, email):
print("Valid")
else:
print("Invalid")
请注意,这样做的效果是查找与要验证的表达式的开头和结尾完全匹配的模式。My email address is malan@harvard.edu 现在被视为无效。 *实际运行仍然有效!**
- 我们建议我们可以做得更好!即使我们现在要查找字符串开头的用户名,@ 符号,以及最后的域名,我们可以输入@ 我们希望的符号!malan@@@harvard.edu 被视为有效!
- 我们可以添加如下词汇:
[] set of characters
[^] complementing the set
- 使用这些新发现的能力,我们可以修改表达式如下:
import re
email = input("What's your email? ").strip()
if re.search(r"^[^@]+@[^@]+\.edu#34;, email):
print("Valid")
else:
print("Invalid")
请注意,^ 意味着匹配字符串的开头。一直到表达式的结尾,$ 表示匹配字符串的末尾。[^@]+ 表示除@之外的任何字符。然后,我们有一个@。[^@]+\.edu 表示除@ 后面跟一个以.edu结尾的表达式之外的任何字符。键入malan@@@harvard.edu 现在被视为无效。
- 我们仍然可以进一步改进这个正则表达式。事实证明,对于电子邮件地址的内容有一定的要求!目前,我们的验证表达式太过宽松。我们可能只想允许在句子中常用的字符。我们可以修改代码如下:
import re
email = input("What's your email? ").strip()
if re.search(r"^[a-zA-Z0-9_]+@[a-zA-Z0-9_].+\.edu#34;, email):
print("Valid")
else:
print("Invalid")
请注意,[a-zA-Z0-9_] 告诉验证字符必须介于a 和z,介于A 和Z,介于0 和9 并可能包括_ 符号。测试输入,你会发现可以指出许多潜在的用户错误。
- 值得庆幸的是,勤奋的程序员已经将常见模式内置到正则表达式中。在这种情况下,你可以按如下方式修改代码:
import re
email = input("What's your email? ").strip()
if re.search(r"^\w+@\w.+\.edu#34;, email):
print("Valid")
else:
print("Invalid")
请注意,\w 与相同[a-zA-Z0-9_]。感谢辛勤工作的程序员们!
- 下面是一些我们可以添加到词汇表中的其他模式:
\d decimal digit
\D not a decimal digit
\s whitespace characters
\S not a whitespace character
\w word character, as well as numbers and the underscore
\W not a word character
- 现在我们知道不仅仅是.edu 电子邮件地址。我们可以修改我们的代码如下:
import re
email = input("What's your email? ").strip()
if re.search(r"^\w+@\w.+\.(com|edu|gov|net|org)#34;, email):
print("Valid")
else:
print("Invalid")
请注意,| 在我们的表达式中具有or的影响。
- 在我们的词汇表中添加更多的符号,下面是一些需要考虑的因素:
A|B either A or B
(...) a group
(?:...) non-caputuring version
Case Sensitivity
- 为了说明如何解决有关区分大小写的问题,EDU 和edu 等等之间存在差异,让我们将代码倒回到以下内容:
import re
email = input("What's your email? ").strip()
if re.search(r"^\w+@\w.+\.edu#34;, email):
print("Valid")
else:
print("Invalid")
请注意我们删除| 之前提供的语句。
- 回想一下,在re.search 函数中,有一个参数flags。
- 一些内置标志变量包括:
re.IGNORECASE
re.MULTILINE
re.DOTALL
请考虑如何在代码中使用它们。
- 因此,我们可以按如下方式更改代码。
import re
email = input("What's your email? ").strip()
if re.search(r"^\w+@\w.+\.edu#34;, email, re.IGNORECASE):
print("Valid")
else:
print("Invalid")
请注意我们如何添加第三个参数re.IGNORECASE。运行此程序时使用MALAN@HARVARD.EDU,则输入现在被视为有效。
- 请考虑以下电子邮件地址malan@cs50.harvard.edu。使用上面的代码,这将被视为无效。为什么会这样呢?
- 因为有一个额外的.,则程序会将其视为无效。实际上有效
- 事实证明,我们可以,看看我们以前的词汇,我们可以把想法组合在一起。
A|B either A or B
(...) a group
(?:...) non-caputuring version
- 我们可以修改代码如下:
import re
email = input("What's your email? ").strip()
if re.search(r"^\w+@(\w+\.)?\w+\.edu#34;, email, re.IGNORECASE):
print("Valid")
else:
print("Invalid")
请注意(\w+\.)? 通知编译器这个新表达式可以只存在一次,也可以根本不存在。malan@cs50.harvard.edu 和malan@harvard.edu 被认为是有效的。
- 有趣的是,到目前为止,我们对代码所做的编辑并没有完全包含所有的检查,以确保一个有效的电子邮件地址。实际上,下面是一个完整的表达式,人们必须键入以确保输入了一个有效的电子邮件地址:
^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
- re库中还有其他函数,你可能会发现它很有用。re.match 和re.fullmatch 你可能会发现它们非常有用。
- 你可以在Python的文档中了解更多信息re。
Cleaning Up User Input
- 你不应该期望你的用户总是遵循你的希望,干净的输入。实际上,用户经常会违背你作为一个程序员的意图。
- 有很多方法可以清理你的数据。
- 在终端窗口中,键入code format.py。然后,在文本编辑器中,编写如下代码:
name = input("What's your name? ").strip()
print(f"hello, {name}")
请注意,实际上我们已经创建了一个“hello world”程序。运行这个程序并输入David,它效果很好!然而,键入Malan, David 时,请注意程序是如何不按预期运行的。我们如何修改程序来清除这些输入呢?
- 按如下所示修改代码。
name = input("What's your name? ").strip()
if "," in name:
last, first = name.split(", ")
name = f"{first} {last}"
print(f"hello, {name}")
请注意last, first = name.split(", ") 如果名字中存在, ,然后,名称被标准化为名字和姓氏。运行我们的代码,键入Malan, David,你可以看到这个程序至少清理了一个用户键入意外内容的场景。
- 你可能会注意到,键入Malan,David 不带空格会导致编译器抛出错误。既然我们现在知道了一些正则表达式语法,让我们把它应用到我们的代码中:
import re
name = input("What's your name? ").strip()
matches = re.search(r"^(.+), (.+)#34;, name)
if matches:
last, first = matches.groups()
name = f"{first} {last}"
print(f"hello, {name}")
请注意,re.search 可以返回一组从用户输入中提取的匹配项。如果匹配,通过re.search返回。运行此程序,键入David Malan 请注意if 条件不运行,并返回名称。如果通过键入Malan, David,也会正确传回名称。
- 我们可以使用matches.group命令请求特定组返回。我们可以修改代码如下:
import re
name = input("What's your name? ").strip()
matches = re.search(r"^(.+), (.+)#34;, name)
if matches:
last = matches.group(1)
first = matches.group(2)
name = f"{first} {last}"
print(f"hello, {name}")
请注意,在此实现中,group 不是复数(没有s).
- 我们的规范可以进一步收紧,如下所示:
import re
name = input("What's your name? ").strip()
matches = re.search(r"^(.+), (.+)#34;, name)
if matches:
name = matches.group(2) + " " + matches.group(1)
print(f"hello, {name}")
请注意group(2) 和group(1) 用一个空格连接在一起。第一组是逗号左边的。第二组是逗号右边的。
- 仍然认识到打字Malan,David 不带空格仍然会破坏我们的代码。因此,我们可以做如下修改:
import re
name = input("What's your name? ").strip()
matches = re.search(r"^(.+), *(.+)#34;, name)
if matches:
name = matches.group(2) + " " + matches.group(1)
print(f"hello, {name}")
请注意添加了* 在我们的验证语句中。这个代码现在会接受并正确处理Malan,David。此外,它还将正确处理“David,Malan,有很多空格在David前”。
- 这是非常常见的利用re.search 就像我们在前面的示例中所做的那样,其中matches 是在之后的一行代码上。不过,我们可以组合这些语句:
import re
name = input("What's your name? ").strip()
if matches := re.search(r"^(.+), *(.+)#34;, name):
name = matches.group(2) + " " + matches.group(1)
print(f"hello, {name}")
请注意我们是如何将两行代码组合在一起的。:= 运算符从右到左赋值,同时允许我们问布尔问题。把你的头转向一边,你就会明白为什么这被称为海象运算符。
- 你可以在Python的文档中了解更多信息re。
Extracting User Input
- 到目前为止,我们已经验证了用户的输入并清理了用户的输入。
- 现在,让我们从用户输入中提取一些特定的信息。code twitter.py 并在文本编辑器窗口中编写如下代码:
url = input("URL: ").strip()
print(url)
注意如果我们输入https://twitter.com/davidjmalan,它准确地显示了用户键入的内容。然而,我们如何能够只提取用户名而忽略URL的其余部分呢?
- 你可以想象一下,我们如何能够简单地去掉标准Twitter URL的开头。我们可以尝试如下:
url = input("URL: ").strip()
username = url.replace("https://twitter.com/", "")
print(f"Username: {username}")
请注意replace 方法允许我们查找一个项目并将其替换为另一个项目。在本例中,我们将查找URL的一部分并将其替换为空。键入完整URLhttps://twitter.com/davidjmalan,程序有效地输出了用户名。但是,目前这个程序有什么缺点呢?
- 如果用户只是输入twitter.com 而不是包含https:// 你可以想象许多场景,用户可能输入或忽略输入URL的某些部分,这些部分会导致程序产生奇怪的输出。为了改进这个程序,我们可以编写如下代码:
url = input("URL: ").strip()
username = url.removeprefix("https://twitter.com/")
print(f"Username: {username}")
请注意我们如何利用removeprefix 方法。此方法将移除字符串的开头。
- 正则表达式只允许我们简洁地表达模式和目标。
- 在re 库中,有一个名为sub方法。此方法允许我们用其他内容替换模式。
- sub 方法如下
re.sub(pattern, repl, string, count=0, flags=0)
请注意pattern 引用我们要查找的正则表达式。然后,有一个repl 字符串来替换模式。最后,还有string 我们要做的替换。
- 在我们的代码中实现这个方法,我们可以修改我们的程序如下:
import re
url = input("URL: ").strip()
username = re.sub(r"https://twitter.com/", "", url)
print(f"Username: {username}")
注意执行这个程序和输入https://twitter.com/davidjmalan 会产生正确的结果。但是,我们的代码中仍然存在一些问题。
- 协议、子域以及用户在用户名后输入URL的任何部分的可能性都是这段代码仍然不理想的原因。我们可以进一步解决这些缺点,如下所示:
import re
url = input("URL: ").strip()
username = re.sub(r"^(https?://)?(www\.)?twitter\.com/", "", url)
print(f"Username: {username}")
请注意^ 添加到url中。请注意. 可能被编译器不正确地解释。因此,我们使用\ 来转义以使其成为\. 。为了同时容忍http 和https,我们添加一个? 到https?,使s 可选。此外,为了容纳www ,我们加上了(www\.)?。 最后,为了防止用户决定完全忽略协议,http:// 或https:// 成为可选的,使用(https?://)。
- 尽管如此,我们仍然盲目地期望用户输入的是一个网址,确实有一个用户名。
- 利用re.search的知识,我们可以进一步改进我们的代码。
import re
url = input("URL: ").strip()
matches = re.search(r"^https?://(www\.)?twitter\.com/(.+)#34;, url, re.IGNORECASE)
if matches:
print(f"Username:", matches.group(2))
请注意我们是如何在用户提供的字符串中搜索上面的正则表达式的。特别是使用(.+)$ 正则表达式捕获出现在URL末尾的内容。因此,如果用户输入URL而没有输入用户名,则不会显示任何输入。
- 进一步加强我们的计划,我们可以利用我们的:= 操作符如下:
import re
url = input("URL: ").strip()
if matches := re.search(r"^https?://(?:www\.)?twitter\.com/(.+)#34;, url, re.IGNORECASE):
print(f"Username:", matches.group(1))
请注意,?: 告诉编译器它不必捕获正则表达式中该位置的内容。
- 不过,我们可以更明确地确保输入的用户名是正确的。使用Twitter的文档,我们可以在正则表达式中添加以下内容:
import re
url = input("URL: ").strip()
if matches := re.search(r"^https?://(?:www\.)?twitter\.com/([a-z0-9_]+)", url, re.IGNORECASE):
print(f"Username:", matches.group(1))
请注意,[a-z0-9_]+ 告诉编译器只需要a-z,0-9的_ 作为正则表达式的一部分。+ 表示我们需要一个或多个字符。
- 你可以在Python的文档中了解更多信息[re(https://docs.python.org/3/library/re.html)。
Summing Up
现在,你已经学习了一种全新的正则表达式语言,可以利用它来验证、清理和提取用户输入。
- 一般表示式
- 区分大小写
- 清除用户输入
- 提取用户输入