hav-cs50-merge-04

哈佛 CS50 中文官方笔记(五)

网络安全

第零讲

原文:cs50.harvard.edu/cybersecurity/notes/0/

  • 保护账户

  • 安全

  • 防御攻击

  • 国家标准与技术研究院 (NIST)

  • 双因素认证 (2FA) 或多因素认证 (MFA)

  • 一次性密码 (OTP)

  • 键盘记录

  • 凭证填充

  • 社会工程学

  • 钓鱼

  • 中间人攻击

  • 单点登录 (SSO)

  • 密码管理器

  • 通行密钥

  • 总结

保护账户

  • 这是 CS50 对网络安全的介绍。

  • 今天,我们将关注账户的安全。

  • 让我们先从谈论安全本身开始。

安全

  • 我们可以将现实世界中的安全想象成一把物理锁的钥匙。

  • 在数字世界中,有许多构建安全性的基石。

  • 授权是指验证你确实是应该有权访问此账户的人。

  • 用户名是证明你应该有权访问账户的一种方式。

  • 密码是另一种证明你应该有权访问账户的方式。

  • 理论上,只有你应该能够提供有效的用户名和密码。

  • 字典攻击是恶意行为者尝试猜测你的密码的一种方式。实际上,黑客可能会通过尝试大量可能的密码列表来进行“暴力攻击”以尝试猜测你的密码。因此,拥有一个非常好的密码来防御攻击是非常重要的。

  • 在考虑安全时,人们应该考虑可用性和安全之间的权衡。一个高度安全的服务可能会变得不太易用。因此,当你考虑保持安全的选择时,考虑什么对你来说最有意义。

防御攻击

  • 考虑一下,如果你的密码(无论是用于手机还是其他用途)仅由四位数字组成,你将有多少种可能的数字组合。这里有 10,000 种可能的数字。通常,我们可以这样考虑可能性:

    10 x 10 x 10 x 10 
    

    注意,在最坏的情况下,恶意行为者需要尝试 10,000 种可能的密码。

  • 我们可以尝试用代码来表示这一点。VS Code是一个开发环境,我们可以在这里编写和执行代码。

  • 考虑以下代码表示的上述问题:

    from string import digits
    
    for i in digits:
        for j in digits:
            for k in digits:
                for l in digits:
                    print(i, j, k, l) 
    

    注意,这段代码是用 Python 编写的,它遍历每个可能的数字组合

  • 终端窗口(我们可以向计算机发出命令的地方)中执行crack.py,我们可以看到对手只需要几毫秒就能生成所有可能的密码。

  • 如果我们要求一个由四个字母组成的密码会发生什么?

  • 如果我们允许 26 个字母的大小写版本,我们可以用数学方式表示为:

    52 x 52 x 52 x 52 
    

    注意,我们有超过 7,000,000 种可能性。

  • 我们可以按照以下方式修改我们的代码:

    from string import ascii_letters
    
    for i in ascii_letters:
        for j in ascii_letters:
            for k in ascii_letters:
                for l in ascii_letters:
                    print(i, j, k, l) 
    

    注意,我们调用了ascii_letters,它包括每个字母的大小写版本。类似于我们之前的程序,这个程序遍历所有可能的组合。

  • 执行此代码,我们发现黑客发现所有可能的密码仍然不需要太多努力。

  • 如果我们要求一个由四个字母、数字或标点符号组成的密码会发生什么?我们将有超过 7,800,000 种可能性可供选择!

  • 我们可以按照以下方式修改我们的代码:

    from string import ascii_letters, digits, punctuation
    
    for i in ascii_letters + digits + punctuation:
        for j in ascii_letters + digits + punctuation:
            for k in ascii_letters + digits + punctuation:
                for l in ascii_letters + digits + punctuation:
                    print(i, j, k, l) 
    
  • 执行此代码时,我们注意到在最坏的情况下,发现所有可能的密码需要的时间明显更长。

  • 从上面的内容中,最重要的启示是,只要我们在时间上提高对手的门槛,对手就越不可能有时间破解你的密码。然而,对于用户来说,更长的、更复杂的密码输入时间更长,也更难以记住。因此,在安全性和可用性之间需要找到一个平衡点。

美国国家标准与技术研究院(NIST)

  • NIST 发布有关如何更有效地保护账户的建议。

  • 你可以在自己的工作中使用他们的建议和最佳实践,也许在你的工作场所或商业活动中也是如此。他们的建议中包括以下关于密码的内容(为了简洁而改写):

    • 记忆中的秘密至少应有八个字符长度。

    • 验证器应允许所有打印的 ASCII 字符和长度不超过 64 个字符的 Unicode 符号。

    • 验证器应将预期的秘密与可用的字典单词、重复序列、泄露的密码列表和上下文特定单词进行比较。

    • 验证器不应允许未经认证的申请人访问密码提示。

    • 验证器不应要求定期更改密码。

    • 验证器应限制失败的认证尝试次数,并锁定潜在的对手。

双因素认证(2FA)或多因素认证(MFA)

  • 多因素认证有三个组成部分。

    • 知识:只有你知道的东西。

    • 拥有:只有授权用户拥有的物品或设备。

    • 内在性:只有你可以获得的因素,比如你的指纹、面部或其他生物识别特征。

一次性密码(OTP)

  • 可以获得一个特殊的关键链或设备,它提供一次性密码。

  • 通常,OTP 是从设备或应用程序获得的。

  • 一些 OTP 方法比其他方法更安全。

  • 文本消息基于的 OTP 很容易通过 SIM 卡交换被欺骗,其中对手获取并克隆 SIM 卡,从而获取你的短信。

  • 更安全的是从安全设备上的应用程序获得的 OTP,例如手机上的身份验证应用程序。

键盘记录

  • 用户名、密码和 OTP 都容易受到对手记录您按键的攻击。

  • 键盘记录是通过在计算机上安装恶意软件来完成的。

  • 最好确保您只登录到您有权访问的设备。

凭证填充

  • 另一种攻击,凭证填充,涉及使用从受损害网站获得的用户名和密码列表在另一个网站上。

  • 如果您在多个网站上使用相同的密码,最好将它们更改为唯一的密码。

社会工程

  • 与技术攻击不同,社会工程攻击涉及利用社会压力和信任来破坏您的凭证。

  • 一个人可能伪装成受信任的第三方来获取您的凭证或您生活的细节。

  • 此外,对手可能试图找到有关您生活的细节,如您的宠物名字等。

钓鱼

  • 钓鱼利用社会工程以技术方式,通过伪装成受信任的网站来获取您的凭证和细节。

  • 例如,您可能被引导到一个看起来像谷歌登录页面但实际上是对手页面的地方。

  • 永远不要盲目信任电子邮件中提供的链接。考虑直接在网页浏览器中输入受信任的 URL。

中间人攻击

  • 在您和您下载的数据源之间,如路由器和交换机等设备,可能被非常复杂的攻击者攻破。

单点登录 (SSO)

  • 由于许多不同的服务需要许多不同的密码要求,并且考虑到之前提供的关于永远不要为不同的服务使用相同密码的建议,有多种方法可以增强您的安全性。

  • SSO 允许您使用 Google 或 Facebook 登录来访问 Google 或 Facebook 不提供的服务。

  • 因此,您能够轻松地以更少的摩擦和更高的安全性访问其他服务。

密码管理器

  • 密码管理器是一款可以管理复杂密码并为您保存的软件。

  • 这允许您不必记住密码。

  • 此外,许多密码管理器能够识别钓鱼网站。

  • 与已经存在多年的浏览器密码保存功能不同,密码管理器是另一款独立的软件,可以在多个服务中提供您的密码。

  • 缺点是,实际上,您是在“把所有的鸡蛋都放在一个篮子里。”您将需要记住一个密码来访问所有其他密码。

Passkeys

  • 一种新兴技术,passkeys 是自动生成的密码,利用了密码学。

  • Passkeys 涉及一个公钥和一个私钥。公钥由服务(如网站)持有,而私钥由您的设备持有。

  • Passkeys 将使您能够登录而无需输入密码。

  • 然而,为了更好地了解这项技术,我们需要学习更多关于密码学的知识。

总结

在本课中,你了解了安全和便利性之间的权衡。你还了解了各种可能使你和他人变得脆弱的攻击方式。最后,你学习了保护登录凭证的一些方法。具体来说,你学习了……

  • 安全性和便利性之间存在权衡。

  • 通常,你的行为和意识是使你免受数字对手侵害的关键。

  • NIST 提供了与安全相关的指南。

  • 双因素认证(2FA)、多因素认证(MFA)和一次性密码(OTPs)是你可以采取的使你更加安全的一些方法。

  • 常见的攻击包括键盘记录、钓鱼、凭证填充和社会工程学。

  • 通过使用单点登录(SSO)或密码管理器,你可以享受更高的安全性。

  • 在未来,密钥(Passkeys)将提供更高的安全性。

欢迎下次再来!

第一讲

原文:cs50.harvard.edu/cybersecurity/notes/1/

  • 保护数据

  • 密码

  • 哈希函数

  • 盐值

  • 单向哈希函数

  • 代码

  • 密码

  • 密钥

  • 密码分析

  • 公钥密码学

  • 密钥交换

  • 数字签名

  • 密钥

  • 传输中的加密

  • 删除

  • 全盘加密

  • 量子计算

  • 总结

保护数据

  • 这是 CS50 的网络安全入门。

  • 上周,我们回顾了账户。

密码

  • 我们关注的是我们保持数据安全的责任。

  • 然而,第三方总是参与我们数据的存储。

  • 你可以想象一个系统如何将用户名和密码存储在文本文件中。

  • 你也可以想象攻击者如何获取这样的文本文件。

  • 我们能否最小化存储密码的明文风险?

哈希函数

  • 哈希函数 是一种方法,通过它将一些明文转换为哈希值,使其更难以阅读。

  • 因此,哈希函数 创建一个哈希值。将密码提供给哈希函数,然后输出为哈希值。

  • 没有访问精确的哈希函数,攻击者无法输出正确的密码。

  • 通常,我们希望哈希函数输出一些非常难以理解且缺乏模式的东西。因此,攻击者无法猜测算法正在做什么。

  • 由于用户名和哈希值存储在服务器上,攻击者无法轻易访问该服务器上的账户。

  • 当用户现在输入密码进行登录时,密码会被传递给哈希算法再次进行比对,将创建的哈希值与存储的哈希值进行比较。

  • 因此,我们增加了攻击者访问受保护数据所需的成本、时间和资源。

  • 尽管如此,字典攻击 可以将字典中的值一个接一个地输入到哈希函数中,作为破解它的一种方式。

  • 此外,暴力攻击 可以尝试逐个字符地顺序输入,以尝试破解密码。

  • 想象一下,彩虹表 是另一种威胁,其中攻击者有一个哈希表中所有潜在哈希值的表。然而,这需要数以千计的存储容量才能完成。

  • 最后,当用户使用相同的密码且这些密码的哈希值完全相同时,会出现一个问题。我们该如何解决这个问题?

盐值

  • 盐值 是一个过程,通过向哈希函数中“撒入”一个附加值,使得哈希值发生变化。

  • 使用盐值几乎可以保证用户提供由用户提供的哈希值,即使是那些具有相同密码的用户,也会收到不同的哈希密码。

  • 因此,再次强调,对手破解这些密码的成本相当高。

  • NIST 建议对记忆中的秘密进行哈希处理和加盐。

单向哈希函数

  • 单向哈希函数 用代码编写,接受任意长度的字符串并输出固定长度的哈希值。

  • 利用这样的函数,持有哈希值和哈希函数的人永远不会知道原始密码。

  • 事实上,在某些使用单向哈希函数的系统中,某些密码可能映射到相同的哈希值。

密码

  • 密码学是研究如何将安全数据从一方传输到另一方的学科。

  • 我们可以通过密码来确保数据的安全。

  • 密码将我们想要说的单词转换成不易理解的单词串。

  • 编码 涉及将明文转换为密文。

  • 解码 是其相反过程,将密文转换为明文。

密码

  • 加密涉及将明文 加密 成密文。

  • 这个加密过程被称为 加密。解密它们的过程被称为 解密

密钥

  • 密钥实际上是很大的字符串。这些密钥用于加密和解密。

  • 对称密钥密码学 涉及将密钥和明文传递到加密算法中,其中输出密文。

  • 在这种情况下,发送者和接收者都彼此有一个共享的秘密,即他们都有访问加密和解密算法的权限。

密码分析

  • 密码分析 是一个研究领域和实践领域,个人在这里研究如何加密和解密数据。

  • 证据表明你是这门课程的一部分,你也可能对密码分析感兴趣。

公钥密码学

  • 你可以想象一个场景,其中安全数据的发送者和接收者可能从未亲自见过面。那么,一方如何在这两个当事人之间建立共享的秘密呢?

  • 公钥加密非对称密钥加密 解决了这个问题。

  • 首先,发送者使用公钥和明文,将这些输入到算法中。这会产生密文。

  • 第二步,接收者使用他们的密钥,将这个密钥和密文输入到算法中。这会产生解密后的文本。

  • RSA 是一种加密标准,描述了这一过程。

密钥交换

  • 另一种算法称为 Diffie-Hellman,其目标是密钥交换。

  • 使用一个约定的值 g 和一个素数值 p

  • 当事人 A 和当事人 B 有一个共享的秘密值,称为 s

  • 当事人 A 和当事人 B 都有自己的私钥。

数字签名

  • 使用公钥和私钥的构建块,你可以使用这些来签署文档。

  • 可以通过两步过程 签署 文档。

  • 第一步,将消息,即文档的内容,传递到哈希函数中,产生哈希值。

  • 第二步,将私钥和哈希值传递到数字签名算法中,这会产生数字签名。

  • 收件人可以通过将消息、文档的内容传递给哈希函数并接收一个哈希值来验证你的数字签名。然后,收件人将提供的公钥和签名传递给解密算法,结果产生一个应该与先前计算的哈希值相匹配的哈希值。

密钥

  • 密钥WebAuthn是一种越来越广泛可用的技术。

  • 很快,用户名和密码将变得不那么常见。

  • 密钥将依赖于设备。例如,当你在手机上访问一个要求你创建账户的网站时,你的手机将生成一个公钥和一个私钥。

  • 然后,你将发送你的公钥到网站。

  • 从那时起,要使用该设备登录网站或同步你的密钥跨设备的服务,你将传递一个与挑战值配对的私钥。一个算法将产生一个签名。

传输中的加密

  • 传输中的加密与在数据网络中移动的数据的安全有关。

  • 想象一个场景,其中两方想要相互通信。

  • 我们希望防止第三方在中间拦截数据。

  • 作为中间人的第三方服务,例如电子邮件提供商,确实可能会阅读你的电子邮件或查看你的消息。

  • 端到端加密是一种方式,通过这种方式,用户可以保证中间没有第三方可以读取数据。

删除

  • 让我们现在考虑一个相当平凡的情景,比如删除一个文件。

  • 一旦在计算机上删除文件,那些已删除文件的指纹可能仍然存在于你的计算机上。

  • 操作系统通常通过简单地忘记它们的位置来删除文件。因此,计算机可能会用新文件覆盖以前的文件。

  • 然而,并不能保证你硬盘上的空闲空间完全清除了旧文件的指纹。

  • 安全删除是一个过程,通过这个过程,所有已删除文件的残留部分都被转换为零、一或随机的零和一序列。

全盘加密

  • 全盘加密静态加密完全加密了硬盘的内容。

  • 如果你的设备被盗或者你卖掉了你的设备,没有人将能够访问你的加密数据。

  • 然而,一个缺点是如果你丢失了密码或者你的面部变化足够大,你将无法访问你的数据。

  • 另一个缺点是黑客可能会通过勒索软件使用这种相同类型的技术来加密你的硬盘并将其作为人质。

量子计算

  • 量子计算是一种新兴的计算机技术,可能能够为对手提供指数级的计算能力。

  • 这种技术可能被对手用来减少猜测密码和破解加密所需的时间。

  • 希望在我们获得这种计算能力之前,坏人还没有。

总结

在本课中,你学习了关于数据安全的内容。你学习了...

  • 网站和服务如何存储密码;

  • 如何将文本值哈希以确保保密性;

  • 关于加盐、单向哈希函数、密钥、加密和解密在安全存储数据中的作用;

  • 关于公钥和私钥;

  • 技术如何利用公钥和私钥来保持数据安全;

  • 如何确保自己的硬件安全;

  • 量子计算带来的新兴利益和威胁。

欢迎下次再见!

第二讲

原文:cs50.harvard.edu/cybersecurity/notes/2/

  • Securing Systems

  • Wi-Fi

  • HTTP

  • HTTPS

  • VPN

  • SSH

  • Ports

  • Malware

  • 杀毒软件

  • 总结

Securing Systems

  • 这是 CS50 的网络安全入门。

  • 这周,我们将重点关注网络和系统。

  • 上次,我们介绍了加密作为保护信息的一种方式。

Wi-Fi

  • 很可能,您已经意识到存在加密和非加密的网络。

  • 加密网络使用加密来保护您与其他设备之间的数据。

  • Wi-Fi 保护接入WPA 是一种用于保护网络的加密形式。

HTTP

  • 超文本传输协议,或 HTTP,是一种未加密的数据传输方式。

  • 利用 HTTP,您容易受到 中间人攻击,攻击者可以在下载的内容中注入额外的 HTML 代码。通过 HTTP 访问的所有网页都可能被注入广告。此外,还可能插入恶意代码。

  • 事实上,还有其他威胁。数据包嗅探 是攻击者查看双方之间传输的数据的一种方式。您可以想象,如果一张信用卡号被放在一个未加密的数据包中,攻击者确实可以检测并窃取它。

  • Cookies 是网站放在您电脑上的小文件。Cookies 可以被网站用来追踪您的身份,显示您的电子邮件,或追踪您的购物车。Cookies 使得您容易受到 会话劫持 的攻击,攻击者可能会注入一个 超级 cookie 来追踪您。

  • 如何防御此类威胁?

HTTPS

  • HTTPS 是一种安全的 HTTP 协议。

  • 双方之间的流量被加密。

  • 这是通过 TLS(传输层安全性)和公钥加密来实现的。

  • 一个网站有一个由第三方(称为 X.509 类型的 证书)签名的公钥。这些网站也有一个私钥。

  • 证书颁发机构CA 是发行证书的可信第三方公司。

  • 当您访问一个网站时,您的浏览器会下载该网站的证书,通过算法运行它,并创建一个哈希值。

  • 然后,它使用网站的公钥和提供的证书签名,将其提供给算法以验证它创建的哈希值与之前找到的完全一致。

  • 如果这些匹配,则网络浏览器应用程序会满意这是一个安全网站。

  • HTTPS 在数学上确实能让我们保持安全,但也有例外。

  • SSL 拦截 是攻击者使用网站上的 HTTP 来重定向流量到恶意网站的一种攻击。攻击者甚至可能将您重定向到一个不是预期网站的 HTTPS-加密域。

  • 缓解这种威胁的一种方法是通过实施 HSTSHTTP 严格传输安全,服务器告诉浏览器将所有流量导向安全连接。

VPN

  • VPN,或称虚拟专用网络,在两个点之间建立加密通道。

  • 在 VPN 中,所有流量都是加密的。

  • 然而,也有一些副作用。

  • 由于双方之间的管道会导致从第二方接收 IP 地址,因此它将向整个网络中的服务显示您的 IP 地址是第二方的:而不是您的原始 IP 地址!

  • 事实上,人们经常使用 VPN 来伪装成另一个国家的人。

SSH

  • SSH是一种安全的协议,您可以通过它远程执行命令。

  • 如果想与远程计算机通信并执行命令,可以发出一个ssh命令。以下是一个使用 SSH 命令连接到斯坦福大学服务器的示例。您仍然需要适当的凭证和权限才能成功连接。

    ssh stanford.edu 
    
  • 如果有适当的访问权限,可以直接在远程服务器上执行命令。

端口

  • 端口号用于将网络流量导向服务器上的特定服务。

  • 例如,端口80指向 HTTP,443指向 HTTPS,22指向 SSH。

  • 服务器监听这些端口以接收传入的流量。

  • 因此,对手可能会进行端口扫描,尝试所有可能的端口,以查看它们是否接受流量。

  • 渗透测试是一项专业可能参与的活动,用于检查端口相关的安全漏洞。

  • 道德黑客是合法的业务,用于测试此类漏洞。

  • 防火墙是一种软件,通过阻止未经授权的访问来保护各种服务,包括来自设备上受损害服务的访问。

  • 防火墙利用IP 地址,这是分配给网络中每台计算机的唯一数字,以防止外部人员参与流量。

  • 防火墙也可以使用深度数据包检查,其中它们检查数据包中的内容,以寻找可能对您的公司感兴趣的材料。这可以用来检查您是否在给媒体或其他可能被视为您的公司对手的各方发邮件。

  • 通过代理使用深度数据包检查,其中中间设备被用作流量进出网络的路径。学校或公司可能会在这个代理上更改 URL,记录您尝试浏览的 URL,并希望保护您免受潜在有害行为的影响。

恶意软件

  • 恶意软件是损害计算机或损害其安全的恶意软件。

  • 病毒是一种附着到您的计算机上的软件。一旦安装,它可以做几乎所有的事情!

  • 蠕虫是一种恶意软件,可以通过安全漏洞从一个计算机移动到另一个计算机。

  • 僵尸网络是一种恶意软件,一旦安装在您的计算机上,就会感染其他计算机,并且可以被对手用来向成千上万的感染计算机发布命令。

  • 被僵尸网络感染的电脑可以被用来发起拒绝服务攻击,即向服务器发送大量请求,目的是减缓或关闭它。由于僵尸网络中有如此多的电脑,这种攻击类型可以被称为来自数千个 IP 地址的分布式拒绝服务攻击

抗病毒软件

  • 抗病毒软件检测病毒,并希望可以将其移除。

  • 自动更新必须启用,以修复软件先前版本中的安全漏洞。

  • 然而,一个人可能仍然容易受到零日攻击的攻击,这种攻击利用了在软件公司有机会创建修复方案之前软件中的未知漏洞。

总结

在本课中,你学习了关于系统安全的内容。你学习了…

  • 无线网络中网络是如何被保护的;

  • 如何使用不安全和安全协议在网络上发送和接收数据;

  • 如何使用虚拟专用网络加密网络流量;

  • 关于端口以及对手利用它们的漏洞;

  • 关于各种恶意软件;

  • 抗病毒软件如何帮助防止恶意软件安装到你的电脑上。

次次见!

第三讲

原文:cs50.harvard.edu/cybersecurity/notes/3/

  • 保护软件

  • 钓鱼攻击

  • 代码注入

  • 反射攻击

  • 存储攻击

  • 字符转义

  • HTTP 头部

  • SQL 注入

  • 预处理语句

  • 命令注入

  • 开发者工具

  • 服务器端验证

  • 跨站请求伪造 (CSRF)

  • 任意代码执行 (ACE)

  • 开源软件

  • 封闭源代码软件

  • 应用商店

  • 软件包管理器

  • 漏洞赏金

  • 识别漏洞

  • 总结

保护软件

  • 这是 CS50 的网络安全入门。

  • 这周,让我们专注于保护你使用的软件或你创建的软件。

  • 上次,我们介绍了各种攻击,攻击者可以使用这些攻击从你那里获取信息。

钓鱼攻击

  • 我们介绍了一种称为 钓鱼攻击 的攻击,其中攻击者欺骗你提供某种信息。

  • 例如,在网站的源代码中,在 HTML 语言中,你可能看到如下代码:

    <p>...</p> 
    

    注意,上面的代码中,一个段落开始并结束。它以一个开标签和一个闭标签开始。

  • 类似地,网页中的链接使用一种称为 锚点标签 的特定类型的标签,将用户从一个网页带到另一个网页。

  • 这样的代码看起来是这样的:

    <a href="https://harvard.edu">Harvard</a> 
    

    注意,此代码是一个锚点标签,允许用户点击“哈佛”一词并访问 harvard.edu

  • 在实际的网页上,你可以将鼠标移到这样的链接上,并看到这个精确链接将带你去哪里。

  • 攻击者可能会利用你注意力不集中的弱点,声称你正在链接到一个网页,而实际上你正在链接到另一个网页。

  • 例如,攻击者可能提供如下代码:

    <a href="https://yale.edu">https://harvard.edu</a> 
    

    注意,此代码是一个锚点标签,它欺骗用户点击 https://harvard.edu,而实际上它浏览到的是 yale.edu。虽然用户会认为他们点击的是哈佛的链接,但实际上他们正在浏览耶鲁。

  • 你可以想象这种策略如何被攻击者用来欺骗你,让你认为你正在访问一个网站,而实际上你正在访问另一个网站。

  • 攻击者经常创建网站的假版本,目的就是为了欺骗用户在这些网站上输入敏感信息。例如,如果你是一名哈佛学生访问这样的假哈佛网站,你可能会尝试登录并提供你的用户名和密码给攻击者。

代码注入

  • 跨站脚本攻击,或称 XSS,是一种攻击形式,其中网站被欺骗通过用户的输入运行恶意代码。

  • 例如,在 Google 上,当你搜索“猫”这个术语时,注意这个术语如何在屏幕上的其他地方出现,显示这个搜索的结果数量。

  • 想象一下,一个对网络略知一二的对手可能会将代码作为输入插入,以此来欺骗网站运行此类代码。

  • 例如,考虑以下可能被插入搜索字段的代码:

    <script>alert('attack')</script> 
    

    注意,此脚本显示了一个通知,说“攻击。”虽然由于安全原因,Google 网站不会显示此类通知,但这代表了对手可能尝试的事情。

  • 如果网站盲目地复制用户输入并输出对手输入的内容,这将是一个重大的安全问题。

反射攻击

  • 反射攻击 是一种利用网站接受输入的方式,欺骗用户的浏览器发送请求信息,从而导致攻击的攻击方式。

  • 想象一下,一个用户可能会被欺骗点击一个结构如下链接:

    <a href="https://www.google.com/search?q=%3Cscript%3Ealert%28%27attack%27%29%3C%2Fscript%3E">cats</a> 
    

    注意,此链接包含上面展示的精确脚本,该脚本旨在在用户的屏幕上创建攻击警报。

  • 用户的操作会欺骗他们自己的网络浏览器反射回针对用户的攻击。

存储攻击

  • 一个网站可能会受到攻击,被欺骗存储恶意代码。

  • 想象一下,有人可能会通过电子邮件发送恶意代码。如果电子邮件提供商盲目接受发送给它的任何代码,接收恶意代码的任何人可能都会成为攻击的受害者。

字符转义

  • 服务使用 字符转义 作为防止此类攻击的一种方式。软件应该转义可能引起麻烦的字符,这些字符代表常见的基于编码的字符。

  • 例如,以下代码...

    <p>About 6,420,000,000 <script>alert('attack')</script></p> 
    

    将由安全软件输出...

    <p>About 6,420,000,000 &lt;script&gt;alert('attack')&lt;/script&gt;</p> 
    

    虽然有点晦涩,但请注意 &lt; 用于转义可能对软件构成威胁的潜在字符。上述输出的结果变成了恶意代码的纯文本表示。

  • 常见的转义字符包括:

    • &lt;,这是小于号,“<”

    • &gt;,这是大于号,“>”

    • &amp;,这是和号,“&”

    • &quot;,这是双引号,“, 本身

    • &apos;,这是单引号,“‘”

HTTP 头部

  • 回想一下,HTTP 头部 是提供给浏览器的一些附加指令。

  • 考虑以下头部:

    Content-Security-Policy: script-src https://example.com/ 
    

    注意,上述网站头部的安全策略仅允许通过单独的文件加载 JavaScript,通常以 .js 结尾。因此,当此安全策略生效时,HTML 中的 <script> 标签不会被浏览器运行。

  • 同样,以下头部将只允许从 .css 文件加载 CSS:

    Content-Security-Policy: style-src https://example.com/ 
    

    注意 style-src 指示仅允许从 .css 文件加载的 CSS。

SQL 注入

  • 结构化查询语言SQL 是一种编程语言,允许从数据库中检索特定信息。

  • 考虑攻击者可能如何尝试欺骗 SQL 执行恶意代码。

  • 考虑以下 SQL 代码:

    SELECT * FROM users
    WHERE username = '{username}' 
    

    注意,这里插入的用户输入的用户名被插入到 SQL 代码中。

  • 永远不要信任用户的输入。

  • 所有输入都应清理,以确保所有用户输入都被转义。

  • 假设攻击者将以下代码插入到用户名字段中:

    malan'; DELETE FROM users; –- 
    

    注意,除了用户名外,还插入了恶意代码。

  • 由于上述输入,结果是以下内容:

    SELECT * FROM users
    WHERE username = 'malan'; DELETE FROM users; --' 
    

    注意,攻击者的恶意输入向查询中添加了额外的代码。结果是系统中的所有用户都被删除。系统上的每个账户都被删除。

  • 假设用户被要求如下输入用户名和密码:

    SELECT * FROM users
    WHERE username = '{username}' AND password = '{password}' 
    

    注意用户被要求输入用户名和密码。

  • 攻击者可能会将以下内容插入到密码字段中:

    ' OR '1'='1 
    
  • 然后,以下 SQL 代码将执行:

    SELECT * FROM users
    WHERE username = 'malan' AND password = ''
    OR '1'='1' 
    

    注意语法上,这会导致向数据库中的所有用户提供。

  • 为了更清楚地看到这一点,注意下面添加了额外的括号:

    SELECT * FROM users
    WHERE (username = 'malan' AND password = '')
    OR '1'='1' 
    

    注意,此代码将显示所有用户名和密码组合为真的用户 OR 所有用户。

  • 事实上,上述输入始终为真。通过这种安全漏洞,攻击者可能了解系统上所有用户的信息,包括管理员。

预处理语句

  • 预处理语句 是预先设计的代码片段,可以正确处理许多数据库功能,包括用户输入。

  • 这样的语句,例如,确保用户输入的数据被正确转义。

  • 预处理语句将采用以下代码…

    SELECT * FROM users
    WHERE username = '{username}' 
    

    并将其替换为…

    SELECT * FROM users
    WHERE username = ? 
    
  • 预处理语句将查找任何 ' 字符并将其替换为 ''。因此,我们上面显示的先前攻击将通过预处理语句呈现:

    SELECT * FROM users
    WHERE username = 'malan''; DELETE FROM users; --' 
    

    注意,在“malan”的末尾的 ' 被替换为 '',使恶意代码无法运行。

  • 结果是恶意字符被转义,使得恶意代码无法运行。

命令注入

  • 命令行界面 是一种使用基于文本的命令来运行计算机系统的方法,与点击菜单和按钮相反。

  • 命令注入 攻击是在底层系统本身发出命令。

  • 如果从用户输入传递命令到命令行,可能会造成灾难性的后果。

  • 两个常见的漏洞点是 systemeval,在这些地方,如果你未对用户输入进行清理,恶意命令可能会在系统上执行。

  • 总是阅读文档以了解如何转义用户的输入。

开发者工具

  • 让我们回到 HTML 和网络的世界。

  • 在浏览器上下文中,开发者工具 允许你探索网页中的一些底层代码。

  • 考虑我们可以使用开发者工具做什么。以下是文本框的代码:

    <input disabled type="checkbox"> 
    

    注意这创建了一种称为复选框的输入类型。此外,注意这个文本框已被禁用,无法通过disabled属性使用。

  • 也许 HTML 安全性的一个挑战是 HTML 驻留在用户的电脑上。因此,用户可能能够更改他们电脑上的本地文件。

  • 拥有通过开发者工具访问自己电脑上 HTML 的用户可以更改 HTML。

    <input type="checkbox"> 
    

    注意这里 HTML 的本地副本已移除disabled属性。

  • 你永远不应该只信任客户端验证。

  • 类似地,考虑以下 HTML:

    <input required type="text"> 
    

    注意这个文本输入是required

  • 可以访问开发者工具的人可以取消此输入的要求,如下所示:

    <input type="text"> 
    

    注意,已移除required属性。

  • 再次强调,永远不要相信客户端验证将确保你的 Web 应用程序的安全性。

服务器端验证

  • 服务器端验证提供安全功能,以确保用户输入是适当和安全的。

  • 虽然这个主题超出了本课程的范围,但只需相信一个原则:用户输入应该在服务器端进行验证。永远不要信任用户输入。

跨站请求伪造 (CSRF)

  • 另一个威胁被称为跨站请求伪造CSRF

  • 网站使用两种主要方法与用户交互,称为GETPOST方法。

  • GET从服务器获取信息。

  • 你可能会考虑亚马逊如何使用GET方法来处理以下 HTML:

    <a href="https://www.amazon.com/dp/B07XLQ2FSK">Buy Now</a> 
    

    注意单击一下就可以购买这个产品。

  • 你可以想象如何欺骗某人购买他们不打算购买的东西。

  • 一个人可以提供一个自动尝试购买产品的图片:

    <img src="https://www.amazon.com/dp/B07XLQ2FSK"> 
    

    注意这里没有提供图片。相反,浏览器将尝试使用这个网页执行GET方法,可能进行未经授权或不需要的购买。

  • 同样,对手可以使用POST方法进行未经授权的购买。

  • 考虑以下“立即购买”按钮的 HTML 代码:

    <form action="https://www.amazon.com/" method="post">
    <input name="dp" type="hidden" value="B07XLQ2FSK">
    <button type="submit">Buy Now</button>
    </form> 
    

    注意一个网页表单,如上所述实现,可能会让人天真地认为自己是安全的,免受未经授权的购买。因为这个表单包含一个亚马逊用于验证的hidden值,从理论上讲,它可能会让程序员认为用户是安全的。

  • 然而,正如许多漏洞的情况一样,这种安全感是错误的。

  • 事实上,只需添加几行代码就可以颠覆上述情况。想象一下,一个对手在自己的网站上(不是亚马逊的)有以下代码:

    <form action="https://www.amazon.com/" method="post">
    <input name="dp" type="hidden" value="B07XLQ2FSK">
    <button type="submit">Buy Now</button>
    </form>
    <script>
    document.forms[0].submit();
    </script> 
    

    注意一个对手网站上的几行代码可以定位到一个表单并自动提交它。

  • 这种欺骗用户在另一个网站上执行命令的能力是 CSRF(跨站请求伪造)的本质。

  • 保护此类攻击的一种方法是在每个用户处由服务器生成一个秘密值的CSRF 令牌。因此,服务器将验证用户提交的 CSRF 令牌是否与服务器期望的令牌匹配。

  • 这些令牌通常通过 HTML 头提交。

随意代码执行 (ACE)

  • 随意代码执行,或 ACE,是在软件中执行不属于预期代码的行为。

  • 其中一种威胁被称为 缓冲区溢出,其中软件被输入所淹没。这种输入溢出到内存的其他区域,导致程序故障。例如,软件可能期望输入短长度,但用户输入了大量长度的输入。

  • 另一个类似的威胁被称为 栈溢出,其中溢出可以用来插入和执行恶意代码。

  • 有时,这样的攻击可以用于 破解 或绕过注册或付费使用软件的需求。

  • 此外,这些攻击可以用于 逆向工程 来查看代码的功能。

开源软件

  • 绕过这种威胁的一种方法是通过使用和创建 开源软件。这种软件的代码可以轻松在线发布,任何人都可以查看。

  • 可以审计代码,确保安全威胁更少。

  • 这些软件仍然容易受到攻击。

封闭源代码软件

  • 封闭源代码软件 是开源软件的对立面。

  • 这种软件的代码对公众不可用,因此可能对对手的攻击更不脆弱。

  • 然而,在开源软件(数千双眼睛在寻找软件中的漏洞)和封闭源代码软件(代码对公众隐藏)之间有一个权衡。

应用商店

  • 应用商店 由谷歌和苹果等实体运营,他们监控提交的代码是否存在对抗意图。

  • 当你只安装授权软件时,你比安装任何开发者提供的软件(不使用应用商店)要安全得多。

  • 应用商店使用加密技术,只接受由授权开发者签名的软件或代码。反过来,应用商店使用数字签名来签名软件。因此,操作系统可以确保只安装了授权的、签名的软件。

软件包管理器

  • 软件包管理器 采用类似的签名机制,以确保从第三方下载的内容是可信的。然而,并不能保证完全安全。

  • 尽管如此,我们总是试图提高对手安装对抗性代码的门槛。

缺陷赏金

  • 缺陷赏金 是个人发现并报告软件漏洞的付费机会。

  • 这样的赏金可能会有效地影响潜在的对手,使他们选择为了发现漏洞而获得报酬,而不是作为攻击者部署它们。

识别漏洞

  • 开发者可以检查 常见漏洞和暴露CVE 编号数据库,以了解全球对手在做什么。

  • 此外,他们可能会检查 常见漏洞评分系统CVSS,以了解这种威胁的严重程度。

  • 此外,还有一个 漏洞预测评分系统EPSS,允许开发者看到全球漏洞的潜在风险,以便他们优先考虑他们的安全工作。

  • 已知的漏洞利用KEV 数据库是已知漏洞的列表。

总结

在本课中,你学习了如何确保软件的安全性。你学习了...

  • 对手如何利用钓鱼、代码注入、反射攻击、SQL 注入和存储攻击等攻击手段渗透软件;

  • 字符转义、HTML 头、预编译语句和服务器端验证如何帮助阻止对手的攻击;

  • 应用商店、包管理器和开发者签名如何帮助防止恶意代码的安装;

  • 网络安全领域的专家如何追踪漏洞利用。

次次见!

第四讲

原文:cs50.harvard.edu/cybersecurity/notes/4/

  • 保留隐私

  • 网页浏览历史

  • HTTP 头部信息

  • 指纹识别

  • 会话 Cookies

  • 跟踪 Cookie

  • 跟踪参数

  • 第三方 Cookies

  • 私密浏览

  • 超级 Cookies

  • DNS

  • 虚拟私人网络(VPN)

  • Tor

  • 权限

  • 总结

保留隐私

  • 这本是 CS50 的网络安全入门课程。

  • 今天,让我们考虑我们在不知情的情况下分享了哪些信息,以及我们如何限制这种分享。

网页浏览历史

  • 你的浏览历史记录既是功能也是对隐私的潜在威胁。

  • 你可能不希望有人访问你访问过的网站。

  • 你可以清除浏览器历史记录。然而,你可能会从所有服务中注销。

  • 服务器通常会有日志来跟踪用户活动。因此,即使你清除了浏览器历史记录,服务器仍然会记录你访问的内容。

  • 服务器日志可能如下所示:

    log_format combined '$remote_addr - $remote_user [$time_local] '
                      '"$request" $status $body_bytes_sent '
                      '"$http_referer" "$http_user_agent"'; 
    

    注意这包括你的 IP 地址、你的本地时间以及其他细节,这些都在计算机之间共享的数字信封中。

  • 我们如何控制我们可以分享的内容?

HTTP 头部信息

  • 正如我们讨论的,HTTP 头部信息是在你的计算机和服务器之间发送的关键值对。

  • 考虑以下可能通过以下 HTML 文件中的链接访问的 URL。

    <a href="https://example.com">cats</a> 
    

    这个 HTML 展示了一个名为 cats 的链接,将用户导向example.com

  • 当你访问一个网站时,浏览器默认会分享将你带到那里的链接。

  • 当你点击一个链接时,浏览器会与网站分享是什么网站将你带过去的。因此,以下头部信息是从浏览器发送到服务器的:

    Referer: https://www.google.com/search?q=cats 
    

    注意这个头部信息分享了你在搜索什么。

  • 如果能够抑制共享的内容不是很好吗?考虑以下:

    Referer: https://www.google.com/ 
    

    注意以下只共享来源:不是你正在进行的特定搜索。

  • 以下元标签可以添加到你的网站中,以限制只共享流量来源。

    <meta name="referrer" content="origin"> 
    

    注意content属性被设置为origin

  • 可以通过在你的网站中添加以下内容来进一步限制,以提供无引用信息。

    <meta name="referrer" content="none"> 
    

    注意content属性被设置为none

指纹识别

  • 每个浏览器比其他浏览器更多地或更少地展示你的身份和行为信息。

  • 无论你选择哪种浏览器,服务器都会记录你的活动。

  • 指纹识别是一种第三方可以根据可用的线索识别你的方式,即使你在尽可能限制浏览器分享你信息的情况下。

  • 其中一条信息是 User-Agent 请求头,它如下描述你的设备:

    Mozilla/5.0 (Linux; {Android Version}; {Build Tag etc.}) 
    AppleWebKit/{WebKit Rev} (KHTML, like Gecko)
    Chrome/{Chrome Rev} Mobile Safari/{WebKit Rev} 
    

    注意到你的浏览器、操作系统版本和设备被识别。

  • Web 服务器还可以定位你的 IP 地址并记录它。

  • Web 服务器还可以发现你的屏幕分辨率、已安装的扩展、已安装的字体和其他信息。

  • 当这些信息随着时间的推移而汇总时,它可以使你越来越容易被识别。

会话 Cookies

  • 记忆 cookies 就像一个虚拟的手章,用于跟踪你个人。

  • Session cookies 是服务器放在你的电脑上以识别你的信息。

  • 一个会话 cookie 可能如下所示:

    HTTP/3 200
    Set-Cookie: session=1234abcd 
    

    注意到这个 cookie 告诉服务器你的会话是 1234abcd

  • 每个用户的会话编号或字符序列都是唯一的。

  • 会话 cookies 通常在服务器确定的某个时间段后过期。

  • Tracking cookies 是设计用来跟踪你的。

  • 第三方使用此类 cookies 来跟踪你在网站上的行为。考虑以下情况:

    Set-Cookie: _ga=GA1.2.0123456789.0; max-age=63072000 
    

    注意到这个 Google Analytics cookie 持续两年,并通过向每个你访问的新网站展示自己来跟踪你的活动。

跟踪参数

  • 在 cookies 隐藏在浏览器“引擎盖下”的地方,tracking parameters 在你访问的链接中是可见的。

  • 考虑以下 URL:

    https://example.com/ad_engagement?click_id=YmVhODI1MmZmNGU4&campaign_id=23 
    

    注意到 click_id 的值 YmVhODI1MmZmNGU4 专门跟踪你。

  • 当 cookies 在后台被跟踪时,你可以看到你访问的链接(基于 URL)如何跟踪你。

  • 越来越多,浏览器倾向于清理跟踪参数。考虑以下 URL:

    https://example.com/ad_engagement?campaign_id=23 
    

    注意到这个链接 跟踪你响应的活动。click_id 的值不再存在。

第三方 Cookies

  • 另一种类型的 cookie 是 third-party cookie

  • 第三方(即其他服务器或公司)希望了解你如何在网站之间旅行。考虑以下 HTTP 请求:

    GET /ad.gif HTTP/3
    Host: example.com
    Referer: https://harvard.edu/ 
    

    注意到这个请求明确要求从 example.com 获取一个名为 ad.gif 的文件。

  • 自动地,服务器会响应以下头信息:

    HTTP/3 200
    Set-Cookie: id=1234abcd; max-age=31536000 
    

    Set-Cookie 响应头设置了一个名为 id 的 cookie,其持续时间为三年。

  • 如果你浏览了使用相同广告的另一个网站,example.com 现在知道你正在浏览 harvard.eduyale.edu。假设你后来发出了以下 HTTP 请求:

    GET /ad.gif HTTP/3
    Cookie: id=1234abcd
    Host: example.com
    Referer: https://yale.edu/ 
    

    注意到之前提到的第三方 cookie id=1234abcd 现在再次显示给 example.com,从而揭示你后来访问了 yale.edu

  • 第三方 cookies 可以用来跟踪我们并货币化关于我们的信息。

私人浏览

  • 一种帮助保护你活动的方法是 private browsing

  • 在一个私人浏览窗口或标签页中,过去的 cookies 会被消除。

  • 尽管如此,网络仍然按照网络的方式运行!在私人浏览窗口的生态系统中仍然可以形成新的 cookies。

  • 更重要的是,服务器仍然可以跟踪您在单个浏览会话中的活动。

  • 提供您互联网服务的人可以始终在您的 HTTP 标头中注入他们自己的 cookie,而您可能并不知道。

  • 您可能可以通过您的互联网提供商选择退出超级 cookie。

DNS

  • 域名系统DNS 是一种服务,通过该服务可以将网站名称,如 harvard.edu,解析为特定的 IP 地址。

  • 按惯例,DNS 的流量完全是未加密的。因此,您向全世界宣布了您试图访问的网站。

  • 您的互联网服务提供商和 DNS 服务知道您试图访问的确切位置。

  • 另一种称为 HTTPS over DNSDoH,以及 TLS over DNSDoT 的替代方案,是一种您可以通过它来加密您的 DNS 请求的服务。

虚拟私人网络 (VPN)

  • 请记住,VPNs 是一种连接互联网的方式,使得看起来您似乎是从不同的设备进行连接。

  • VPNs 建立了您自己的电脑 (A) 和一个受信任的服务器 (B) 之间的加密连接。然后服务器 B 将您的请求发送到互联网,因此看起来您的流量似乎来自 B 而不是 A

  • 如果您的电脑感染了恶意软件,VPN 不能保护您。

  • VPN 会使得您的流量看起来似乎来自 VPN 的 IP 地址而不是您自己的电脑。

Tor

  • Tor 是一种软件,它将您的流量重定向到 Tor 服务器的一个节点。

  • 流量将通过许多加密节点进行导向。

  • 按设计,软件不会记住您的大部分活动。

  • 利用此类服务提供了很高的可能性,几乎无法识别关于您的信息。

  • 然而,请注意,如果您是唯一在工作场所或学校本地网络中使用 Tor 的人,通过其他手段完全有可能识别出您是谁。

  • 没有任何技术能为您提供绝对的保护。

权限

  • 操作系统按照惯例,会请求在您的设备上使用某些权限。

  • 基于位置的服务 默认情况下会提供您的地理位置。最好注意,如果您向 Apple Maps 和 Google Maps 提供此类权限,它们非常清楚您在任何给定时间的位置。

总结

在本课程中,我们讨论了...

  • 许多课程,希望已经提高了您对提供给第三方信息的认识;

  • 计算机中、服务器中、软件中以及您整体隐私中的漏洞是如何产生的;

  • 您如何通过提高意识来减轻这些漏洞;以及

  • 您如何更好地管理您和您服务的他人的隐私。

这就是 CS50 的网络安全入门课程。

Python

第零讲

原文:cs50.harvard.edu/python/notes/0/

  • 使用 Python 创建代码

  • 函数

  • 错误

  • 提升你的第一个 Python 程序

    • 变量

    • 注释

    • 伪代码

  • 进一步改进你的第一个 Python 程序

  • 字符串和参数

    • 引号的小问题
  • 字符串格式化

  • 更多关于字符串

  • 整数或 int

  • 可读性优先

  • 浮点数基础

  • 更多关于浮点数

  • 定义

  • 返回值

  • 总结

使用 Python 创建代码

  • VS Code 是一个文本编辑器。除了编辑文本外,你还可以在终端中可视化浏览文件和运行基于文本的命令。

  • 在终端中,你可以执行code hello.py来开始编码。

  • 在上面的文本编辑器中,你可以输入print("hello, world")。这是一个著名的经典程序,几乎所有的程序员在学习过程中都会编写。

  • 在终端窗口中,你可以执行命令。为了运行此程序,你需要将光标移至屏幕底部,在终端窗口中点击。现在你可以在终端窗口中输入第二个命令。在美元符号旁边,输入python hello.py并按下键盘上的回车键。

  • 回想一下,计算机实际上只理解零和一。因此,当你运行python hello.py时,Python 将解释你在hello.py中创建的文本,并将其转换为计算机可以理解的零和一。

  • 运行python hello.py程序的结果是hello, world

  • 恭喜!你刚刚创建了你的第一个程序。

函数

  • 函数是计算机或计算机语言已经知道如何执行的动作或动词。

  • 在你的hello.py程序中,print函数知道如何将内容打印到终端窗口。

  • print函数接受参数。在这种情况下,"hello, world"print函数接受的参数。

错误

  • 错误是编码的自然部分。这些都是你需要解决的问题!不要气馁!这是成为优秀程序员过程的一部分。

  • 想象一下,在我们的hello.py程序中,我们不小心输入了print("hello, world",忘记了print函数所需的最后的)。如果你犯了这个错误,解释器将在终端窗口中输出错误信息!

  • 错误信息通常会告诉你你的错误,并提供如何修复它们的线索。然而,会有很多次解释器并不那么有帮助。

提升你的第一个 Python 程序

  • 我们可以个性化你的第一个 Python 程序。

  • 在我们的 hello.py 文本编辑器中,我们可以添加另一个函数。input 是一个接受提示作为参数的函数。我们可以编辑我们的代码如下:

    input("What's your name? ")
    print("hello, world") 
    
  • 然而,仅此修改并不能让程序输出用户输入的内容。为此,我们需要向您介绍变量

变量

  • 变量只是在你自己的程序中存储值的容器。

  • 在你的程序中,你可以通过编辑它来引入你自己的变量,如下所示:

    name = input("What's your name? ")
    print("hello, world") 
    

    注意到在 name = input("What's your name? ") 中间的这个等号 = 在编程中有一个特殊的作用。这个等号实际上是将右侧的内容赋值给左侧。因此,input("What's your name? ") 返回的值被赋值给 name

  • 如果你按照如下方式编辑你的代码,你会注意到一个意外的结果:

    name = input("What's your name? ")
    print("hello, name") 
    
  • 无论用户输入什么,程序都会在终端窗口返回 hello, name

  • 进一步编辑我们的代码,你可以输入

    name = input("What's your name? ")
    print("hello,")
    print(name) 
    
  • 在终端窗口的结果将是

    What's your name? David
    hello
    David 
    
  • 我们正在接近我们可能期望的结果!

  • 你可以在 Python 的 数据类型 文档中了解更多。

注释

  • 注释是程序员跟踪他们在程序中所做事情的一种方式,甚至可以向其他人传达他们对于一段代码的意图。简而言之,它们是你和将看到你代码的其他人的笔记!

  • 你可以在你的程序中添加注释,以便能够看到程序正在做什么。你可能编辑你的代码如下:

    # Ask the user for their name name = input("What's your name? ")
    print("hello,")
    print(name) 
    
  • 注释也可以作为你的待办事项列表。

模拟代码

  • 模拟代码是一种重要的注释类型,它成为了一种特殊类型的待办事项列表,尤其是在你不理解如何完成编码任务时。例如,在你的代码中,你可能编辑你的代码如下:

    # Ask the user for their name name = input("What's your name? ")
    
    # Print hello print("hello,")
    
    # Print the name inputted print(name) 
    

进一步改进您的第一个 Python 程序

  • 我们可以进一步编辑我们的代码如下:

    # Ask the user for their name name = input("What's your name? ")
    
    # Print hello and the inputted name print("hello, " + name) 
    
  • 结果表明,一些函数接受许多参数。

  • 我们可以通过如下编辑我们的代码来使用逗号 , 传递多个参数:

    # Ask the user for their name name = input("What's your name? ")
    
    # Print hello and the inputted name print("hello,", name) 
    

    如果我们在终端输入“David”,输出将是 hello, David。成功。

字符串和参数

  • 在 Python 中,字符串被称为 str,是一系列文本。

  • 在我们的代码中回滚一点,回到以下内容,结果出现在多行上有一个视觉上的副作用:

    # Ask the user for their name name = input("What's your name? ")
    print("hello,")
    print(name) 
    
  • 函数通过参数影响其行为。如果我们查看 print 函数的文档(print),你会注意到我们可以了解很多关于 print 函数所接受的参数。

  • 通过查看此文档,你会了解到 print 函数自动包含参数 end='\n'。这个 \n 表示当运行时 print 函数将自动创建换行。该函数接受一个名为 end 的参数,默认情况下是创建新行。

  • 然而,我们可以技术上为 end 参数提供一个参数,这样就不会创建新行!

  • 我们可以如下修改我们的代码:

    # Ask the user for their name name = input("What's your name? ")
    print("hello,", end="")
    print(name) 
    

    通过提供 end="",我们正在覆盖 end 的默认值,这样它就不会在第一个打印语句之后创建新行。提供名字“David”,终端窗口中的输出将是 hello, David

  • 参数因此是函数可以接受的参数。

  • 你可以在 Python 的文档中了解更多关于 print 的信息这里

关于引号的一个小问题

  • 注意将引号作为字符串的一部分添加进来是有挑战性的。

  • print("hello,"friend"") 将不会工作,解释器会抛出错误。

  • 通常,有两种方法可以解决这个问题。首先,你可以简单地更改引号为单引号。

  • 另一种更常用的方法是编写 print("hello, \"friend\"")。反斜杠告诉解释器,接下来的字符应该被视为字符串中的引号,并避免解释器错误。

格式化字符串

  • 使用字符串最优雅的方式可能是以下这样:

    # Ask the user for their name name = input("What's your name? ")
    print(f"hello, {name}") 
    

    注意 print(f"hello, {name}") 中的 f。这个 f 是 Python 特殊处理字符串的指示符,与我们在这次讲座中展示的先前方法不同。预期你将在本课程中频繁使用这种字符串风格。

更多关于字符串的内容

  • 你永远不应该期望你的用户会按预期合作。因此,你需要确保用户的输入被纠正或检查。

  • 结果表明,字符串中内置了移除字符串中空白字符的能力。

  • 通过在 name 上使用 strip 方法(例如,name = name.strip()),你将移除用户输入的左右两侧的所有空白字符。你可以修改你的代码如下:

    # Ask the user for their name name = input("What's your name? ")
    
    # Remove whitespace from the str name = name.strip()
    
    # Print the output print(f"hello, {name}") 
    

    重新运行这个程序,无论你在名字前后输入多少空格,它都会移除所有空白字符。

  • 使用 title 方法,它会将用户的名字转换为大写首字母:

    # Ask the user for their name name = input("What's your name? ")
    
    # Remove whitespace from the str name = name.strip()
    
    # Capitalize the first letter of each word name = name.title()
    
    # Print the output print(f"hello, {name}") 
    
  • 到目前为止,你可能已经厌倦了在终端窗口中反复输入 python。你可以使用键盘上的上箭头键来回忆你最近输入的终端命令。

  • 注意你可以修改你的代码使其更高效:

    # Ask the user for their name name = input("What's your name? ")
    
    # Remove whitespace from the str and capitalize the first letter of each word name = name.strip().title()
    
    # Print the output print(f"hello, {name}") 
    
  • 我们甚至可以更进一步!

    # Ask the user for their name, remove whitespace from the str and capitalize the first letter of each word name = input("What's your name? ").strip().title()
    
    # Print the output print(f"hello, {name}") 
    
  • 你可以在 Python 的文档中了解更多关于字符串的信息这里

整数或 int

  • 在 Python 中,整数被称为 int

  • 在数学的世界里,我们熟悉 +, -, *, / 和 % 运算符。最后一个运算符 % 或取模运算符可能对你来说并不那么熟悉。

  • 你不必使用文本编辑器窗口来运行 Python 代码。在你的终端中,你可以单独运行 python。你将在终端窗口中看到 >>>。然后你可以运行实时、交互式代码。你可以输入 1+1,然后它会运行这个计算。这种模式在本课程中不太常用。

  • 再次打开 VS Code,我们可以在终端中键入code calculator.py。这将创建一个新文件,我们将在这个文件中创建自己的计算器。

  • 首先,我们可以声明一些变量。

    x = 1
    y = 2
    
    z = x + y
    
    print(z) 
    

    自然地,当我们运行python calculator.py时,我们在终端窗口中得到的答案是3。我们可以使用input函数使其更具交互性。

    x = input("What's x? ")
    y = input("What's y? ")
    
    z = x + y
    
    print(z) 
    
  • 运行这个程序,我们发现输出是不正确的,为12。这可能是为什么?

  • 在此之前,我们已经看到了如何使用+符号连接两个字符串。因为你的计算机键盘输入在解释器中作为文本传入,所以它被视为一个字符串。因此,我们需要将这个输入从字符串转换为整数。我们可以这样做:

    x = input("What's x? ")
    y = input("What's y? ")
    
    z = int(x) + int(y)
    
    print(z) 
    

    现在的结果是正确的。使用int(x)被称为“类型转换”,其中值从一种类型的变量(在这种情况下,是一个字符串)临时转换为另一种类型(在这里,是一个整数)。

  • 我们可以进一步改进我们的程序如下:

    x = int(input("What's x? "))
    y = int(input("What's y? "))
    
    print(x + y) 
    

    这说明你可以对函数运行函数。内部函数首先运行,然后是外部函数。首先运行input函数,然后是int函数。

  • 你可以在 Python 的int函数文档中了解更多信息,链接为int

可读性为王

  • 在决定你的编码任务的方法时,请记住,对于同一个问题,人们可以为许多方法做出合理的论证。

  • 无论你采取什么方法来处理编程任务,请记住,你的代码必须是可读的。你应该使用注释来给自己和他人提供关于你的代码正在做什么的线索。此外,你应该以可读的方式编写代码。

浮点数基础

  • 浮点数是一个包含小数点的实数,例如0.52

  • 你可以修改你的代码以支持浮点数如下:

    x = float(input("What's x? "))
    y = float(input("What's y? "))
    
    print(x + y) 
    

    这个更改允许用户输入1.23.4以显示总数4.6

  • 然而,让我们假设你想要将总数四舍五入到最接近的整数。查看 Python 文档中round函数,你会看到可用的参数是round(number[, ndigits])。那些方括号表示程序员可以指定某些可选内容。因此,你可以这样做round(n)来将数字四舍五入到最近的整数。或者,你可以按照以下方式编写代码:

    # Get the user's input x = float(input("What's x? "))
    y = float(input("What's y? "))
    
    # Create a rounded result z = round(x + y)
    
    # Print the result print(z) 
    

    输出将被四舍五入到最接近的整数。

  • 如果我们想要格式化长数字的输出呢?例如,而不是看到1000,你可能希望看到1,000。你可以按照以下方式修改你的代码:

    # Get the user's input x = float(input("What's x? "))
    y = float(input("What's y? "))
    
    # Create a rounded result z = round(x + y)
    
    # Print the formatted result print(f"{z:,}") 
    

    虽然相当晦涩,但print(f"{z:,}")创建了一个场景,其中输出的z将在可能看起来像1,0002,500的地方包含逗号。

更多关于浮点数的内容

  • 我们如何四舍五入浮点数?首先,修改你的代码如下:

    # Get the user's input x = float(input("What's x? "))
    y = float(input("What's y? "))
    
    # Calculate the result z = x / y
    
    # Print the result print(z) 
    

    当输入2作为 x 和3作为 y 时,结果 z 是0.6666666666,看起来像我们预期的那样无限期地继续下去。

  • 让我们假设我们想要向下取整。我们可以修改我们的代码如下:

    # Get the user's input x = float(input("What's x? "))
    y = float(input("What's y? "))
    
    # Calculate the result and round z = round(x / y, 2)
    
    # Print the result print(z) 
    

    如我们所预期的那样,这将把结果四舍五入到最接近的两个小数位。

  • 我们也可以使用 f-string 格式化输出,如下所示:

    # Get the user's input x = float(input("What's x? "))
    y = float(input("What's y? "))
    
    # Calculate the result z = x / y
    
    # Print the result print(f"{z:.2f}") 
    

    这个神秘的 f-string 代码显示的结果与我们的先前的四舍五入策略相同。

  • 你可以在 Python 的 float 函数文档中了解更多信息:float

Def

  • 不是很想创建我们自己的函数吗?

  • 让我们在终端窗口中通过输入 code hello.py 来恢复 hello.py 的最终代码。你的起始代码应该如下所示:

    # Ask the user for their name, remove whitespace from the str and capitalize the first letter of each word name = input("What's your name? ").strip().title()
    
    # Print the output print(f"hello, {name}") 
    

    我们可以改进我们的代码,创建一个为我们说“hello”的专用函数!

  • 在我们的文本编辑器中删除所有代码,让我们从头开始:

    name = input("What's your name? ")
    hello()
    print(name) 
    

    尝试运行此代码,你的解释器将抛出一个错误。毕竟,没有为 hello 定义函数。

  • 我们可以创建一个名为 hello 的函数,如下所示:

    def hello():
        print("hello")
    
    name = input("What's your name? ")
    hello()
    print(name) 
    

    注意,def hello() 下的所有内容都需要缩进。Python 是一种缩进语言。它使用缩进来理解哪些内容属于上述函数的一部分。因此,hello 函数中的所有内容都必须缩进。如果某项内容没有缩进,它会被视为不在 hello 函数内部。在终端窗口中运行 python hello.py,你会看到你的输出并不完全如你所愿。

  • 我们可以进一步改进我们的代码:

    # Create our own function def hello(to):
        print("hello,", to)
    
    # Output using our own function name = input("What's your name? ")
    hello(name) 
    

    在这里,在第一行,你正在创建你的 hello 函数。然而,这次你正在告诉解释器这个函数接受一个参数:一个名为 to 的变量。因此,当你调用 hello(name) 时,计算机将 name 作为 to 传递给 hello 函数。这就是我们将值传递给函数的方式。非常有用!在终端窗口中运行 python hello.py,你会看到输出更接近我们在这堂课中早些时候提出的理想输出。

  • 我们可以修改我们的代码,给 hello 添加一个默认值:

    # Create our own function def hello(to="world"):
        print("hello,", to)
    
    # Output using our own function name = input("What's your name? ")
    hello(name)
    
    # Output without passing the expected arguments hello() 
    

    自己测试一下你的代码。注意第一个 hello 的行为可能正如你所预期,而第二个 hello,由于没有传递值,将默认输出 hello, world

  • 我们不必在程序的开头就有我们的函数。我们可以将其向下移动,但我们需要告诉解释器我们有一个 main 函数和一个单独的 hello 函数。

    def main():
    
        # Output using our own function
        name = input("What's your name? ")
        hello(name)
    
        # Output without passing the expected arguments
        hello()
    
    # Create our own function def hello(to="world"):
        print("hello,", to) 
    

    然而,这单独的步骤将创建某种错误。如果我们运行 python hello.py,什么都不会发生!原因在于,这段代码中没有任何内容实际调用 main 函数并使我们的程序活跃起来。

  • 以下非常小的修改将调用 main 函数并使我们的程序恢复正常:

    def main():
    
        # Output using our own function
        name = input("What's your name? ")
        hello(name)
    
        # Output without passing the expected arguments
        hello()
    
    # Create our own function def hello(to="world"):
        print("hello,", to)
    
    main() 
    

返回值

  • 你可以想象许多场景,在这些场景中,你不仅希望函数执行一个动作,还希望它将值返回到主函数。例如,与其仅仅打印 x + y 的计算结果,你可能会希望函数将这个计算的结果返回到程序的另一部分。我们称这种“返回”值为 return 值。

  • 通过输入 code calculator.py 返回到我们的 calculator.py 代码。删除那里的所有代码。按照以下方式重新编写代码:

    def main():
        x = int(input("What's x? "))
        print("x squared is", square(x))
    
    def square(n):
        return n * n
    
    main() 
    

    实际上,x 被传递给 square。然后,x * x 的计算结果被返回到主函数。

总结

通过本节课的工作,你学到了将在自己的程序中无数次使用的技能。你学习了关于…

  • 在 Python 中创建你的第一个程序;

  • 函数;

  • 错误;

  • 变量;

  • 注释;

  • 模拟代码;

  • 字符串;

  • 参数;

  • 格式化字符串;

  • 整数;

  • 可读性原则;

  • 浮点数;

  • 创建你自己的函数;以及

  • 返回值。

第一讲

原文:cs50.harvard.edu/python/notes/1/

  • 条件语句

  • if 语句

  • 控制流、elif 和 else

  • 取模

  • 创建我们自己的奇偶函数

  • Pythonic

  • match

  • 总结

条件语句

  • 条件语句允许你,作为程序员,让你的程序做出决定:就像你的程序根据某些条件在左边的路或右边的路之间做出选择。

  • 条件语句允许你的程序做出决定,根据指定的条件选择一条路径而不是另一条路径。

  • Python 内置了一套“运算符”,用于提出数学问题。

  • >< 符号你可能很熟悉。

  • >= 表示“大于或等于”。

  • <= 表示“小于或等于”。

  • == 表示“等于”。注意双等号:单个等号用于赋值,而两个等号用于比较值。

  • != 表示“不等于”。

  • 条件语句比较左边的项与右边的项。

if 语句

  • 在你的终端窗口中,键入 code compare.py。这将创建一个名为“compare”的新文件。

  • 在文本编辑器窗口中,开始如下:

    x = int(input("What's x? "))
    y = int(input("What's y? "))
    
    if x < y:
        print("x is less than y") 
    

    注意你的程序如何接受用户对 x 和 y 的输入,将它们作为整数转换并保存到各自的 x 和 y 变量中。然后,if 语句比较 x 和 y。如果满足 x < y 的条件,则执行 print 语句。

  • if 语句使用 bool(布尔)值(TrueFalse)来决定是否执行代码。如果比较 x > y 的结果是 True,解释器将运行缩进的代码块。

控制流、elif 和 else

  • 进一步修改你的代码如下:

    x = int(input("What's x? "))
    y = int(input("What's y? "))
    
    if x < y:
        print("x is less than y")
    if x > y:
        print("x is greater than y")
    if x == y:
        print("x is equal to y") 
    

    注意你提供了一系列 if 语句。首先,评估第一个 if 语句。然后,执行第二个 if 语句的评估。最后,执行最后一个 if 语句的评估。这种决策流程称为“控制流”。

  • 我们的代码可以表示如下:

    flowchart TD
      A([start]) --> B{x < y}
      B -- True --> C["#quot;x is less than y#quot;"]
      C --> D{x > y}
      D -- True --> E["#quot;x is greater than y#quot;"]
      E --> F{x == y}
      F -- True --> G["#quot;x is equal to y#quot;"]
      G --> H([stop])
      B -- False --> D
      D -- False --> F
      F -- False --> H 
    
  • 这个程序可以通过不连续问三个问题来改进。毕竟,不是所有三个问题都能得到 true 的结果!按照以下方式修改你的程序:

    x = int(input("What's x? "))
    y = int(input("What's y? "))
    
    if x < y:
        print("x is less than y")
    elif x > y:
        print("x is greater than y")
    elif x == y:
        print("x is equal to y") 
    

    注意 elif 的使用如何使程序做出更少的决策。首先,评估 if 语句。如果这个语句被评估为真,则不会运行所有的 elif 语句。然而,如果 if 语句被评估并发现为假,则第一个 elif 将被评估。如果是真的,它将不会运行最终的评估。

  • 我们的代码可以表示如下:

    flowchart TD
      A([start]) --> B{x < y}
      B -- True --> C["#quot;x is less than y#quot;"]
      B -- False --> D{x > y}
      D -- True --> E["#quot;x is greater than y#quot;"]
      D -- False --> F{x == y}
      F -- True --> G["#quot;x is equal to y#quot;"]
      G --> H([stop])
      F -- False --> H
      C --> H
      E --> H 
    
  • 虽然你的电脑可能在速度上没有注意到我们的第一个程序和这个修订程序之间的差异,但考虑一下,一个每天运行数十亿或数万亿此类计算的在线服务器,这样的小代码决策肯定会有影响。

  • 我们可以对我们的程序进行最后一次改进。注意 elif x == y 在逻辑上不是必须的评估。毕竟,如果逻辑上 x 不小于 y 且 x 不大于 y,那么 x 一定等于 y。因此,我们不需要运行 elif x == y。我们可以使用 else 语句创建一个“通配符”,默认结果。我们可以这样修改:

    x = int(input("What's x? "))
    y = int(input("What's y? "))
    
    if x < y:
        print("x is less than y")
    elif x > y:
        print("x is greater than y")
    else:
        print("x is equal to y") 
    

    注意到通过我们的修订,这个程序的相对复杂性已经降低。

  • 我们的代码可以表示如下:

    flowchart TD
      A([start]) --> B{x < y}
      B -- True --> C["#quot;x is less than y#quot;"]
      B -- False --> D{x > y}
      D -- True --> E["#quot;x is greater than y#quot;"]
      D -- False --> F["#quot;x is equal to y#quot;"]
      F --> G([stop])
      C --> G
      E --> G 
    

  • or 允许程序在一种或多种选择之间做出决定。例如,我们可以进一步编辑我们的程序如下:

    x = int(input("What's x? "))
    y = int(input("What's y? "))
    
    if x < y or x > y:
        print("x is not equal to y")
    else:
        print("x is equal to y") 
    

    注意到我们的程序结果相同,但复杂性降低。代码的效率提高了。

  • 到目前为止,我们的代码相当不错。然而,设计是否可以进一步改进?我们可以进一步编辑我们的代码如下:

    x = int(input("What's x? "))
    y = int(input("What's y? "))
    
    if x != y:
        print("x is not equal to y")
    else:
        print("x is equal to y") 
    

    注意到我们完全移除了 or,只是简单地问,“x 是否不等于 y?”我们只问一个问题。非常高效!

  • 为了说明,我们也可以将代码修改如下:

    x = int(input("What's x? "))
    y = int(input("What's y? "))
    
    if x == y:
        print("x is equal to y")
    else:
        print("x is not equal to y") 
    

    注意到 == 操作符用于判断左边的值和右边的值是否相等。使用双等号非常重要。如果你只使用一个等号,解释器可能会抛出一个错误。

  • 我们可以将代码表示如下:

    flowchart TD
      A([start]) --> B{x == y}
      B -- True --> C["#quot;x is equal to y#quot;"]
      B -- False --> E["#quot;x is not equal to y#quot;"]
      C --> F([stop])
      E --> F 
    

  • or 类似,and 也可以在条件语句中使用。

  • 在终端窗口中执行 code grade.py。启动你的新程序如下:

    score = int(input("Score: "))
    
    if score >= 90 and score <= 100:
        print("Grade: A")
    elif score >=80 and score < 90:
        print("Grade: B")
    elif score >=70 and score < 80:
        print("Grade: C")
    elif score >=60 and score < 70:
        print("Grade: D")
    else:
        print("Grade: F") 
    

    注意到通过执行 python grade.py,你将能够输入一个分数并得到一个等级。然而,请注意这里存在潜在的错误。

  • 通常,我们不想让用户输入正确信息。我们可以这样改进我们的代码:

     score = int(input("Score: "))
    
      if 90 <= score <= 100:
          print("Grade: A")
      elif 80 <= score < 90:
          print("Grade: B")
      elif 70 <= score < 80:
          print("Grade: C")
      elif 60 <= score < 70:
          print("Grade: D")
      else:
          print("Grade: F") 
    

    注意到 Python 允许你以其他编程语言中相当不常见的方式链接着操作符和条件。

  • 尽管如此,我们还可以进一步改进我们的程序:

    score = int(input("Score: "))
    
    if score >= 90:
        print("Grade: A")
    elif score >= 80:
        print("Grade: B")
    elif score >= 70:
        print("Grade: C")
    elif score >= 60:
        print("Grade: D")
    else:
        print("Grade: F") 
    

    注意到通过减少问题数量,程序得到了改进。这使得我们的程序更容易阅读,并且在未来的维护中更加高效。

  • 你可以在 Python 的文档中了解更多关于控制流的信息。

取模

  • 在数学中,奇偶性指的是一个数是偶数还是奇数。

  • 编程中的取模 % 操作符允许你查看两个数是否能够整除,或者除后是否有余数。

  • 例如,4 % 2 的结果将是零,因为它可以整除。然而,3 % 2 不能整除,结果将是一个非零的数字!

  • 在终端窗口中,通过输入 code parity.py 创建一个新的程序。在文本编辑器窗口中,输入以下代码:

    x = int(input("What's x? "))
    
    if x % 2 == 0:
        print("Even")
    else:
        print("Odd") 
    

    注意我们的用户可以输入任何大于等于 1 的数字来查看它是否为偶数或奇数。

创建我们自己的偶奇函数

  • 如在第六讲 中讨论的,你会发现创建自己的函数很有用!

  • 我们可以创建自己的函数来检查一个数字是否为偶数或奇数。按照以下方式调整你的代码:

    def main():
        x = int(input("What's x? "))
        if is_even(x):
            print("Even")
        else:
            print("Odd")
    
    def is_even(n):
        if n % 2 == 0:
            return True
        else:
            return False
    
    main() 
    

    注意到我们的if语句is_even(x)即使没有操作符也能正常工作。这是因为我们的函数返回一个bool(布尔值),TrueFalse,并将其返回给主函数。if语句只是简单地评估xis_even是否为真或假。

Pythonic

  • 在编程世界中,有一些编程类型被称为“Pythonic”的编程。也就是说,有一些编程方式只在 Python 编程中看到。考虑以下程序的修订版:

    def main():
        x = int(input("What's x? "))
        if is_even(x):
            print("Even")
        else:
            print("Odd")
    
    def is_even(n):
        return True if n % 2 == 0 else False
    
    main() 
    

    注意到我们代码中的这个返回语句几乎就像一个英文句子。这是仅在 Python 中才能看到的独特编码方式。

  • 我们可以进一步修改代码,使其更加易读:

    def main():
        x = int(input("What's x? "))
        if is_even(x):
            print("Even")
        else:
            print("Odd")
    
    def is_even(n):
        return n % 2 == 0
    
    main() 
    

    注意程序将评估n % 2 == 0的结果,将其视为TrueFalse,并将其简单地返回给主函数。

match

  • ifelifelse语句类似,match语句可以用来有条件地运行与某些值匹配的代码。

  • 考虑以下程序:

     name = input("What's your name? ")
    
      if name == "Harry":
          print("Gryffindor")
      elif name == "Hermione":
          print("Gryffindor")
      elif name == "Ron": 
          print("Gryffindor")
      elif name == "Draco":
          print("Slytherin")
      else:
          print("Who?") 
    

    注意前三个条件语句打印了相同的响应。

  • 我们可以使用or关键字稍微改进这段代码:

     name = input("What's your name? ")
    
      if name == "Harry" or name == "Hermione" or name == "Ron": 
          print("Gryffindor")
      elif name == "Draco":
          print("Slytherin")
      else:
          print("Who?") 
    

    注意elif语句的数量减少了,这提高了代码的可读性。

  • 或者,我们可以使用match语句将名称映射到房屋。考虑以下代码:

     name = input("What's your name? ")
    
      match name: 
          case "Harry":
              print("Gryffindor")
          case "Hermione":
              print("Gryffindor")
          case "Ron": 
              print("Gryffindor")
          case "Draco":
              print("Slytherin")
          case  _:
              print("Who?") 
    

    注意到最后一个情况中使用了_符号。这将与任何输入匹配,产生类似于else语句的行为。

  • 匹配语句将match关键字后面的值与case关键字后面的每个值进行比较。如果在事件中找到匹配项,则执行相应的缩进代码部分,程序停止匹配。

  • 我们可以改进代码:

     name = input("What's your name? ")
    
      match name: 
          case "Harry" | "Hermione" | "Ron":
              print("Gryffindor")
          case "Draco":
              print("Slytherin")
          case  _:
              print("Who?") 
    

    注意,使用了单个竖线|。与or关键字类似,这允许我们在同一个case语句中检查多个值。

总结

现在,你可以在 Python 中使用条件语句来提问,并让程序相应地采取行动。在本讲座中,我们讨论了…

  • 条件语句;

  • if语句;

  • 控制流程,elifelse

  • or

  • and

  • 取模;

  • 创建你自己的函数;

  • Pythonic 编码;

  • match

第二讲

原文:cs50.harvard.edu/python/notes/2/

  • 循环

  • while 循环

  • for 循环

  • 通过用户输入改进

  • 更多关于列表的信息

  • 长度

  • 字典

  • 马里奥

  • 总结

循环

  • 实际上,循环是一种重复做某事的方式。

  • 在终端窗口中键入 code cat.py 开始。

  • 在文本编辑器中,从以下代码开始:

    print("meow")
    print("meow")
    print("meow") 
    

    通过在终端窗口中键入 python cat.py 来运行此代码,你会注意到程序喵了三次。

  • 在成为一名程序员的过程中,你想要考虑如何改进那些你反复输入相同内容的代码区域。想象一下,你可能在某个地方想要“喵”500 次。反复输入相同的表达式 print("meow") 是否合理?

  • 循环使你能够创建一个反复执行的代码块。

while 循环

  • while 循环在所有编程语言中几乎是通用的。

  • 这样的循环会反复执行代码块。

  • 在文本编辑器窗口中,按照以下方式编辑你的代码:

    i = 3
    while i != 0:
        print("meow") 
    

    注意,尽管这段代码会多次执行 print("meow"),但它永远不会停止!它会无限循环。while 循环通过反复询问循环的条件是否得到满足来工作。在这种情况下,解释器会问,“i 是否不等于零?”当你陷入一个永远执行的循环时,你可以按键盘上的 Ctrl+C 来退出循环。

  • 为了修复这个永远持续下去的循环,我们可以按照以下方式编辑我们的代码:

    i = 3
    while i != 0:
      print("meow")
      i = i - 1 
    

    注意,现在我们的代码执行得很好,每次“迭代”循环时都会将 i 减少 1。术语迭代在编码中具有特殊意义。通过迭代,我们指的是通过循环的一个周期。第一次迭代是通过循环的“0 次”迭代。第二次是“1 次”迭代。在编程中,我们从 0 开始计数,然后是 1,然后是 2。

  • 我们可以进一步改进我们的代码如下:

     i = 1
      while i <= 3:
          print("meow")
          i = i + 1 
    

    注意,当我们编写 i = i + 1 时,我们是从右向左赋值 i 的值。上面,我们像大多数人类一样从 1 开始计数(1,2,3)。如果你执行上面的代码,你会看到它喵了三次。在编程中,最好的做法是从 0 开始计数。

  • 我们可以将我们的代码改进为从 0 开始计数:

    i = 0
    while i < 3:
        print("meow")
        i += 1 
    

    注意,将运算符更改为 i < 3 允许我们的代码按预期工作。我们首先从 0 开始计数,然后它通过我们的循环迭代三次,产生三次喵声。同时,注意 i += 1i = i + 1 是相同的。

  • 到目前为止,我们的代码如下所示:

    flowchart TD
      A([start]) --> B[i = 0]
      B --> C{i < 3}
      C -- True --> D["#quot;meow#quot;"]
      D --> E[i += 1]
      E --> C
      C -- False --> F([stop]) 
    

    注意,我们的循环将 i 计数到 3,但不包括 3。

For 循环

  • for 循环是另一种类型的循环。

  • 要最好地理解 for 循环,最好先从 Python 中一种新的变量类型 list(列表)开始讲起。就像我们生活中的其他领域一样,我们可以有一个购物清单,一个待办事项清单等。

  • for 循环遍历一个 list 中的项目。例如,在文本编辑器窗口中,将你的 cat.py 代码修改如下:

    for i in [0, 1, 2]:
        print("meow") 
    

    注意到与之前的 while 循环代码相比,这段代码多么简洁。在这段代码中,i0 开始,喵喵叫,然后 i 被赋值为 1,喵喵叫,最后 i 被赋值为 2,喵喵叫,然后结束。

  • 虽然这段代码实现了我们的目标,但还有一些改进代码以应对极端情况的可能性。乍一看,我们的代码看起来很棒。但是,如果你想要迭代到一百万呢?最好创建能够处理这种极端情况的代码。相应地,我们可以这样改进我们的代码:

    for i in range(3):
        print("meow") 
    

    注意到 range(3) 自动返回三个值(012)。这段代码将执行并产生预期的效果,喵喵叫三次。

  • 我们的代码可以进一步改进。注意到我们在代码中从未显式地使用 i。也就是说,虽然 Python 需要使用 i 作为存储循环迭代次数的位置,但我们从未用它做任何其他用途。在 Python 中,如果这样的变量在我们的代码中没有其他意义,我们只需简单地用单个下划线 _ 来表示这个变量。因此,你可以这样修改你的代码:

    for _ in range(3):
        print("meow") 
    

    注意到将 i 改为 _ 对我们程序的运行没有影响。

  • 我们的代码可以进一步改进。为了拓展你的思维,考虑以下代码:

    print("meow" * 3) 
    

    注意到它将喵喵叫三次,但程序将输出 meowmeowmeow 作为结果。考虑一下:你如何在每次喵喵叫的末尾创建一个换行符?

  • 的确,你可以这样编辑你的代码:

    print("meow\n" * 3, end="") 
    

    注意到这段代码在每行产生三个喵喵叫,通过添加 end=""\n,我们告诉解释器在每次喵喵叫的末尾添加一个换行符。

通过用户输入改进

  • 也许我们想要从用户那里获取输入。我们可以使用循环作为验证用户输入的一种方式。

  • 在 Python 中,使用 while 循环来验证用户输入是一种常见的范式。

  • 例如,让我们尝试提示用户输入一个大于或等于 0 的数字:

    while True:
        n = int(input("What's n? "))
        if n < 0:
            continue
        else:
            break 
    
  • 注意到我们在 Python 中引入了两个新的关键字,continuebreakcontinue 明确告诉 Python 跳到循环的下一个迭代。另一方面,break 告诉 Python 在完成所有迭代之前“提前退出”循环。在这种情况下,当 n 小于 0 时,我们将 continue 到循环的下一个迭代——最终用“请输入 n 的值?”重新提示用户。如果 n 大于或等于 0,我们将 break 出循环,允许程序的其余部分继续运行。

  • 结果表明,在这种情况下 continue 关键字是多余的。我们可以这样改进我们的代码:

    while True:
        n = int(input("What's n? "))
        if n > 0:
            break
    
    for _ in range(n):
        print("meow") 
    

    注意到这个 while 循环将一直运行(永远)直到 n 大于 0。当 n 大于 0 时,循环会中断。

  • 结合我们之前的学习,我们可以使用函数进一步改进我们的代码:

    def main():
        meow(get_number())
    
    def get_number():
        while True:
            n = int(input("What's n? "))
            if n > 0:
                return n
    
    def meow(n):
        for _ in range(n):
            print("meow")
    
    main() 
    

    注意,我们不仅将你的代码修改为在多个函数中操作,而且还使用了 return 语句将 n 的值返回到 main 函数。

更多关于列表的信息

  • 考虑著名的哈利·波特宇宙中的霍格沃茨世界。

  • 在终端中,输入 code hogwarts.py

  • 在文本编辑器中,编写如下代码:

    students = ["Hermione", "Harry", "Ron"]
    
    print(students[0])
    print(students[1])
    print(students[2]) 
    

    注意我们如何有一个包含学生姓名的 list,如上所示。然后我们打印位于第 0 位置的学生,“赫敏”。其他每个学生也会被打印出来。

  • 正如我们之前所展示的,我们可以使用循环来遍历列表。你可以改进你的代码如下:

    students = ["Hermione", "Harry", "Ron"]
    
    for student in students:
        print(student) 
    

    注意,对于 students 列表中的每个 student,它将按预期打印学生。你可能想知道为什么我们没有使用之前讨论的 _ 标识符。我们选择不这样做是因为 student 在我们的代码中被明确使用。

  • 你可以在 Python 的列表文档中了解更多信息。

长度

  • 我们可以利用 len 来检查名为 studentslist 的长度。

  • 假设你不仅想打印学生的名字,还想打印他们在列表中的位置。为了实现这一点,你可以修改你的代码如下:

    students = ["Hermione", "Harry", "Ron"]
    
    for i in range(len(students)):
        print(i + 1, students[i]) 
    

    注意,执行此代码不仅会得到每个学生的位置加一(使用 i + 1),还会打印出每个学生的名字。len 允许你动态地看到学生列表的长度,无论它增长多少。

  • 你可以在 Python 的内置函数 len文档中了解更多信息。

字典

  • dicts 或字典一种数据结构,允许你将键与值关联。

  • 其中,list 是多个值的列表,而 dict 将键与值关联起来。

  • 考虑到霍格沃茨的学院,我们可能将特定的学生分配到特定的学院。

    哈利·波特的名字

  • 我们可以使用 lists 单独完成这项任务:

    students = ["Hermione", "Harry", "Ron", "Draco"]
    houses = ["Gryffindor", "Gryffindor", "Griffindor", "Slytherin"] 
    

    注意,我们可以保证我们始终将这些列表保持有序。students 列表中的第一个位置与 houses 列表中的第一个位置对应的学院相关联,依此类推。然而,当我们的列表增长时,这可能会变得相当繁琐!

  • 我们可以使用 dict 来改进我们的代码如下:

    students = {
        "Hermione": "Gryffindor",
        "Harry": "Gryffindor",
        "Ron": "Gryffindor",
        "Draco": "Slytherin",
    }
    print(students["Hermione"])
    print(students["Harry"])
    print(students["Ron"])
    print(students["Draco"]) 
    

    注意我们如何使用 {} 花括号来创建字典。lists 使用数字来遍历列表,而 dicts 允许我们使用单词。

  • 运行你的代码并确保你的输出如下:

    $ python hogwarts.py
    Gryffindor
    Gryffindor
    Gryffindor
    Slytherin 
    
  • 我们可以改进我们的代码如下:

    students = {
        "Hermione": "Gryffindor",
        "Harry": "Gryffindor",
        "Ron": "Gryffindor",
        "Draco": "Slytherin",
    }
    for student in students:
        print(student) 
    

    注意,执行此代码时,for 循环将只遍历所有键,结果是一个包含学生名字的列表。我们如何打印出键和值?

  • 修改你的代码如下:

    students = {
        "Hermione": "Gryffindor",
        "Harry": "Gryffindor",
        "Ron": "Gryffindor",
        "Draco": "Slytherin",
    }
    for student in students:
        print(student, students[student]) 
    

    注意 students[student] 将遍历每个学生的键并找到他们学院的值。执行你的代码,你会注意到输出有点混乱。

  • 我们可以通过改进我们的代码来清理print函数:

    students = {
        "Hermione": "Gryffindor",
        "Harry": "Gryffindor",
        "Ron": "Gryffindor",
        "Draco": "Slytherin",
    }
    for student in students:
        print(student, students[student], sep=", ") 
    

    注意这如何在打印的每个项目之间创建一个干净的逗号分隔。

  • 如果你执行python hogwarts.py,你应该看到以下内容:

    $ python hogwarts.py
    Hermione, Gryffindor
    Harry, Gryffindor
    Ron, Gryffindor
    Draco, Slytherin 
    
  • 如果我们关于学生的信息更多,我们如何将更多数据与每个学生关联起来?

    哈利波特名字

  • 你可以想象想要与多个键关联大量数据。增强你的代码如下:

    students = [
        {"name": "Hermione", "house": "Gryffindor", "patronus": "Otter"},
        {"name": "Harry", "house": "Gryffindor", "patronus": "Stag"},
        {"name": "Ron", "house": "Gryffindor", "patronus": "Jack Russell terrier"},
        {"name": "Draco", "house": "Slytherin", "patronus": None},
    ] 
    

    注意这段代码创建了一个dictlist。名为students的列表中有四个dict:每个学生一个。注意,Python 有一个特殊的None标识符,表示与键没有关联的值。

  • 现在,你可以访问关于这些学生的大量有趣数据。现在,进一步修改你的代码如下:

    students = [
        {"name": "Hermione", "house": "Gryffindor", "patronus": "Otter"},
        {"name": "Harry", "house": "Gryffindor", "patronus": "Stag"},
        {"name": "Ron", "house": "Gryffindor", "patronus": "Jack Russell terrier"},
        {"name": "Draco", "house": "Slytherin", "patronus": None},
    ]
    
    for student in students:
        print(student["name"], student["house"], student["patronus"], sep=", ") 
    

    注意for循环将遍历students列表中每个dict

  • 你可以在 Python 的dict文档中了解更多信息链接

马里奥

  • 记住经典游戏马里奥有一个英雄跳过砖块。让我们创建这个游戏的文本表示。

    马里奥方块

  • 开始编码如下:

    print("#")
    print("#")
    print("#") 
    

    注意我们是如何一次又一次地复制和粘贴相同的代码。

  • 考虑以下方式改进代码:

    for _ in range(3):
        print("#") 
    

    注意这基本上实现了我们想要创建的效果。

  • 考虑:我们能否进一步抽象化,以便以后用这段代码解决更复杂的问题?修改你的代码如下:

    def main():
        print_column(3)
    
    def print_column(height):
        for _ in range(height):
            print("#")
    
    main() 
    

    注意我们的列可以增长到我们想要的任何程度,而不需要任何硬编码。

  • 现在,让我们尝试水平打印一行。修改你的代码如下:

    def main():
        print_row(4)
    
    def print_row(width):
        print("?" * width)
    
    main() 
    

    注意我们现在有了可以创建从左到右的方块的代码。

  • 查看下面的幻灯片,注意马里奥既有行也有列的方块。

    马里奥地下城

  • 考虑:我们如何在代码中实现行和列?修改你的代码如下:

    def main():
        print_square(3)
    
    def print_square(size):
    
        # For each row in square
        for i in range(size):
    
            # For each brick in row
            for j in range(size):
    
                #  Print brick
                print("#", end="")
    
            # Print blank line
            print()
    
    main() 
    

    注意我们有一个外层循环,它处理正方形中的每一行。然后,我们有一个内层循环,在每一行打印一个砖块。最后,我们有一个print语句打印一个空行。

  • 我们可以进一步抽象我们的代码:

    def main():
        print_square(3)
    
    def print_square(size):
        for i in range(size):
            print_row(size)
    
    def print_row(width):
        print("#" * width)
    
    main() 
    

总结

你现在在你的 Python 能力增长列表中又获得了一种新能力。在本讲中,我们讨论了...

  • 循环

  • while

  • for

  • len

  • list

  • dict

posted @ 2025-11-08 11:25  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报