Week 6 Python
欢迎!
在前几周,我们向大家介绍了编程的基本构建块。
你学习了使用一种名为 C 的低级编程语言进行编程。
今天,我们将使用一种名为 Python 的高级编程语言。
当你学习这种新语言时,你会发现你能够更好地自学新的编程语言。
你好 Python!
几十年来,人们已经看到了如何改进以前编程语言中的设计决策。
Python 是一种建立在你已经在 C 中学到的知识之上的编程语言。
Python 还可以访问大量用户创建的库。
与 C 这种编译型语言不同,Python 是一种解释型语言,你不需要单独编译程序。相反,你在 Python 解释器 (Python Interpreter) 中运行程序。
到目前为止,代码看起来像这样:
// A program that says hello to the world
#include
int main(void)
{
printf("hello, world\n");
}
- 今天,你会发现编写和编译代码的过程得到了简化。
例如,上面的代码在 Python 中将呈现为:
# A program that says hello to the world
print("hello, world")
请注意,分号消失了,也不需要任何库。你可以通过在终端输入 python hello.py 来运行该程序。
- Python 特别适合以相对简单的方式实现 C 中相当复杂的功能。
拼写检查器 (Speller)
为了说明这种简洁性,让我们在终端窗口输入 code dictionary.py 并编写如下代码:
# Words in dictionary
words = set()
def check(word):
"""Return true if word is in dictionary else false"""
return word.lower() in words
def load(dictionary):
"""Load dictionary into memory, returning true if successful else false"""
with open(dictionary) as file:
words.update(file.read().splitlines())
return True
def size():
"""Returns number of words in dictionary if loaded else 0 if not yet loaded"""
return len(words)
def unload():
"""Unloads dictionary from memory, returning true if successful else false"""
return True
请注意,上面有四个函数。在 check 函数中,如果 word 在 words 中,它就返回 True。这比 C 中的实现容易得多!同样,在 load 函数中,打开字典文件。对于该文件中的每一行,我们将该行添加到 words 中。使用 rstrip,可以从添加的单词中删除尾随的换行符。size 仅返回 words 的长度 len。unload 只需要返回 True,因为 Python 会自动处理内存管理。
上面的代码说明了为什么存在高级语言:为了简化并让你更轻松地编写代码。
然而,速度是一个权衡。因为 C 允许你(程序员)做出关于内存管理的决策,所以它运行速度可能比 Python 快——这取决于你的代码。虽然 C 仅运行你的代码行,但 Python 在你调用其内置函数时运行幕后所有的代码。
你可以在 Python 文档中了解有关函数的更多信息。
过滤器 (Filter)
为了进一步说明这种简洁性,请在终端窗口输入 code blur.py 创建一个新文件,并编写如下代码:
# Blurs an image
from PIL import Image, ImageFilter
# Blur image
before = Image.open("bridge.bmp")
after = before.filter(ImageFilter.BoxBlur(1))
after.save("out.bmp")
请注意,该程序从名为 PIL 的库中导入了 Image 和 ImageFilter 模块。它接收一个输入文件并创建一个输出文件。
此外,你可以创建一个名为 edges.py 的新文件,如下所示:
# Finds edges in an image
from PIL import Image, ImageFilter
# Find edges
before = Image.open("bridge.bmp")
after = before.filter(ImageFilter.FIND_EDGES)
after.save("out.bmp")
请注意,这段代码只是对 blur 代码的一个小调整,但会产生截然不同的结果。
Python 允许你将 C 和其他“低级”编程语言中更为复杂的编程细节抽象化。
函数
在 C 中,你可能见过如下函数:
printf("hello, world\n");
在 Python 中,你会看到如下函数:
print("hello, world")
库、模块和包
- 与 C 一样,CS50 库也可以在 Python 中使用。
以下函数将特别有用:
get_float
get_int
get_string
你可以按如下方式导入 CS50 库:
import cs50
你还可以选择仅从 CS50 库导入特定函数,如下所示:
from cs50 import get_float, get_int, get_string
字符串
在 C 中,你可能还记得这段代码:
// get_string and printf with %s
#include
#include
int main(void)
{
string answer = get_string("What's your name? ");
printf("hello, %s\n", answer);
}
此代码在 Python 中转换为:
# get_string and print, with concatenation
from cs50 import get_string
answer = get_string("What's your name? ")
print("hello, " + answer)
你可以通过在终端窗口执行 code hello.py 来编写此代码。然后,你可以通过运行 python hello.py 来执行此代码。请注意 + 符号如何连接 "hello, " 和 answer。
同样,这也可以在不进行连接的情况下完成:
# get_string and print, without concatenation
from cs50 import get_string
answer = get_string("What's your name? ")
print("hello,", answer)
请注意,print 语句会自动在 hello 语句和 answer 之间创建一个空格。
同样,你可以将上述代码实现为:
# get_string and print, with format strings
from cs50 import get_string
answer = get_string("What's your name? ")
print(f"hello, {answer}")
请注意大括号如何允许 print 函数对 answer 进行插值,从而使 answer 出现在其中。f 指出该字符串需要包含格式化的 answer。
位置参数和命名参数
C 中的函数(例如
fread、fwrite和printf)使用位置参数,你可以在其中提供以逗号作为分隔符的参数。作为程序员,你必须记住哪个参数位于哪个位置。这些被称为“位置参数”。在 Python 中,命名参数允许你提供参数而无需考虑位置。
你可以在 文档 中了解有关
print函数参数的更多信息。
访问该文档,你可能会看到以下内容:
print(*objects, sep=' ', end='\n', file=None, flush=False)
请注意,可以提供各种对象进行打印。提供了一个单个空格的分隔符,当向 print 提供多个对象时,将显示该分隔符。同样,在 print 语句的末尾提供了一个换行符。
变量
变量声明也被简化了。在 C 中,你可能有
int counter = 0;。在 Python 中,同一行将写作counter = 0。你无需声明变量的类型。Python 倾向于使用
counter += 1来加一,失去了 C 中输入counter++的能力。
类型
- Python 中的数据类型不需要显式声明。例如,你看到上面的
answer是一个字符串,但我们不必告诉解释器它是字符串:它自己知道。
在 Python 中,常用的类型包括:
bool
float
int
str
请注意,缺少 long 和 double。Python 将自动处理较大和较小数字应使用哪种数据类型。
Python 中的一些其他数据类型包括:
range sequence of numbers
list sequence of mutable values
tuple sequence of immutable values
dict collection of key-value pairs
set collection of unique values
- 这些数据类型中的每一种都可以在 C 中实现,但在 Python 中,它们的实现更为简单。
计算器
你可能还记得本课程前面的 calculator.c:
// Addition with int
#include
#include
int main(void)
{
// Prompt user for x
int x = get_int("x: ");
// Prompt user for y
int y = get_int("y: ");
// Perform addition
printf("%i\n", x + y);
}
我们可以实现一个简单的计算器,就像我们在 C 中所做的那样。在终端窗口中输入 code calculator.py 并编写代码如下:
# Addition with int [using get_int]
from cs50 import get_int
# Prompt user for x
x = get_int("x: ")
# Prompt user for y
y = get_int("y: ")
# Perform addition
print(x + y)
请注意 CS50 库是如何导入的。然后,从用户处收集 x 和 y。最后,打印结果。请注意,在 C 程序中看到的 main 函数完全消失了!虽然可以使用 main 函数,但这不是必需的。
我们可以移除 CS50 库的辅助轮。修改你的代码如下:
# Addition with int [using input]
# Prompt user for x
x = input("x: ")
# Prompt user for y
y = input("y: ")
# Perform addition
print(x + y)
请注意,执行上述代码会导致奇怪的程序行为。为什么会这样呢?
你可能已经猜到解释器将 x 和 y 理解为字符串。你可以通过使用 int 函数来修复代码,如下所示:
# Addition with int [using input]
# Prompt user for x
x = int(input("x: "))
# Prompt user for y
y = int(input("y: "))
# Perform addition
print(x + y)
请注意 x 和 y 的输入如何传递给 int 函数,该函数将其转换为整数。如果不将 x 和 y 转换为整数,字符将被连接起来。
条件语句
在 C 中,你可能还记得这样的程序:
// Conditionals, Boolean expressions, relational operators
#include
#include
int main(void)
{
// Prompt user for integers
int x = get_int("What's x? ");
int y = get_int("What's y? ");
// Compare integers
if (x y)
{
printf("x is less than y\n");
}
else if (x > y)
{
printf("x is greater than y\n");
}
else
{
printf("x is equal to y\n");
}
}
在 Python 中,它会呈现如下:
# Conditionals, Boolean expressions, relational operators
from cs50 import get_int
# Prompt user for integers
x = get_int("What's x? ")
y = get_int("What's y? ")
# Compare integers
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")
请注意,不再有花括号。相反,使用了缩进。其次,if 语句中使用了冒号。此外,elif 取代了 else if。if 和 elif 语句中也不再需要括号。
进一步查看比较,请考虑 C 中的以下代码:
// Logical operators
#include
#include
int main(void)
{
// Prompt user to agree
char c = get_char("Do you agree? ");
// Check whether agreed
if (c == 'Y' || c == 'y')
{
printf("Agreed.\n");
}
else if (c == 'N' || c == 'n')
{
printf("Not agreed.\n");
}
}
上面的实现可以如下:
# Logical operators
from cs50 import get_string
# Prompt user to agree
s = get_string("Do you agree? ")
# Check whether agreed
if s == "Y" or s == "y":
print("Agreed.")
elif s == "N" or s == "n":
print("Not agreed.")
请注意,C 中使用的两个竖线已替换为 or。事实上,人们经常喜欢 Python,因为它更容易被人类阅读。另外,请注意 Python 中不存在 char。相反,使用 str。
使用 列表 (lists) 来实现相同代码的另一种方法如下:
# Logical operators, using lists
from cs50 import get_string
# Prompt user to agree
s = get_string("Do you agree? ")
# Check whether agreed
if s in ["y", "yes"]:
print("Agreed.")
elif s in ["n", "no"]:
print("Not agreed.")
请注意我们如何能够在 list 中表达多个关键字,例如 y 和 yes。
面向对象编程
某些类型的值可能不仅具有内部的属性,还具有函数。在 Python 中,这些值被称为对象。
在 C 中,我们可以创建一个
struct,你可以在其中将多个变量关联到单个自创建的数据类型中。在 Python 中,我们可以做到这一点,并且还可以在自创建的数据类型中包含函数。当一个函数属于一个特定的对象时,它被称为方法。
例如,Python 中的 str 类型有内置方法。因此,你可以按如下方式修改代码:
# Logical operators, using lists
# Prompt user to agree
s = input("Do you agree? ").lower()
# Check whether agreed
if s in ["y", "yes"]:
print("Agreed.")
elif s in ["n", "no"]:
print("Not agreed.")
请注意 s 的旧值如何被 s.lower()(str 的内置方法)的结果覆盖。
同样,你可能还记得我们如何在 C 中复制字符串:
// Capitalizes a copy of a string without memory errors
#include
#include
#include
#include
#include
int main(void)
{
// Get a string
char *s = get_string("s: ");
if (s == NULL)
{
return 1;
}
// Allocate memory for another string
char *t = malloc(strlen(s) + 1);
if (t == NULL)
{
return 1;
}
// Copy string into memory
strcpy(t, s);
// Capitalize copy
if (strlen(t) > 0)
{
t[0] = toupper(t[0]);
}
// Print strings
printf("s: %s\n", s);
printf("t: %s\n", t);
// Free memory
free(t);
return 0;
}
注意代码行数。
我们可以在 Python 中实现上述内容,如下所示:
# Capitalizes a copy of a string
# Get a string
s = input("s: ")
# Capitalize copy of string
t = s.capitalize()
# Print strings
print(f"s: {s}")
print(f"t: {t}")
请注意该程序比 C 中的对应程序短了多少。
循环
Python 中的循环与 C 非常相似。你可能还记得 C 中的以下代码:
// Demonstrates for loop
#include
int main(void)
{
for (int i = 0; i 3; i++)
{
printf("meow\n");
}
}
for 循环可以在 Python 中实现,如下所示:
# Better design
for i in range(3):
print("meow")
请注意,从未明确使用 i。但是,Python 会自动递增 i 的值。
此外,可以按如下方式实现 while 循环:
# Demonstrates while loop
i = 0
while i 3:
print("meow")
i += 1
为了进一步理解 Python 中的循环和迭代,让我们创建一个名为 uppercase.py 的新文件,如下所示:
# Uppercases string one character at a time
before = input("Before: ")
print("After: ", end="")
for c in before:
print(c.upper(), end="")
print()
请注意如何使用 end= 将参数传递给 print 函数,从而使该行在没有换行的情况下继续。此代码一次处理字符串中的一个字符。
阅读文档,我们发现 Python 有可以在整个字符串上实现的方法,如下所示:
# Uppercases string all at once
before = input("Before: ")
after = before.upper()
print(f"After: {after}")
请注意 .upper() 如何应用于整个字符串。
抽象
正如我们今天早些时候暗示的,你可以使用函数进一步改进代码,并将各种代码抽象为函数。修改你之前创建的 meow.py 代码,如下所示:
# Abstraction
def main():
for i in range(3):
meow()
# Meow once
def meow():
print("meow")
main()
请注意,meow 函数抽象了 print 语句。此外,请注意 main 函数出现在文件的顶部。在文件的底部,调用了 main 函数。按照惯例,你应该在 Python 中创建 main 函数。
事实上,我们可以在函数之间传递变量,如下所示:
# Abstraction with parameterization
def main():
meow(3)
# Meow some number of times
def meow(n):
for i in range(n):
print("meow")
main()
请注意 meow 现在如何接收变量 n。在 main 函数中,你可以调用 meow 并向其传递类似 3 的值。然后,meow 在 for 循环中使用 n 的值。
阅读上面的代码,请注意作为 C 程序员的你如何能够很容易地理解它。虽然某些约定有所不同,但你之前学到的构建块在这种新的编程语言中非常明显。
截断和浮点数不精确
- 回想一下,在 C 中,我们经历过截断,即一个整数除以另一个整数可能会导致不精确的结果。
你可以通过修改 calculator.py 的代码来了解 Python 如何处理此类除法:
# Division with integers, demonstration lack of truncation
# Prompt user for x
x = int(input("x: "))
# Prompt user for y
y = int(input("y: "))
# Divide x by y
z = x / y
print(z)
请注意,执行此代码会产生一个值,但如果你在 .333333 之后看到更多数字,你会发现我们面临浮点数不精确。不会发生截断。
我们可以通过稍微修改代码来揭示这种不精确性:
# Floating-point imprecision
# Prompt user for x
x = int(input("x: "))
# Prompt user for y
y = int(input("y: "))
# Divide x by y
z = x / y
print(f"{z:.50f}")
请注意,这段代码揭示了不精确性。Python 仍然面临这个问题,就像 C 一样。
异常
- 让我们进一步探讨运行 Python 代码时可能发生的异常。
修改 calculator.py 如下:
# Doesn't handle exception
# Prompt user for an integer
n = int(input("Input: "))
print("Integer")
请注意,输入错误的数据可能会导致错误。
我们可以通过修改代码来 try 处理和“捕获”潜在的异常,如下所示:
# Handles exception
# Prompt user for an integer
try:
n = int(input("Input: "))
print("Integer.")
except ValueError:
print("Not integer.")
请注意,上面的代码反复尝试获取正确的数据类型,并在需要时提供附加提示。
马里奥 (Mario)
回想一下几周前我们面临的挑战,即像《马里奥》一样,将三个方块叠在一起。
在 Python 中,我们可以实现类似的东西,如下所示:
# Prints a column of 3 bricks with a loop
for i in range(3):
print("#")
这将打印一列三块砖。
在 C 中,我们有 do-while 循环的优势。然而,在 Python 中,通常使用 while 循环,因为 Python 没有 do-while 循环。你可以在名为 mario.py 的文件中编写如下代码:
# Prints a column of n bricks with a loop
from cs50 import get_int
while True:
n = get_int("Height: ")
if n > 0:
break
for i in range(n):
print("#")
请注意如何使用 while 循环来获取高度。一旦输入大于零的高度,循环就会中断。
考虑下图:
在 Python 中,我们可以通过修改代码来实现,如下所示:
# Prints a row of 4 question marks with a loop
for i in range(4):
print("?", end="")
print()
请注意,你可以覆盖 print 函数的行为,以保持在同一行而不换行。
与之前的迭代精神类似,我们可以进一步简化这个程序:
# Prints a row of 4 question marks without a loop
print("?" * 4)
请注意,我们可以利用 * 来乘以字符串,使其重复 4 次。
如果是大块砖块呢?
要实现上述内容,你可以修改代码如下:
# Prints a 3-by-3 grid of bricks with loops
for i in range(3):
for j in range(3):
print("#", end="")
print()
请注意一个 for 循环如何嵌套在另一个循环中。外部的 print 语句在每行砖块的末尾添加一个新行。
你可以在 Python 文档 中了解有关 print 函数的更多信息。
列表 (Lists)
list 是 Python 中的一种数据结构。
list 内部有内置方法或函数。
例如,考虑以下代码:
# Averages three numbers using a list
# Scores
scores = [72, 73, 33]
# Print average
average = sum(scores) / len(scores)
print(f"Average: {average}")
请注意,你可以使用内置的 sum 方法来计算平均值。
你甚至可以使用以下语法从用户那里获取值:
# Averages three numbers using a list and a loop
from cs50 import get_int
# Get scores
scores = []
for i in range(3):
score = get_int("Score: ")
scores.append(score)
# Print average
average = sum(scores) / len(scores)
print(f"Average: {average}")
请注意,此代码对列表使用了内置的 append 方法。
搜索和字典
- 我们还可以在数据结构内进行搜索。
考虑一个名为 phonebook.py 的程序,如下所示:
# Implements linear search for names using loop
# A list of names
names = ["Yuliia", "David", "John"]
# Ask for name
name = input("Name: ")
# Search for name
for n in names:
if name == n:
print("Found")
break
else:
print("Not found")
请注意这是如何实现对每个名称的线性搜索的。
但是,我们不需要手动遍历列表。在 Python 中,我们可以这样执行线性搜索:
# Implements linear search for names using `in`
# A list of names
names = ["Yuliia", "David", "John"]
# Ask for name
name = input("Name: ")
# Search for name
if name in names:
print("Found")
else:
print("Not found")
请注意 in 如何用于实现线性搜索。
不过,这段代码还可以改进。
回想一下,字典 或
dict是键 (key) 和 值 (value) 对的集合。
你可以在 Python 中实现字典,如下所示:
# Implements a phone book as a list of dictionaries, without a variable
from cs50 import get_string
people = [
{"name": "Yuliia", "number": "+1-617-495-1000"},
{"name": "David", "number": "+1-617-495-1000"},
{"name": "John", "number": "+1-949-468-2750"},
]
# Search for name
name = get_string("Name: ")
for person in people:
if person["name"] == name:
print(f"Found {person['number']}")
break
else:
print("Not found")
请注意,列表的每个条目都是一个包含 name 和 number 的字典。
更好的是,严格来说,我们不需要同时使用 name 和 number 标签。我们可以将这段代码简化如下:
# Implements a phone book using a dictionary
from cs50 import get_string
people = {
"Yuliia": "+1-617-495-1000",
"David": "+1-617-495-1000",
"John": "+1-949-468-2750",
}
# Search for name
name = get_string("Name: ")
if name in people:
print(f"Number: {people[name]}")
else:
print("Not found")
请注意,字典是使用花括号实现的。然后,语句 if name in people 搜索 name 是否在 people 字典的键中。此外,请注意在 print 语句中,我们如何使用 name 的值作为索引来访问 people 字典。非常有用!
Python 已尽最大努力使内置搜索达到“常数时间”。
你可以在 Python 文档 中了解有关字典的更多信息。
命令行参数
与 C 一样,你也可以使用命令行参数。考虑以下代码:
# Prints a command-line argument
from sys import argv
if len(argv) == 2:
print(f"hello, {argv[1]}")
else:
print("hello, world")
请注意,argv[1] 是使用格式化字符串打印的,由 print 语句前面的 f 指出。
你可以在 Python 文档 中了解有关 sys 库的更多信息。
退出状态
sys 库也有内置方法。我们可以使用 sys.exit(i) 以特定的退出代码退出程序:
# Exits with explicit value, importing sys
import sys
if len(sys.argv) != 2:
print("Missing command-line argument")
sys.exit(1)
print(f"hello, {sys.argv[1]}")
sys.exit(0)
请注意,点符号用于调用 sys 的内置函数。
CSV 文件
- Python 还内置了对 CSV 文件的支持。
修改 phonebook.py 的代码如下:
import csv
file = open("phonebook.csv", "a")
name = input("Name: ")
number = input("Number: ")
writer = csv.writer(file)
writer.writerow([name,number])
file.close()
注意 writerow 会自动为我们在 CSV 文件中添加逗号。
虽然 file.open 和 file.close 是 Python 中可用的语法,但可以按如下方式改进此代码:
import csv
name = input("Name: ")
number = input("Number: ")
with open("phonebook.csv", "a") as file:
writer = csv.writer(file)
writer.writerow([name,number])
请注意,代码在 with 语句下缩进。文件在代码块完成后会自动关闭。
同样,我们可以向 CSV 文件中写入字典,如下所示:
import csv
name = input("Name: ")
number = input("Number: ")
with open("phonebook.csv", "a") as file:
writer = csv.DictWriter(file, fieldnames=["name", "number"])
writer.writerow({"name": name, "number": number})
请注意,这段代码与我们之前的版本非常相似,但使用了 csv.DictWriter 代替。
第三方库
Python 的优势之一是其庞大的用户群和同样大量的第三方库。
如果你已经安装了 Python,你可以通过在自己的计算机上键入
pip install CS50来安装 CS50 库。考虑到其他库,David 演示了
cowsay和qrcode的使用。
总结
在本课程中,你学习了如何在 Python 中实现之前课程中的编程构建块。此外,你还了解了 Python 如何允许编写更简化的代码。此外,你还学习了如何利用各种 Python 库。最后,你了解到作为程序员,你的技能并不局限于单一编程语言。你已经看到了如何通过本课程发现一种新的学习方式,这种方式可以帮助你学习任何编程语言——甚至可能是几乎任何学习途径!具体来说,我们讨论了……
Python
变量
条件语句
循环
类型
面向对象编程
截断与浮点数不精确
异常
字典
命令行参数
第三方库
下次见!