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 函数中,如果 wordwords 中,它就返回 True。这比 C 中的实现容易得多!同样,在 load 函数中,打开字典文件。对于该文件中的每一行,我们将该行添加到 words 中。使用 rstrip,可以从添加的单词中删除尾随的换行符。size 仅返回 words 的长度 lenunload 只需要返回 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 的库中导入了 ImageImageFilter 模块。它接收一个输入文件并创建一个输出文件。

此外,你可以创建一个名为 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 中的函数(例如 freadfwriteprintf)使用位置参数,你可以在其中提供以逗号作为分隔符的参数。作为程序员,你必须记住哪个参数位于哪个位置。这些被称为“位置参数”。

  • 在 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

请注意,缺少 longdouble。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 库是如何导入的。然后,从用户处收集 xy。最后,打印结果。请注意,在 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)

请注意,执行上述代码会导致奇怪的程序行为。为什么会这样呢?

你可能已经猜到解释器将 xy 理解为字符串。你可以通过使用 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)

请注意 xy 的输入如何传递给 int 函数,该函数将其转换为整数。如果不将 xy 转换为整数,字符将被连接起来。

条件语句

在 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 ififelif 语句中也不再需要括号。

进一步查看比较,请考虑 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 中表达多个关键字,例如 yyes

面向对象编程

  • 某些类型的值可能不仅具有内部的属性,还具有函数。在 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 的皮毛。因此,当你继续学习时,Python 文档 将特别重要。

  • 你可以在 Python 文档 中了解有关字符串方法的更多信息。

循环

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 的值。然后,meowfor 循环中使用 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 方法。

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

  • 你还可以在 Python 文档 中了解有关 len 的更多信息。

搜索和字典

  • 我们还可以在数据结构内进行搜索。

考虑一个名为 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")

请注意,列表的每个条目都是一个包含 namenumber 的字典。

更好的是,严格来说,我们不需要同时使用 namenumber 标签。我们可以将这段代码简化如下:

# 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.openfile.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 演示了 cowsayqrcode 的使用。

总结

在本课程中,你学习了如何在 Python 中实现之前课程中的编程构建块。此外,你还了解了 Python 如何允许编写更简化的代码。此外,你还学习了如何利用各种 Python 库。最后,你了解到作为程序员,你的技能并不局限于单一编程语言。你已经看到了如何通过本课程发现一种新的学习方式,这种方式可以帮助你学习任何编程语言——甚至可能是几乎任何学习途径!具体来说,我们讨论了……

  • Python

  • 变量

  • 条件语句

  • 循环

  • 类型

  • 面向对象编程

  • 截断与浮点数不精确

  • 异常

  • 字典

  • 命令行参数

  • 第三方库

下次见!