自然的怀抱——北京自然博物馆参观随感

  国家自然博物馆,一次意外而充盈的沉浸体验。

  之所以说“意外”,是我之前甚至未曾听闻这座“国字头”博物馆的大名,更未动过到此参观的念头。小时候有一段时间喜欢翻阅百科全书,对这些已知的事物,亲眼目睹它们的还原似乎并不那么诱人。至少在选修这门课程之前我是这样想的。

阅读更多

通告

删除了不再有时效性的文章。

雪(精选集)

连载小说 & 概念 EP & 随笔

系列一:连载小说

(主线剧情:星, 梦, 昙未来黎明之烬涉江 & 凝眉

最新:凝眉

纪念初中时候的一段故事吧。顺便把这个系列收个尾。大概就结束了。

他和她坐在跑道边,说是要等着,等最后一片云霞沉寂在地平线下。时不时地,他们凑在对方的耳边,轻声低语;欢谑声里,沉沉的烟火灿烂着,像雪染成的白绫。风打在他和她的肩上。

阅读更多

NOIP2020 游记

(2021.4.29 补记)

这篇游记在去年十一月份开坑,一月份的时候正式写完。

还记得得知判决的那个晚上,我咕掉作业抱着手机坐了一整晚,还和家长起了一些争执。几个月以后再回来看,心态也平和了许多吧。可能这段经历不能带给我别的什么,也可能再过一段时间会为当时的选择而自怨自艾,为什么不最后冲一把,说不定就……

不过没有如果了吧。到了现在早就没有回头路了,何况回头路上还布着更跌宕的坎坷。@SiRiehn_nx 能够逆天改命,又不代表你也可以,真以为自己行了啊。

上面这些可能更多是写给未来的自己的吧,提醒自己不要后悔,没必要再去填补句号的空白。

阅读更多

Lecture 9

Et Cetera

  • Over the many past lessons, we have covered so much related to Python!
  • In this lesson, we will be focusing upon many of the “et cetera” items not previously discussed. “Et cetera” literally means “and the rest”!
  • Indeed, if you look at the Python documentation, you will find quite “the rest” of other features.

set

  • In math, a set would be considered a set of numbers without any duplicates.

  • In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    {"name": "Padma", "house": "Ravenclaw"},
    ]

    houses = []
    for student in students:
    if student["house"] not in houses:
    houses.append(student["house"])

    for house in sorted(houses):
    print(house)

    Notice how we have a list of dictionaries, each being a student. An empty list called houses is created. We iterate through each student in students. If a student’s house is not in houses, we append to our list of houses.

  • It turns out we can use the built-in set features to eliminate duplicates.

  • In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    {"name": "Padma", "house": "Ravenclaw"},
    ]

    houses = set()
    for student in students:
    houses.add(student["house"])

    for house in sorted(houses):
    print(house)

    Notice how no checking needs to be included to ensure there are no duplicates. The set object takes care of this for us automatically.

  • You can learn more in Python’s documentation of set.

Global Variables

  • In other programming languages, there is the notion of global variables that are accessible to any function.

  • We can leverage this ability within Python. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    balance = 0


    def main():
    print("Balance:", balance)


    if __name__ == "__main__":
    main()

    Notice how we create a global variable called balance, outside of any function.

  • Since no errors are presented by executing the code above, you’d think all is well. However, it is not! In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    balance = 0


    def main():
    print("Balance:", balance)
    deposit(100)
    withdraw(50)
    print("Balance:", balance)


    def deposit(n):
    balance += n


    def withdraw(n):
    balance -= n


    if __name__ == "__main__":
    main()

    Notice how we now add the functionality to add and withdraw funds to and from balance. However, executing this code, we are presented with an error! We see an error called UnboundLocalError. You might be able to guess that, at least in the way we’ve currently coded balance and our deposit and withdraw functions, we can’t reassign it a value value inside a function.

  • To interact with a global variable inside a function, the solution is to use the global keyword. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    balance = 0


    def main():
    print("Balance:", balance)
    deposit(100)
    withdraw(50)
    print("Balance:", balance)


    def deposit(n):
    global balance
    balance += n


    def withdraw(n):
    global balance
    balance -= n


    if __name__ == "__main__":
    main()

    Notice how the global keyword tells each function that balance does not refer to a local variable: instead, it refers to the global variable we originally placed at the top of our code. Now, our code functions!

  • Utilizing our powers from our experience with object-oriented programming, we can modify our code to use a class instead of a global variable. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Account:
    def __init__(self):
    self._balance = 0

    @property
    def balance(self):
    return self._balance

    def deposit(self, n):
    self._balance += n

    def withdraw(self, n):
    self._balance -= n


    def main():
    account = Account()
    print("Balance:", account.balance)
    account.deposit(100)
    account.withdraw(50)
    print("Balance:", account.balance)


    if __name__ == "__main__":
    main()

    Notice how we use account = Account() to create an account. Classes allow us to solve this issue of needing a global variable more cleanly because these instance variables are accessible to all the methods of this class utilizing self.

  • Generally speaking, global variables should be used quite sparingly, if at all!

Constants

  • Some languages allow you to create variables that are unchangeable, called “constants”. Constants allow one to program defensively and reduce the opportunities for important values to be altered.

  • In the text editor window, code as follows:

    1
    2
    3
    4
    5

    MEOWS = 3

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

    Notice MEOWS is our constant in this case. Constants are typically denoted by capital variable names and are placed at the top of our code. Though this looks like a constant, in reality, Python actually has no mechanism to prevent us from changing that value within our code! Instead, you’re on the honor system: if a variable name is written in all caps, just don’t change it!

  • One can create a class “constant”, now in quotes because we know Python doesn’t quite support “constants”. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Cat:
    MEOWS = 3

    def meow(self):
    for _ in range(Cat.MEOWS):
    print("meow")


    cat = Cat()
    cat.meow()

    Because MEOWS is defined outside of any particular class method, all of them have access to that value via Cat.MEOWS.

Type Hints

  • In other programming languages, one expresses explicitly what variable type you want to use.
  • As we saw earlier in the course, Python does require the explicit declaration of types.
  • Nevertheless, it’s good practice need to ensure all of your variables are of the right type.
  • mypy is a program that can help you test to make sure all your variables are of the right type.
  • You can install mypy by executing in your terminal window: pip install mypy.

In the text editor window, code as follows:

1
2
3
4
5
6
7
def meow(n):
for _ in range(n):
print("meow")


number = input("Number: ")
meow(number)

You may already see that number = input("Number: )" returns a string, not an int. But meow will likely want an int!

  • A type hint can be added to give Python a hint of what type of variable meow should expect. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    def meow(n: int):
    for _ in range(n):
    print("meow")


    number = input("Number: ")
    meow(number)

    Notice, though, that our program still throws an error.

  • After installing mypy, execute mypy meows.py in the terminal window. mypy will provide some guidance about how to fix this error.

  • You can annotate all your variables. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    def meow(n: int):
    for _ in range(n):
    print("meow")


    number: int = input("Number: ")
    meow(number)

    Notice how number is now provided a type hint.

  • Again, executing mypy meows.py in the terminal window provides much more specific feedback to you, the programmer.

  • We can fix our final error by coding as follows:

    1
    2
    3
    4
    5
    6
    7
    def meow(n: int):
    for _ in range(n):
    print("meow")


    number: int = int(input("Number: "))
    meow(number)

    Notice how running mypy how produces no errors because cast our input as an integer.

  • Let’s introduce a new error by assuming that meow will return to us a string, or str. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    def meow(n: int):
    for _ in range(n):
    print("meow")


    number: int = int(input("Number: "))
    meows: str = meow(number)
    print(meows)

    Notice how the meow function has only a side effect. Because we only attempt to print “meow”, not return a value, an error is thrown when we try to store the return value of meow in meows.

  • We can further use type hints to check for errors, this time annotating the return values of functions. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    def meow(n: int) -> None:
    for _ in range(n):
    print("meow")


    number: int = int(input("Number: "))
    meows: str = meow(number)
    print(meows)

    Notice how the notation -> None tells mypy that there is no return value.

  • We can modify our code to return a string if we wish:

    1
    2
    3
    4
    5
    6
    7
    def meow(n: int) -> str:
    return "meow\n" * n


    number: int = int(input("Number: "))
    meows: str = meow(number)
    print(meows, end="")

    Notice how we store in meows multiple strs. Running mypy produces no errors.

  • You can learn more in Python’s documentation of Type Hints.

  • You can learn more about mypy through the program’s own documentation.

Docstrings

  • A standard way of commenting your function’s purpose is to use a docstring. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    def meow(n):
    """Meow n times."""
    return "meow\n" * n


    number = int(input("Number: "))
    meows = meow(number)
    print(meows, end="")

    Notice how the three double quotes designate what the function does.

  • You can use docstrings to standardize how you document the features of a function. In the text editor window, code as follows: s

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def meow(n):
    """
    Meow n times.

    :param n: Number of times to meow
    :type n: int
    :raise TypeError: If n is not an int
    :return: A string of n meows, one per line
    :rtype: str
    """
    return "meow\n" * n


    number = int(input("Number: "))
    meows = meow(number)
    print(meows, end="")

    Notice how multiple docstring arguments are included. For example, it describes the parameters taken by the function and what is returned by the function.

  • Established tools, such as Sphinx, can be used to parse docstrings and automatically create documentation for us in the form of web pages and PDF files such that you can publish and share with others.

  • You can learn more in Python’s documentation of docstrings.

argparse

  • Suppose we want to use command-line arguments in our program. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import sys

    if len(sys.argv) == 1:
    print("meow")
    elif len(sys.argv) == 3 and sys.argv[1] == "-n":
    n = int(sys.argv[2])
    for _ in range(n):
    print("meow")
    else:
    print("usage: meows.py [-n NUMBER]")

    Notice how sys is imported, from which we get access to sys.argv—an array of command-line arguments given to our program when run. We can use several if statements to check whether the use has run our program properly.

  • Let’s assume that this program will be getting much more complicated. How could we check all the arguments that could be inserted by the user? We might give up if we have more than a few command-line arguments!

  • Luckily, argparse is a library that handles all the parsing of complicated strings of command-line arguments. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("-n")
    args = parser.parse_args()

    for _ in range(int(args.n)):
    print("meow")

    Notice how argparse is imported instead of sys. An object called parser is created from an ArgumentParser class. That class’s add_argument method is used to tell argparse what arguments we should expect from the user when they run our program. Finally, running the parser’s parse_args method ensures that all of the arguments have been included properly by the user.

  • We can also program more cleanly, such that our user can get some information about the proper usage of our code when they fail to use the program correctly. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    import argparse

    parser = argparse.ArgumentParser(description="Meow like a cat")
    parser.add_argument("-n", help="number of times to meow")
    args = parser.parse_args()

    for _ in range(int(args.n)):
    print("meow")

    Notice how the user is provided some documentation. Specifically, a help argument is provided. Now, if the user executes python meows.py --help or -h, the user will be presented with some clues about how to use this program.

  • We can further improve this program. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    import argparse

    parser = argparse.ArgumentParser(description="Meow like a cat")
    parser.add_argument("-n", default=1, help="number of times to meow", type=int)
    args = parser.parse_args()

    for _ in range(args.n):
    print("meow")

    Notice how not only is help documentation included, but you can provide a default value when no arguments are provided by the user.

  • You can learn more in Python’s documentation of argparse.

Unpacking

  • Would it not be nice to be able to split a single variable into two variables? In the text editor window, code as follows:

    1
    2
    first, _ = input("What's your name? ").split(" ")
    print(f"hello, {first}")

    Notice how this program tries to get a user’s first name by naively splitting on a single space.

  • It turns out there are other ways to unpack variables. You can write more powerful and elegant code by understanding how to unpack variables in seemingly more advanced ways. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    def total(galleons, sickles, knuts):
    return (galleons * 17 + sickles) * 29 + knuts


    print(total(100, 50, 25), "Knuts")

    Notice how this returns the total value of Knuts.

  • What if we wanted to store our coins in a list? In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    def total(galleons, sickles, knuts):
    return (galleons * 17 + sickles) * 29 + knuts


    coins = [100, 50, 25]

    print(total(coins[0], coins[1], coins[2]), "Knuts")

    Notice how a list called coins is created. We can pass each value in by indexing using 01, and so on.

  • This is getting quite verbose. Wouldn’t it be nice if we could simply pass the list of coins to our function?

  • To enable the possibility of passing the entire list, we can use unpacking. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    def total(galleons, sickles, knuts):
    return (galleons * 17 + sickles) * 29 + knuts


    coins = [100, 50, 25]

    print(total(*coins), "Knuts")

    Notice how a * unpacks the sequence of the list of coins and passes in each of its individual elements to total.

  • Suppose that we could pass in the names of the currency in any order? In the text editor window, code as follows:

    1
    2
    3
    4
    5
    def total(galleons, sickles, knuts):
    return (galleons * 17 + sickles) * 29 + knuts


    print(total(galleons=100, sickles=50, knuts=25), "Knuts")

    Notice how this still calculates correctly.

  • When you start talking about “names” and “values,” dictionaries might start coming to mind! You can implement this as a dictionary. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    def total(galleons, sickles, knuts):
    return (galleons * 17 + sickles) * 29 + knuts


    coins = {"galleons": 100, "sickles": 50, "knuts": 25}

    print(total(coins["galleons"], coins["sickles"], coins["knuts"]), "Knuts")

    Notice how a dictionary called coins is provided. We can index into it using keys, such as “galleons” or “sickles”.

  • Since the total function expects three arguments, we cannot pass in a dictionary. We can use unpacking to help with this. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    def total(galleons, sickles, knuts):
    return (galleons * 17 + sickles) * 29 + knuts


    coins = {"galleons": 100, "sickles": 50, "knuts": 25}

    print(total(**coins), "Knuts")

    Notice how ** allows you to unpack a dictionary. When unpacking a dictionary, it provides both the keys and values.

args and kwargs

  • Recall the print documentation we looked at earlier in this course:

    1
    print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
  • args are positional arguments, such as those we provide to print like print("Hello", "World").

  • kwargs are named arguments, or “keyword arguments”, such as those we provide to print like print(end="").

  • As we see in the prototype for the print function above, we can tell our function to expect a presently unknown number positional arguments. We can also tell it to expect a presently unknown number of keyword arguments. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    def f(*args, **kwargs):
    print("Positional:", args)


    f(100, 50, 25)

    Notice how executing this code will be printed as positional arguments.

  • We can even pass in named arguments. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    def f(*args, **kwargs):
    print("Named:", kwargs)


    f(galleons=100, sickles=50, knuts=25)

    Notice how the named values are provided in the form of a dictionary.

  • Thinking about the print function above, you can see how *objects takes any number of positional arguments.

  • You can learn more in Python’s documentation of print.

map

  • Early on, we began with procedural programming.

  • We later revealed Python is an object oriented programming language.

  • We saw hints of functional programming, where functions have side effects without a return value. We can illustrate this in the text editor window, type code yell.py and code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def main():
    yell("This is CS50")


    def yell(word):
    print(word.upper())


    if __name__ == "__main__":
    main()

    Notice how the yell function is simply yelled.

  • Wouldn’t it be nice to yell a list of unlimited words? Modify your code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def main():
    yell(["This", "is", "CS50"])


    def yell(words):
    uppercased = []
    for word in words:
    uppercased.append(word.upper())
    print(*uppercased)


    if __name__ == "__main__":
    main()

    Notice we accumulate the uppercase words, iterating over each of the words and uppercasing them. The uppercase list is printed utilizing the * to unpack it.

  • Removing the brackets, we can pass the words in as arguments. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def main():
    yell("This", "is", "CS50")


    def yell(*words):
    uppercased = []
    for word in words:
    uppercased.append(word.upper())
    print(*uppercased)


    if __name__ == "__main__":
    main()

    Notice how *words allows for many arguments to be taken by the function.

  • map allows you to map a function to a sequence of values. In practice, we can code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def main():
    yell("This", "is", "CS50")


    def yell(*words):
    uppercased = map(str.upper, words)
    print(*uppercased)


    if __name__ == "__main__":
    main()

    Notice how map takes two arguments. First, it takes a function we want applied to every element of a list. Second, it takes that list itself, to which we’ll apply the aforementioned function. Hence, all words in words will be handed to the str.upper function and returned to uppercased.

  • You can learn more in Python’s documentation of map.

List Comprehensions

  • List comprehensions allow you to create a list on the fly in one elegant one-liner.

  • We can implement this in our code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def main():
    yell("This", "is", "CS50")


    def yell(*words):
    uppercased = [arg.upper() for arg in words]
    print(*uppercased)


    if __name__ == "__main__":
    main()

    Notice how instead of using map, we write a Python expression within square brackets. For each argument, .upper is applied to it.

  • Taking this concept further, let’s pivot toward another program.

  • In the text editor window, type code gryffindors.py and code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    ]

    gryffindors = []
    for student in students:
    if student["house"] == "Gryffindor":
    gryffindors.append(student["name"])

    for gryffindor in sorted(gryffindors):
    print(gryffindor)

    Notice we have a conditional while we’re creating our list. If the student’s house is Gryffindor, we append the student to the list of names. Finally, we print all the names.

  • More elegantly, we can simplify this code with a list comprehension as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    ]

    gryffindors = [
    student["name"] for student in students if student["house"] == "Gryffindor"
    ]

    for gryffindor in sorted(gryffindors):
    print(gryffindor)

    Notice how the list comprehension is on a single line!

filter

  • Using Python’s filter function allows us to return a subset of a sequence for which a certain condition is true.

  • In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    ]


    def is_gryffindor(s):
    return s["house"] == "Gryffindor"


    gryffindors = filter(is_gryffindor, students)

    for gryffindor in sorted(gryffindors, key=lambda s: s["name"]):
    print(gryffindor["name"])

    Notice how a function called is_gryffindor is created. This is our filtering function that will take a student s, and return True or False depending on whether the student’s house is Gryffindor. You can see the new filter function takes two arguments. First, it takes the function that will be applied to each element in a sequence—in this case, is_gryffindor. Second, it takes the sequence to which it will apply the filtering function—in this case, students. In gryffindors, we should see only those students who are in Gryffindor.

  • filter can also use lambda functions as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    ]


    gryffindors = filter(lambda s: s["house"] == "Gryffindor", students)

    for gryffindor in sorted(gryffindors, key=lambda s: s["name"]):
    print(gryffindor["name"])

    Notice how the same list of students is provided.

  • You can learn more in Python’s documentation of filter.

Dictionary Comprehensions

  • We can apply the same idea behind list comprehensions to dictionaries. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    students = ["Hermione", "Harry", "Ron"]

    gryffindors = []

    for student in students:
    gryffindors.append({"name": student, "house": "Gryffindor"})

    print(gryffindors)

    Notice how this code doesn’t (yet!) use any comprehensions. Instead, it follows the same paradigms we have seen before.

  • We can now apply dictionary comprehensions by modifying our code as follows:

    1
    2
    3
    4
    5
    students = ["Hermione", "Harry", "Ron"]

    gryffindors = [{"name": student, "house": "Gryffindor"} for student in students]

    print(gryffindors)

    Notice how all the prior code is simplified into a single line where the structure of the dictionary is provided for each student in students.

  • We can even simplify further as follows:

    1
    2
    3
    4
    5
    students = ["Hermione", "Harry", "Ron"]

    gryffindors = {student: "Gryffindor" for student in students}

    print(gryffindors)

    Notice how the dictionary will be constructed with key-value pairs.

enumerate

  • We may wish to provide some ranking of each student. In the text editor window, code as follows:

    1
    2
    3
    4
    students = ["Hermione", "Harry", "Ron"]

    for i in range(len(students)):
    print(i + 1, students[i])

    Notice how each student is enumerated when running this code.

  • Utilizing enumeration, we can do the same:

    1
    2
    3
    4
    students = ["Hermione", "Harry", "Ron"]

    for i, student in enumerate(students):
    print(i + 1, student)

    Notice how enumerate presents the index and the value of each student.

  • You can learn more in Python’s documentation of enumerate.

Generators and Iterators

  • In Python, there is a way to protect against your system running out of resources the problems they are addressing become too large.

  • In the United States, it’s customary to “count sheep” in one’s mind when one is having a hard time falling asleep.

  • In the text editor window, type code sleep.py and code as follows:

    1
    2
    3
    n = int(input("What's n? "))
    for i in range(n):
    print("🐑" * i)

    Notice how this program will count the number of sheep you ask of it.

  • We can make our program more sophisticated by adding a main function by coding as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    def main():
    n = int(input("What's n? "))
    for i in range(n):
    print("🐑" * i)


    if __name__ == "__main__":
    main()

    Notice how a main function is provided.

  • We have been getting into the habit of abstracting away parts of our code.

  • We can call a sheep function by modifying our code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def main():
    n = int(input("What's n? "))
    for i in range(n):
    print(sheep(i))


    def sheep(n):
    return "🐑" * n


    if __name__ == "__main__":
    main()

    Notice how the main function does the iteration.

  • We can provide the sheep function more abilities. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def main():
    n = int(input("What's n? "))
    for s in sheep(n):
    print(s)


    def sheep(n):
    flock = []
    for i in range(n):
    flock.append("🐑" * i)
    return flock


    if __name__ == "__main__":
    main()

    Notice how we create a flock of sheep and return the flock.

  • Executing our code, you might try different numbers of sheep such as 101000, and 10000. What if you asked for 1000000 sheep, your program might completely hang or crash. Because you have attempted to generate a massive list of sheep, your computer may be struggling to complete the computation.

  • The yield generator can solve this problem by returning a small bit of the results at a time. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def main():
    n = int(input("What's n? "))
    for s in sheep(n):
    print(s)


    def sheep(n):
    for i in range(n):
    yield "🐑" * i


    if __name__ == "__main__":
    main()

    Notice how yield provides only one value at a time while the for loop keeps working.

  • You can learn more in Python’s documentation of generators.

  • You can learn more in Python’s documentation of iterators.

![[1d5dd84a0d148b851127e481fc7624e.png]]
![[bc34d3b3c7287c070b98f7b80309104.png]]

函数 $f(x,y)$ 在区域 $D$ 上对 $x$,对 $y$ 均连续,则 $\forall (x_0,y_0) \in D$,
$$
|f(x,y)-f(x_0,y_0)|
\leq|f(x,y)-f(x,y_0)|+|f(x,y_0)-f(x_0,y_0)| \rightarrow 0
$$
xy/(x^2+y^2)

其中 $(x,y)$ 属于 $(x_0,y_0)$ 的某个邻域。

Harvard CS50P 课程笔记

寒假入门一下 Python,主要关注语法特性。中英文可能混杂,塑料英语见谅。

加入了部分 CS50 的内容。

原版英文笔记见 https://cs50.harvard.edu/python/2022/notes/

Lecture 0-5: Intro

注意 Python 和 C/C++ 的简要区别:

  • 函数体可被传参,可返回 multiple values(实质是 tuple 的封包/解包)。元组的不可变是指元组所指向的内存中的内容不可变
  • 完全不同的变量作用域/可覆盖机理,依照解释器调用顺序而非编辑器顺序。
    • Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问。
    • 注意:内部作用域想修改外部作用域的变量时,需使用 global 和 nonlocal 关键字。如果不使用 global,则这个变量只读。
    1
    2
    3
    4
    5
    6
    7
    8
    num = 1 
    def fun1():
    global num # 需要使用 global 关键字声明
    print(num)
    num = 123
    print(num)
    fun1()
    print(num)
  • 多变量赋值,见后文 Unpacking 部分。
  • 使用 for … in … 循环。更确切地说,习惯使用 in 关键字,以处理复杂类型的成员:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    people = {
    "Carter": "+1-617-495-1000",
    "David": "+1-949-468-2750"
    }

    name = "David"
    if name in people:
    number = people[name]
    print(f"Number: {number}")
    We use if name in people: to search the keys of our dictionary for a name. If the key exists, then we can get the value with the bracket notation, people[name].

Sequences

  1. 对于 str,可以进行 if "You" in "DoYou Can":
  2. f 字符串 print(f"Hello, {name}")。详细用法现查。
1
2
3
>>> x = 1  
>>> print(f'{x+1=}')   # Python 3.8
x+1=2
  1. 字符串的运算符。
  2. [::] Slice:
  • 第三个参数可接收步长,常见用法是正数或 [::-1]

Features

  • Perhaps useful: random.choice([list]) random.shufffle([]), str.upper().lower().title()
  • sorted(),用法见后文。
  • sys.argv (-> list) 读取 command line 的内容。assert 断言来终止程序。
  • try … except … else … finally
    • 可以 except 一个元组,内含多个异常。
  1. A simple sample of requests:
1
2
3
import requests
response = requests.get(url)
o = response.json() # translate the json file into a mixture of dict & list, according to the json file structure.
  1. Keyword match
1
2
3
4
5
6
7
match name:
case "Harry" | "Hermione" | "Ron":
print("1")
case "Draco":
print("2")
case _:
print("?")
  1. pytest 及其文件组织方式
1
2
3
4
| hello.py
| test (folder)
|--- __init__.py
|--- test_hello.py
whereas `test_hello.py` will be treated the same level as `hello.py`, i.e., you can `import hello` in `test_hello.py`. 
run `pytest test`.
  1. We can use the with keyword, which will close the file for us after we’re finished:
1
with open("phonebook.csv", "a") as file:

Lecture 6,7 are skipped.

Lecture 8: OOP

  1. 注意 str 和 keys:dict 的引号冲突。
  2. The order of the keys is typically remembered.
  3. student = Student() is creating an object/instance of that class.
  4. You can consider name as an attibute while student.name is accessing an instance variable.
  5. 类的 attributes 不必事先声明。
  6. keyword raise,制造异常配合 try…except。
  7. What is attibute?类的属性
    An attribute is anything that comes after a dot.
    A property is always a function in python.
  8. __init__, __str__
  9. @property 实现对成员赋值的加工处理
    重要 如果设置了 ‘getter func’ @property \n def house(self), and after that, ‘setter func’ @house.setter \n def house(self, house),那么当操作 object.house 时上述函数会被自动调用。似乎拥有先后次序。它们均可以被 __init__ 调用,尽管似乎位置顺序相反。
    然而,这样做会引起 func house 和 instance var house 的冲突。conventional solution 如下:
1
2
3
4
5
6
7
8
9
10
11
class Student:
def __init__(self, name, house):
self.name = name
self.house = house
@property
def house(self):
return self._house
def house(self, house):
if house not in somelist:
raise ValueError("???")
self._house = house

So now technically we have

  • instance var _house;
  • property(an attribute) house.
    注意! object._house 是 accessible 的。Python 没有 public/private/protected 关键字来限制访问。
    property 的访问形式与 instant var 相同,e.g. student.house = "Ravenclaw" 作为其语法糖。
  1. 留意函数 str.strip(), list.append(), type()。
  2. @classmethod 使得其不依赖类的实体而被调用。
  3. Class variable exists only once:
1
2
3
4
5
6
7
class Hat:
houses = ["1", "2", "3", "4"]
@classmethod
def sort(cls):
return random.choice()

Hat.sort()

因此,借助此修饰可以将读取并创建实例的工作整合进 class 里。

1
2
3
4
5
6
7
8
9
10
11
class Student:
def __init__(self, name, house):
self.name = name
self.house = house
def __str__(self):
return f"{self.name} from {self.house}"
@classmethod
def get(cls):
name = input("Name: ")
house = input("House: ")
return cls(name, house)

注意:technically 这里的 cls() 当然可以使用 Student() 来替代。推测可能是出于后续继承等的原因。在这个程序段中,此处的 cls 总是当前类。

Inheritance

Inheritance: Design your classes in a hierarchical(分层) fashion. 避免 duplication。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Wizard:
def __init__(self, name):
if not name:
raise ValueError("Missing name")
self.name = name
class Student(Wizard): # inherit: subclass/descendant class
def __init__(self, name, house):
super().__init__(name)
self.house = house

class Professor(Wizard):
def __init__(self, name, subject):
uper().__init__(name)
self.subject = subject


wizard = Wizard("Albus")
student = Student("Harry")
professor = Professor("Severus")
...

多继承暂时 skipped。

重载运算符

Python 支持重载运算符,需查询相应符号对应的保留函数名。

1
2
3
4
5
class Vault:
def __add__(self, other): # self 在左,other 在右
a = self.a + other.a
b = self.b + other.b
return Vault(a, b)

这里的左右类型不必相同。

Lecture 9: Et Cetera

set

  • In math, a set would be considered a set of numbers without any duplicates.

  • In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    {"name": "Padma", "house": "Ravenclaw"},
    ]

    houses = []
    for student in students:
    if student["house"] not in houses:
    houses.append(student["house"])

    for house in sorted(houses):
    print(house)

    Notice how we have a list of dictionaries, each being a student. An empty list called houses is created. We iterate through each student in students. If a student’s house is not in houses, we append to our list of houses.

  • It turns out we can use the built-in set features to eliminate duplicates.

  • In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    {"name": "Padma", "house": "Ravenclaw"},
    ]

    houses = set()
    for student in students:
    houses.add(student["house"])

    for house in sorted(houses):
    print(house)

    Notice how no checking needs to be included to ensure there are no duplicates. The set object takes care of this for us automatically.

Global Variables

  • In other programming languages, there is the notion of global variables that are accessible to any function.

  • Python 建议使用 OOP,尽量规避全局变量的使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Account:
    def __init__(self):
    self._balance = 0

    @property
    def balance(self):
    return self._balance

    def deposit(self, n):
    self._balance += n

    def withdraw(self, n):
    self._balance -= n


    def main():
    account = Account()
    print("Balance:", account.balance)
    account.deposit(100)
    account.withdraw(50)
    print("Balance:", account.balance)


    if __name__ == "__main__":
    main()

    Notice how we use account = Account() to create an account. Classes allow us to solve this issue of needing a global variable more cleanly because these instance variables are accessible to all the methods of this class utilizing self.

Constants

  • Constants are typically denoted by capital variable names and are placed at the top of our code. Though this looks like a constant, in reality, Python actually has no mechanism to prevent us from changing that value within our code! Instead, you’re on the honor system: if a variable name is written in all caps, just don’t change it!

  • One can create a class “constant”:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Cat:
    MEOWS = 3

    def meow(self):
    for _ in range(Cat.MEOWS):
    print("meow")


    cat = Cat()
    cat.meow()

    Because MEOWS is defined outside of any particular class method, all of them have access to that value via Cat.MEOWS.

Type Hints

  • Python does require the explicit declaration of types. 然而,Python 是强类型语言,几乎不容忍隐式的类型转换。引入 type hint 以便于在正式运行前进行从头到尾推导的类型检查。

  • A type hint can be added to give Python a hint of what type of variable or function should expect. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    def meow(n: int) -> None:
    for _ in range(n):
    print("meow")


    number: int = int(input("Number: "))
    meows: str = meow(number)
    print(meows)

Docstrings

  • You can use docstrings to standardize how you document the features of a function. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def meow(n):
    """
    Meow n times.

    :param n: Number of times to meow
    :type n: int
    :raise TypeError: If n is not an int
    :return: A string of n meows, one per line
    :rtype: str
    """
    return "meow\n" * n


    number = int(input("Number: "))
    meows = meow(number)
    print(meows, end="")
  • Established tools, such as Sphinx, can be used to parse docstrings and automatically create documentation for us in the form of web pages and PDF files such that you can publish and share with others.

  • You can learn more in Python’s documentation of docstrings.

argparse

  • You can learn more in Python’s documentation of argparse.

Unpacking

  • 一个序列 seq 在去赋值和传参时会被自动解包。赋值举例:

    1
    2
    first, _ = input("What's your name? ").split(" ")
    print(f"hello, {first}")

    ‘=’ 左右变量的个数必须相同。注意,这里等号的左侧事实上也是一个被解包的序列,其类型可以被指定但没有必要。比如,下面的写法是可被接受的:
    [s, _] = "Your name".split()

  • A * unpacks the sequence of a list(seq) and passes in each of its individual elements to a function:

    1
    2
    3
    4
    5
    6
    7
    def total(galleons, sickles, knuts):
    return (galleons * 17 + sickles) * 29 + knuts


    coins = [100, 50, 25]

    print(total(*coins), "Knuts")

    被解包后的序列本身没有类型、不能做左右值,不能在程序中独立存在。用于传参时,等价于传入恰好数量的 arguments。

  • 解包 dict 时,会把 key 的内容去引号解析为 parameters, 相当于指定参数名地传参。理所当然地,dict 内部的顺序对结果没有影响。

    1
    2
    3
    4
    5
    6
    7
    def total(galleons, sickles, knuts):
    return (galleons * 17 + sickles) * 29 + knuts


    coins = {"galleons": 100, "sickles": 50, "knuts": 25}

    print(total(**coins), "Knuts")

args and kwargs

  • Recall the prototype for the print function:

    1
    print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
  • We can tell our function to expect a presently unknown number positional arguments. We can also tell it to expect a presently unknown number of keyword arguments. In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
       def f(*args, **kwargs):
    print("Positional:", args)
    print("Named:", kwargs)
    >>> f(100, 50, 25)
    Positional: (100, 50, 30)
    Named: {}
    >>> f(120,4, name='C', tin='UNI')
    Positional: (120, 4)
    Named: {'name': 'C', 'tin': 'UNI'}
  • args are positional arguments, such as those we provide to print like print("Hello", "World"). 类型为 tuple. 接收未指定名称的参数。

  • kwargs are named arguments, or “keyword arguments”, such as those we provide to print like print(end=""). 类型为 dict.

  • 可以使用 kwargs 接收所有被指定 parameter 名的参数,也可以在函数定义处指明你关心的特定名称的参数。

  • In the text editor window, code as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def main():
    yell("This", "is", "CS50")


    def yell(*words):
    uppercased = []
    for word in words:
    uppercased.append(word.upper())
    print(*uppercased)


    if __name__ == "__main__":
    main()

    Notice how *words allows for many arguments to be taken by the function.

map

  • Hints of functional programming: where functions have side effects without a return value.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def main():
    yell("This is CS50")


    def yell(word):
    print(word.upper())


    if __name__ == "__main__":
    main()

    Notice how the yell function is simply yelled.

  • map(func, iterable) 将 iterable 的每一项用 func 作用后,返回这个整体。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def main():
    yell("This", "is", "CS50")


    def yell(*words):
    uppercased = map(str.upper, words)
    print(*uppercased)


    if __name__ == "__main__":
    main()

List Comprehensions

  • List comprehensions allow you to create a list on the fly in one elegant one-liner.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    ]

    gryffindors = [
    student["name"] for student in students if student["house"] == "Gryffindor"
    ]

    for gryffindor in sorted(gryffindors):
    print(gryffindor)

Dictionary Comprehensions

  • Notice how the dictionary will be constructed with key-value pairs.

    1
    2
    3
    4
    5
    students = ["Hermione", "Harry", "Ron"]

    gryffindors = {student: "Gryffindor" for student in students}

    print(gryffindors)

filter

  • filter(functioniterable) 将 iterable 的每一项判断 func 作用后是否为真,返回结果为真的相应值的整体。

  • filter can also use lambda functions as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    students = [
    {"name": "Hermione", "house": "Gryffindor"},
    {"name": "Harry", "house": "Gryffindor"},
    {"name": "Ron", "house": "Gryffindor"},
    {"name": "Draco", "house": "Slytherin"},
    ]


    gryffindors = filter(lambda s: s["house"] == "Gryffindor", students)

    for gryffindor in sorted(gryffindors, key=lambda s: s["name"]):
    print(gryffindor["name"])

enumerate

  • We may wish to provide some ranking of each student. In the text editor window, code as follows:

    1
    2
    3
    4
    students = ["Hermione", "Harry", "Ron"]

    for i in range(len(students)):
    print(i + 1, students[i])

    Notice how each student is enumerated when running this code.

  • Utilizing enumeration, we can do the same:

    1
    2
    3
    4
    students = ["Hermione", "Harry", "Ron"]

    for i, student in enumerate(students):
    print(i + 1, student)

    Notice how enumerate presents the index and the value of each student.

  • You can learn more in Python’s documentation of enumerate.

Generators and Iterators

  • 当需要生成一个 iterable 并对其每一项操作时,在生成函数中使用 yield 使得 iterable 每生成一项便传入相应操作中处理。一次操作完成后,生成器再生成第二项传入,以此类推,以避免 iterable 的体积过大。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def main():
    n = int(input("What's n? "))
    for s in sheep(n):
    print(s)


    def sheep(n):
    for i in range(n):
    yield "🐑" * i


    if __name__ == "__main__":
    main()

    Notice how yield provides only one value at a time while the for loop keeps working.

UCB CS188 课程笔记

对于像我一样英语水平不高的,建议看 slides 而不是 notes。

多元函数

一个有意思的问题:

函数 $f(x,y)$ 在区域 $D$ 上对 $x$,对 $y$ 均连续,则 $\forall (x_0,y_0) \in D$,是否会有
$$
|f(x,y)-f(x_0,y_0)|
\leq|f(x,y)-f(x,y_0)|+|f(x,y_0)-f(x_0,y_0)| \rightarrow 0(?)
$$
其中 $(x,y)$ 属于 $(x_0,y_0)$ 的某个邻域。

我们可以举出反例来证明上面的结论错误,如 $f(x,y)=\cfrac{2xy}{x^2+y^2}$,补充定义 $f(0,0)=0$。请指出 $(?)$ 式推导的错误之处。

多元函数的邻域