红魔咖啡馆

头发越掉越多,头发越掉越少

0%

Generators

Generator Functions

  • 生成器函数是用来生成(yields)值而非返回值的函数
  • 生成器函数使用yield关键字返回生成的值
  • 生成器可以多次生成值,而普通函数只能返回一次值
  • 生成器是调用生成器函数时自动创建的迭代器
  • 调用生成器函数,它会返回一个生成器来迭代该函数生成的值
  • 当函数执行到yield时,生成的值将会作为下次迭代器的数值,而此时执行在yield处执行,但会记住当前环境,以便下次继续执行 e.g.
1
2
3
4
5
6
7
8
9
10
def evens(start, end):
    even = start + (start%2)
    while even<end:
        yield even
        even+=2

if __name__ == "__main__":
    t = evens(2,10)
    for i in t:
        print(i)
2
4
6
8

Generators & Iterators

yield from语句 yield from语句允许从一个迭代器或可迭代变量中生成所有值 两个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def countdown(k):
    if k>0:
        yield k
        yield from countdown(k-1)
        '''
        等价于 
        for i in countdown(k-1):
            yield i
        '''
    else:
        yield 'Blast off'
if __name__ == "__main__":
    t = countdown(3)
    for i in t:
        print(i)
3
2
1
Blast of
1
2
3
4
5
6
7
8
9
10
11
def prefixes(s):
    if s:
        yield from prefixes(s[:-1])
        yield s
def substrings(s):
    if s:
        yield from prefixes(s)
        yield from substrings(s[1:])
if __name__ == "__main__":
    print(list(prefixes("both")))
    print(list(substrings("top")))
['b', 'bo', 'bot', 'both']
['t', 'to', 'top', 'o', 'op', 'p']

Example: Partitions

详见递归时的数字分割例子 使用列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
def partitions(n,m):
    if n<0 or m==0:
        return []
    else:
        exact_match = []
        if n==m:
            exact_match = [str(m)]
        with_m = [p+'+'+str(m) for p in partitions(n-m,m)]
        without_m = partitions(n,m-1)
        return exact_match+with_m+without_m
if __name__ == "__main__":
    for p in partitions(6,4):
        print(p)
2+4
1+1+4
3+3
1+2+3
1+1+1+3
2+2+2
1+1+2+2
1+1+1+1+2
1+1+1+1+1+1

使用生成器: 使代码更优雅,而且可以输出指定个数的可能结果

1
2
3
4
5
6
7
8
9
10
11
def partitions(n,m):
    if n>0 and m>0:
        if n==m:
            yield str(m)
        for p in partitions(n-m,m):
            yield p+'+'+str(m)
        yield from partitions(n,m-1)
if __name__ == "__main__":
    t = partitions(6,4)
    for p in range(5):
        print(next(t))
2+4
1+1+4
3+3
1+2+3
1+1+1+3

Iterators

Iterators

迭代器是一种常见的接口,python中常用作一种访问不同容器元素的方式 - 容器可以提供一个迭代器,以按照某种顺序访问容器内元素 - iter(iterable):创建迭代器,接受任何可迭代的东西,返回可迭代元素的迭代器 - next(iterator):推进迭代器,返回迭代器的下一个元素 - 不同迭代器可以迭代相同值,但它们彼此之间是独立的 - 使用list,tuple或sorted函数可以查看一个迭代器的剩余元素 - 当迭代器到达末尾,Python会返回一个StopIteration的异常 - 所有迭代器都是可变对象

1
2
3
4
5
6
7
8
s = [[1,2],3,4,5]
t = iter(s)
u = iter(s)
print("t:",next(t))
print("u:",next(u))
print("t:",next(t))
list(t)
print(next(t))
t: [1, 2]
u: [1, 2]
t: 3

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)

Cell In[4], line 8
      6 print("t:",next(t))
      7 list(t)
----> 8 print(next(t))
StopIteration: 

Dictionary Itertion

字典的键,值以及键值对都是可遍历的 ,可以生成迭代器 字典中的键值对顺序取决于它们的添加顺序(Python3.6+) 迭代各项目的方法如下

1
2
3
4
5
6
7
8
9
10
11
d = {'one':1, 'two':2, 'three':3}
d['zero'] = 0
# 遍历键
k = iter(d.keys()) # iter(d)
print(next(k))
# 遍历值
v = iter(d.values())
print(next(v))
# 遍历键值对
i = iter(d.items()) # 以元组形式迭代
print(next(i))
one
1
('one', 1)

注意:若在迭代器创建后,字典发生了结构上的改变(增加,减少元素等),迭代器会失效。若只是改变键对应的值则不会。

For Statement

for循环可以遍历迭代器本身,可以从迭代器当前位置遍历到迭代器末尾,但这会推进迭代器,故不能重复使用 而可迭代对象可以从头到尾遍历多次

1
2
3
4
5
6
r = range(3,6)
ri = iter(r)
for i in ri:
    print(i)
for i in ri:
    print(i)
3
4
5

Built-in Function for Iteration

许多python内置的序列操作使用迭代器作为返回值,以惰性方式计算 惰性计算意味着只有被请求时才计算结果 e.g. map(func, iterable):接受一个函数与一个可迭代对象,将该函数应用于可迭代对象中的每一个元素,返回一个迭代器遍历可迭代对象中所有x在func下的值 filter(func, iterable):接受一个断言函数与一个可迭代对象,返回一个迭代器遍历可迭代对象中所有在func下为真的x zip(first_iter, second_iter):接受两个可迭代对象,返回迭代器遍历相同索引的(x,y)对 reversed(sequence):接受一个序列,返回迭代器反向遍历该序列

1
2
3
4
5
bcd = ['b', 'c', 'd']
m = map(lambda x: x.upper(), bcd) # 返回一个迭代器
print(next(m))
print(next(m))
print(next(m))
B
C
D
1
2
3
4
5
6
7
def double(x):
    print(x, '=>',2*x)
    return 2*x
m = map(double, range(3,7))
f = lambda y: y>=10
t = filter(f,m) # 仅遍历满足f函数下的返回值
list(filter(f,m)) # 将所有可能的返回值存入列表
3 => 6
4 => 8
5 => 10
6 => 12





[10, 12]

Zip

zip函数返回迭代器,遍历相同索引的值组成的元组 若一个可迭代对象比另一个长,zip会跳过多余的 zip可以接受多个可迭代对象

1
2
3
4
5
print(list(zip([1,2],[3,4])))

print(list(zip([1,2],[3,4,5])))

print(list(zip([1,2],[3,4,5], [6,7])))
[(1, 3), (2, 4)]
[(1, 3), (2, 4)]
[(1, 3, 6), (2, 4, 7)]

e.g. 检测任意一个序列是否为回文序列,使用zip

1
2
3
4
5
6
7
8
9
10
11
def palindrome(s):
    # >>> palindrome([3,1,4,1,5])
    # False
    # >>> palindrome([3,1,4,1,3])
    # True
    # >>> palindrome('seveneves')
    # True
    return all([a==b for a,b in zip(s,reversed(s))])
if __name__ == '__main__':
    print(palindrome([3,1,4,1,3]))
    print(palindrome('seveneves'))
True
True

Using Iterators

使用迭代器的代码对数据本身更改不大 - 当数据表示形式改变时,使用迭代器可以不必重写代码 - 其他人更可能在他们的数据上使用你的代码 迭代器将序列中的元素与所在位置绑定 - 将对象传递给其他函数时始终保留着位置 - 可以确保序列中的每个元素只执行一次 - 传递迭代器限制了对序列执行的操作,即只能请求下一个值

Web应用的请求与响应机制

HTTP协议

HTTP是web通信的基础协议,是客户端与服务器进行交互的标准,所有www文件必须遵守这个标准,其端口号为80

HTTPS是HTTP的安全版,在HTTP下加入SSL层 SSL是主要用于web的安全传输协议,在传输层对网络连接进行加密,其端口号为443

工作原理

HTTP通信由两部分组成:客户端请求信息、服务器响应信息 - 当用户在地址栏输入一个URL并回车后,客户端向web服务器发送HTTP请求,建立一个到服务器指定端口的TCP连接 - 服务器在那个端口监听客户端发送过来的请求,收到请求后,服务器根据内容与类型进行处理,并生成HTTP响应,包括一个状态行与相应的信息 - 客户端接受服务器返回的响应,并根据内容呈现给用户

URL

统一资源定位符(Uniform / Universal Resource Locator),是用于完整描述网页和其他资源的地址的一种标识方法 基本格式: scheme://host[:port#]/path/…/[?query-string][#anchor] - scheme:协议(如http与https) - host:服务器ip地址或域名 - port:服务器端口(如果走协议默认端口,缺省端口80) - path:访问资源的路径 - query-string:参数,发送给http服务器的数据 - anchor:锚,跳转到网页的指定锚点位置

HTTP请求

HTTP用来提交和获取资源,客户端发送一个HTTP请求请求到服务器的请求信息,包括:请求行、请求头部、空行、请求数据四个部分,格式如下

HTTP请求

一些常用的请求报头: - Host:对应URL中的web名称与端口号,用于指定被请求资源的Internet主机与端口号 - Connection:表示客户端与服务器连接类型 Client 发起一个包含 Connection:keep-alive 的请求, HTTP/1.1 使用 keep-alive 为默认值。 Server 收到请求后:如果 Server 支持 keep-alive(长连接), 回复一个包含 Connection:keep-alive 的响应, 不关闭连接;如果 Server 不支持 keep-alive, 回复一个包含 Connection:close 的响应, 关闭连接。如果 client 收到包含 Connection:keep-alive 的响应, 向同一个连接发送下一个请求, 直到一方主动关闭连接。 keep-alive 在很多情况下能够重用连接, 减少资源消耗, 缩短响应时间, 比如当浏览器需要多个文件时(比如一个 HTML 文件和相关的图形文件), 不需要每次都去请求建立连接。 - Upgrade-Insecure-Requests:升级不安全的请求, 意思是会在加载 http 资源时自动替换成 https 请求, 让浏览器不再显示 https 页面中的 http 请求警报。 - User-Agent:客户浏览器的详细信息,服务器根据这条信息来判断来访用户是否为真实用户 - Accept:指浏览器或其他客户端可以接受的MIME(Multipurpose Internet Mail Extensions)文件类型,服务器根据其判断并返回适当文件格式 - Accept: /: 表示什么都可以接收。 - Accept: text/html, application/xhtml+xml;q=0.9, image/;q=0.8: 表示浏览器支持的 MIME 类型分别是html文本、xhtml和xml文档、 所有的图像格式资源。 q是权重系数, 范围 \(0 =< q <= 1\),q值越大,请求越倾向于获得其“;”之前的类型表示的内容。若没有指定 q 值, 则默认为1,按从左到右排序顺序;若被赋值为 0,则用于表示浏览器不接受此内容类型。 Text:用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的; Application:用于传输应用程序数据或者二进制数据。 - Referer:表明产生请求的网页来自于哪个URL,用户是从该referer页面访问到当前请求的页面,可以用来跟踪web请求来自哪个页面,哪个网站 - Accept-Encoding:指出浏览器可以接受的编码方式。 编码方式不同于文件格式, 它是为了压缩文件并加速文件传递速度。 浏览器在接收到 Web 响应之后先解码, 然后再检查文件格式, 许多情形下这可以减少大量的下载时间。 如Accept-Encoding:gzip;q=1.0, identity; q=0.5, ;q=0 如果有多个 Encoding 同时匹配, 按照q值顺序排列,本例中按顺序支持 gzip, identity压缩编码, 支持gzip的浏览器会返回经过gzip编码的HTML页面。 如果请求消息中没有设置这个域服务器假定客户端对各种内容编码都可以接受。 - Accept-Language:指语言可以接受的语言种类,如zh或zh-cn指中文 - Accept-Charset:指浏览器可以接受的字符编码,缺省为任何字符集 - Cookie:浏览器用这个属性向服务器发送 Cookie。 - Content-Type:POST请求里用来表示的内容类型。

示例:

HTTP请求示例

常见HTTP方法

  • GET:请求指定资源,通常附加在URL中,不适合传递大量数据与隐私数据

  • POST:向服务器提交数据,通常用于提交表单与上传文件

  • PUT:更新指定资源内容

  • HEAD:只返回响应头,不返回响应体

  • OPTIONS:查询服务器支持的HTTP方法

HTTP响应

由四部分组成,分别为状态行、消息报头、空行、响应正文,格式如下:

HTTP响应

理论上所有响应头信息都是回应请求头的,但还会添加对应的响应头信息

示例:

HTTP响应示例

HTTP状态码

web服务器对HTTP请求的回应,表示请求是否成功处理

  • 1xx:服务器成功接受部分请求
  • 2xx:成功(200 OK)
  • 3xx:重定向(301 Moved Permanently)
  • 4xx:客户端错误(404 Not Found)
  • 5xx:服务器错误(500 Internal Server Error)

Cookie与Session:

服务器和客户端的交互仅限于请求/响应过程,结束之后便断开,在下一次请求时,服务器会认为新的客户端。为了维护他们之间的链接,让服务器知道这是前一个用户发送的请求,必须在一个地方保存客户端的信息。 - Cookie:通过在 客户端 记录的信息确定用户的身份。 - Session:通过在 服务器端 记录的信息确定用户的身份

请求参数的获取与处理

HTTP携带多种数据,这里通过python,使用urllib与requests库来处理这些数据

处理URL路径与查询参数

​ URL 查询参数是指在 URL 中通过键值对的形式传递的参数,用于在 URL 中增加额外的信息,如查询条件、排序方式、页码等。查询参数通常使用键值对的形式定义,键和值之间用 = 符号分隔,多个参数之间用 & 符号分隔。例如,定义一个查询参数 page 的值为 2,可以写成 page=2

​ 查询参数的主要用途是通过 URL 传递信息,实现搜索查询、过滤器等功能。例如,用户在输入框输入 abc,按下回车之后,会返回一条地址为 https://www.example.com/?keyword=abc 的 URL。在用户访问时,服务器会通过 URL 参数 keyword 检索相关内容,然后返回给用户

​ 在python中,可以使用urllib库中urllib.parse模块中的parse_qs函数来解析查询字符串,以下为示例程序,程序返回字典,键值对分别为等号左右的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from urllib.parse import urlparse, parse_qs

# 示例URL
url = 'http://example.com/path/to/resource?name=John&age=30'

# 解析URL
parsed_url = urlparse(url)

# 获取路径
path = parsed_url.path
print("Path:", path)

# 解析查询参数
query_params = parse_qs(parsed_url.query)
print("Query Parameters:", query_params)
Path: /path/to/resource
Query Parameters: {'name': ['John'], 'age': ['30']}

处理表单数据

表单数据是通过网页上的表单收集并提交给服务器的信息,用户可以填写表单来提供各种信息。当用户点击表单上的提交按钮时,表单数据会被发送到服务器,服务器则根据接收到的数据执行相应操作
表单的基本结构如下
1
2
3
4
5
6
7
8
9
<form action="/submit" method="post">
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username"><br><br>
    
    <label for="password">密码:</label>
    <input type="password" id="password" name="password"><br><br>
    
    <input type="submit" value="提交">
</form>

<form>标签定义了一个表单,action属性指定了表单数据提交的目标url,method属性制定了提交数据时使用的HTTP方法 - GET:当method属性被设置为GET时,表单数据会被附加在url后面作为查询参数提交。这种方法适合提交少量的非敏感数据 - POST:当method属性被设置为POST时,表单数据会在请求体中发送,不会出现在url中。这种方法适合提交敏感与大量数据 <input>标签用于创建输入控件,type属性定义了输入控件的类型,name属性为提交属性时的键名,id用于关联<label>标签 <label>标签用于描述每个输入控件的用途 表单数据的编码: - application/x-www-form-urlencoded:这是默认的编码方式,表单数据被编码成键值对的形式,如 username=John&password=Doe。 - multipart/form-data:当表单中包含文件上传字段时,必须使用此编码方式。它允许将文件和其他数据一起发送。

我们可以在python中通过request库发送POST请求来获取表单数据并处理,返回的是html类型的表单数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

# 示例表单数据
form_data = {
    'username': 'john_doe',
    'password': 'securepassword'
}

# 发送POST请求
response = requests.post('https://www.baidu.com/', data=form_data)

# 查看响应
print("Response Status Code:", response.status_code)
print("Response Text:", response.text)

处理JSON数据

python中可以使用request库来处理json数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import json

# 示例JSON数据
json_data = {
    'name': 'John',
    'age': 30
}

# 发送POST请求
response = requests.post('https://www.baidu.com/', json=json_data)

# 查看响应
print("Response Status Code:", response.status_code)
print("Response JSON:", response.json())

处理文件上传

HTTP文件上传是通过HTTP协议将文件从客户端传输到服务器的一种方式。通常使用POST方法,并且数据格式为multipart/form-data。这种格式允许在同一个请求中传输多个字段和文件。
HTTP文件上传中,请求头需要包含Content-Type,其值为multipart/form-data,并指定一个边界(boundary),用于分隔不同的字段和文件。例如
1
2
3
4
5
6
7
8
9
10
POST /upload HTTP/1.1
Host: www.example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarycz5DOEJKqu7XXB7k

------WebKitFormBoundarycz5DOEJKqu7XXB7k
Content-Disposition: form-data; name="file"; filename="example.png"
Content-Type: image/png

<文件内容>
------WebKitFormBoundarycz5DOEJKqu7XXB7k--
我们可以使用python的request库来处理文件上传响应
1
2
3
4
5
6
7
8
9
10
11
import requests

# 打开文件
files = {'file': open('example.txt', 'rb')}

# 发送POST请求
response = requests.post('http://example.com/upload', files=files)

# 查看响应
print("Response Status Code:", response.status_code)
print("Response Text:", response.text)

Mutablity

Objects

  • 对象用于表达信息,是一种包含了数据与行为的数据抽象
  • 有属性的东西都可以作为对象,python中一切皆对象
  • Python中,优先级最高的对象被称为类(class)
  • 面向对象编程(OOP):
    • 对象是OOP的核心
    • OOP使用一种暗喻来组织大型程序
    • 使用一种特殊语法可以提高程序的可读性与组织
  • 很多数据操作都是通过对象实现的
  • 对象可以做许多相关的事情,而函数只能做一件事

e.g. String

字符串是一种表达文本的数据抽象 ### 字符串的表示 目前常见的表示方法是用ASCII与Unicode字符集表示字符 前者包含了控制字符、数字、字母与标点,后者则包含了不同语言中的更多字符

1
2
3
4
from unicodedata import name, lookup
print(name('A')) # 查询字符集中的字符名称
print(lookup('BABY')) # 根据字符名称输出对应字符
print(lookup('BABY').encode()) # 查看该字符的字节编码
LATIN CAPITAL LETTER A
👶
b'\xf0\x9f\x91\xb6'

Mutation Operations

一些对象是可以改变的

1
2
3
4
5
6
7
8
9
10
11
suits = ['coin', 'string', 'myriad']
original_suits = suits
print(suits.pop()) # 弹出一个元素(默认最后一个)
suits.remove('string') # 移除指定元素
print(suits)
suits.append('cup') # 在尾部增加一个元素
suits.extend(['sword', 'club']) # 添加序列中的多个元素来拓展列表
print(suits)
suits[2] = 'spade'
suits[0:2] = ['heart', 'diamond']
print(original_suits)
myriad
['coin']
['coin', 'cup', 'sword', 'club']
['heart', 'diamond', 'spade', 'club']

根据以上代码发现,我们可以对一个对象(suits)进行若干操作来变化其值 而我们在最初将suits与original_suits进行绑定,故变化也会在original_suits中体现 综上,所有指向相同对象的names都会受到Mutation的影响,其中这里的mutation指对象发生的变化 只有可变类型的对象才能更改,如列表与字典

函数调用时发生的Mutation

函数可以更改其作用域中任何可变对象的值

1
2
3
4
5
def mystery(s):
    s.pop()
    s.pop()
four = [1,2,3,4]
mystery(four)

mystery函数实现了对列表对象值的更改,甚至mystery不需要传入参数,直接更改其所在作用域(全局作用域)中four列表的内容

表达式的Mutation

表达式的值会随着names绑定值或对象的改变而改变

1
2
3
4
x = [1,2]
print(x+x)
x.append(3)
print(x+x)
[1, 2, 1, 2]
[1, 2, 3, 1, 2, 3]

Tuples

  • 元组是一种不可变的序列,使用圆括号包裹起来
  • 实际上,任何以逗号隔开的元素都会被解释成元组,非必须加圆括号
  • 使用tuple()创建元组或将其他序列转化为元组
  • 在单个元素后加一个逗号可以将单个元素转化成元组
  • 元组可以相加,也可以使用成员运算符in来判断元素是否存在
  • 由于元组不可变,可以将其作为字典的键使用
  • 若元组中包含了可变对象,则该对象可以被更改
1
2
3
s = ([1,2],3)
s[0][0]=4
print(s)
([4, 2], 3)

Mutation

相同与改变

1
2
3
4
5
a = [10]
b = a
print(a==b)
a.append(20)
print(a==b)
True
True

在这个例子中,我们可以说a与b是相同的,因为b与a绑定到了同一个对象,当对其中一个发生变化,另一个也会同时改变

1
2
3
4
5
a = [10]
b = [10]
print(a==b)
b.append(20)
print(a==b)

在这个例子中,a与b是不同的,尽管它们曾有过相同的内容,但对b进行改变后,a会随之改变,因此这时二者便不同了

Identity Operators

Identity<exp0> is <exp1> 当两个表达式指向相同对象时返回True Equality <exp0> == <exp1> 当两个表达式拥有相同值时返回True

相同对象始终拥有相等的值,但反之不一定成立

1
2
3
4
5
a = [10]
b = [10]
c = b
print(a is b)
print(b is c)
False
True

可变对象在函数中的默认值

函数中声明的默认值是函数值的一部分,而不是每次调用时才生成 这导致了若该对象是可变的,而且在函数中间进行了修改,则该修改会在下次调用函数时保留 如下面的代码,每次调用增加的值会保留在默认值中

1
2
3
4
5
def f(s=[]):
    s.append(3)
    return len(s)
for i in range(3):
    print(f())
1
2
3

Mutable Fuctions

在函数中使用可变对象可以在多次调用时保留上次操作的值

1
2
3
4
5
6
7
8
9
10
11
12
13
def make_withdraw_list(balance):
    b = [balance]
    def withdraw(amount):
        if amount > b[0]:
            return 'Insufficient funds'
        b[0] = b[0] - amount
        return b[0]
    return withdraw

withdraw = make_withdraw_list(100)
print(withdraw(25))
print(withdraw(25))
withdraw(100)
75
50
'Insufficient funds'

该函数将存款存在作为可变对象的列表中 函数中的withdraw函数始终指向列表b并修改其值,该列表总是这个列表,随着时间其中的内容被更改 为了实现每次更改列表中的值,该函数使用了可变对象来创建了一个可变函数 function

Trees

描述树形结构的术语

递归描述 - 一课树有一个根节点和一系列分支节点 - 每个分支也是一棵树,也有根节点与分支节点 - 没有分支节点的树被称为叶子节点

亲戚描述

  • 树的每个位置被称为节点
  • 每个节点可以表示任何值
  • 一个节点可以称为另一个节点的父节点/子节点 trees

实现树形结构的抽象

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
26
27
28
29
30
31
def tree(label, branches=[]):
    for branch in branches:
        assert is_tree(branch), "branches must be trees" # 确保构造的是一棵树
    return [label] +list(branches) # 将同一层的分支节点放在一个列表中

def label(tree):
    return tree[0]

def branches(tree):
    return tree[1:]

def is_tree(tree):
    """判断是不是一棵树"""
    if type(tree) != list or len(tree) <1: # 确保树的分支是树,以及存在一个值
        return False
    for branch in branches(tree): # 确保树的分支的分支都是树
        if not is_tree(branch):
            return False
    return True

def is_leaf(tree): 
    """判断树本身是不是叶子节点"""
    return not branches(tree)


if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(t)
    print(label(t))
    print(branches(t))
    print(label(branches(t)[0]))
[1, [5, [7]], [6]]
1
[[5, [7]], [6]]
5

Tree Processing

处理叶子节点

使用递归,将会在每个分支节点进行递归调用,最后合并

1
2
3
4
5
6
7
8
9
10
def count_leaves(t):
    """Count the leaves of tree T"""
    if is_leaf(t):
        return 1
    else:
        return sum([count_leaves(b) for b in branches(t)])

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(count_leaves(t))
2

返回叶子节点的值

实现leaves函数,返回树的叶子节点的值的列表

1
2
3
4
5
6
7
8
9
10
def leaves(tree):
    """return a list containing the leaf labels of tree"""
    if is_leaf(tree):
        return [label(tree)]
    else:
        return sum([leaves(b) for b in branches(tree)], [])

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(leaves(t))
[7, 6]

根据树创建树

使用递归根据另一棵树创建一颗新树 如让叶子节点值+1的树 或让所有节点都+1的树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def increment_leaves(t):
    """return a tree like t but with leaf labels incremented"""
    if is_leaf(t):
        return tree(label(t)+1)
    else:
        bs = [increment_leaves(b) for b in branches(t)]
        return tree(label(t), bs)

def increment(t):
    """return a tree like t but with all labels incremented"""
    return tree(label(t)+1, [increment(b) for b in branches(t)])

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(leaves(t))
    print(increment_leaves(t))
    print(increment(t))
[7, 6]
[1, [5, [8]], [7]]
[2, [6, [8]], [7]]

例:print_tree

按节点在树中的深度缩进打印一颗树

1
2
3
4
5
6
7
8
def print_tree(t, indent = 0):
    print(' '*indent + str(label(t)))
    for b in branches(t):
        print_tree(b, indent+1)

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(print_tree(t))
1
 5
  7
 6
None

例: 求从根节点沿路径到叶子节点求和并打印

1
2
3
4
5
6
7
8
9
10
11
def print_sums(t, so_far):
    so_far = so_far +label(t)
    if is_leaf(t):
        print(so_far)
    else:
        for b in branches(t):
            print_sums(b,so_far)

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(print_sums(t, 0))
13
7
None

Dictionaries 部分跳过

Divide

传入一组商数与一组除数,返回字典,键为每个商数,值为表示每个商数能整除的除数的列表

1
2
3
4
5
6
7
8
9
10
def divide(quotients, divisors):
    """Return a dictonary in which each quotient q is a key for the list of
    divisors that it divides evenly.

    >>> divide([3, 4, 5], [8, 9, 10, 11, 12])
    {3: [9, 12], 4: [8, 12], 5: [10]}
    >>> divide(range(1, 5), range(20, 25))
    {1: [20, 21, 22, 23, 24], 2: [20, 22, 24], 3: [21, 24], 4: [20, 24]}
    """
    return {i: [x for x in divisors if x%i==0] for i in quotients}

注意用表达式建立列表与字典的方法

Buying Fruit

实现buy函数,通过给定的水果与价格,用恰好为传入的total_amount的价格购买指定的水果(每种指定水果至少买一次) 用display函数输出所有结果

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
26
27
28
29
30
31
32
33
34
35
36
def buy(required_fruits, prices, total_amount):
    """Print ways to buy some of each fruit so that the sum of prices is amount.

    >>> prices = {'oranges': 4, 'apples': 3, 'bananas': 2, 'kiwis': 9}
    >>> buy(['apples', 'oranges', 'bananas'], prices, 12)
    [2 apples][1 orange][1 banana]
    >>> buy(['apples', 'oranges', 'bananas'], prices, 16)
    [2 apples][1 orange][3 bananas]
    [2 apples][2 oranges][1 banana]
    >>> buy(['apples', 'kiwis'], prices, 36)
    [3 apples][3 kiwis]
    [6 apples][2 kiwis]
    [9 apples][1 kiwi]
    """
    def add(fruits, amount, cart):
        if fruits == [] and amount == 0:
            print(cart)
        elif fruits and amount > 0:
            fruit = fruits[0]
            price = prices[fruit]
            for k in range(1,amount//price+1):
                add(fruits[1:], amount-k*price, cart+display(fruit,k))
    add(required_fruits, total_amount, '')

def display(fruit, count):
    """Display a count of a fruit in square brackets.

    >>> display('apples', 3)
    '[3 apples]'
    >>> display('apples', 1)
    '[1 apple]'
    """
    assert count >= 1 and fruit[-1] == 's'
    if count == 1:
        fruit = fruit[:-1]  # get rid of the plural s
    return '[' + str(count) + ' ' + fruit + ']'

返回条件是fruits为空或钱被花光,且递归时每次都取fruits的首个元素 可以推断递归时对fruits数组进行了切片,每次往后切一个元素 for循环遍历选择每个水果的个数,从至少选一个到最多能选的个数\(\frac{amount}{price}+1\) 递归时传入总价格减去已经使用的价格数的不同情况,并使用display函数进行字符串拼接来显示

Cities ADT

以下问题基于建立的该ADT 一个城市由以下参数描述:名称、经度、维度 包括一个构造函数 - make_city(name, lat, lon):建立一个城市对象,存储其名称、经度、维度 以下选择器 - get_name(city):获取城市名称 - get_lat(city):获取城市经度 - get_lon(city):获取城市维度 该抽象数据类型已经在文件中实现,你不需要知道是怎么实现的

Distance

计算并返回两城市的距离

1
2
3
4
5
6
7
8
9
10
11
12
13
from math import sqrt
def distance(city_a, city_b):
    """
    >>> city_a = make_city('city_a', 0, 1)
    >>> city_b = make_city('city_b', 0, 2)
    >>> distance(city_a, city_b)
    1.0
    >>> city_c = make_city('city_c', 6.5, 12)
    >>> city_d = make_city('city_d', 2.5, 15)
    >>> distance(city_c, city_d)
    5.0
    """
    return sqrt(abs(get_lat(city_a)-get_lat(city_b))**2+abs(get_lon(city_a)-get_lon(city_b))**2)

Closer City

比较两个城市离给定经纬度的远近,返回更近的那个城市

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def closer_city(lat, lon, city_a, city_b):
    """
    Returns the name of either city_a or city_b, whichever is closest to
    coordinate (lat, lon). If the two cities are the same distance away
    from the coordinate, consider city_b to be the closer city.

    >>> berkeley = make_city('Berkeley', 37.87, 112.26)
    >>> stanford = make_city('Stanford', 34.05, 118.25)
    >>> closer_city(38.33, 121.44, berkeley, stanford)
    'Stanford'
    >>> bucharest = make_city('Bucharest', 44.43, 26.10)
    >>> vienna = make_city('Vienna', 48.20, 16.37)
    >>> closer_city(41.29, 174.78, bucharest, vienna)
    'Bucharest'
    """
    fake_city = make_city('fake', lat, lon)
    if distance(fake_city,city_a)<distance(fake_city,city_b):
        return get_name(city_a)
    else:
        return get_name(city_b)

可以将指定的经纬度看作第三个城市并构造这么一个对象 通过上面已经写完的distance函数计算两城市与第三个城市的距离并比较,输出更近的 若相同输出city_b

Data Abstraction

Data Abstraction

大多数值都是复合值,由多种对象组成

抽象数据类型可以让我们将符合对象作为一个单元操作,允许我们隔离使用数据的程序的两个部分:

  • 数据的表示
  • 数据的操作

即在数据的表示与操作之间建立一个抽象屏障

Example——Rational Numbers

表示

任何有理数可以表示为一个最简分数

这为小数提供了更精确的表示方法

故我们需要分开分子分母,作为一个复合数据类型:

  • rational(n,d)返回一个有理数x
  • numer(x)返回有理数x的分子
  • denom(x)返回有理数的分母

第一个函数称为构造函数(constructor),它用于构造一个新值作为抽象数据类型的实例(instance)

第二、三个函数称为选择器(selectors),它们返回得到的有理数的整数部分

这三个函数作为有理数的抽象数据类型使用,用它们进行数字操作

算术

根据小学二年级知识,我们根据分数来执行有理数加乘,公式如下

有理数算术
1
2
3
4
5
6
7
8
9
10
def mul_rational(x,y):
    return rational(numer(x)*numer(y),denom(x)*denom(y))

def add_rational(x,y):
    nx, dx = numer(x), denom(y)
    ny, dy = numer(y), denom(y)
    return rational(nx*dy+ny*dx, dx*dy)

def equal_rational(x,y):
    return numer(x)*denom(y)==numer(y)*denom(x)

Abstraction Barriers

拿上方的有理数函数作为例子

抽象屏障

抽象屏障表示使用由有理数计算时使用的函数只能是对应相关的,而不能越界表示

  • 用有理数计算时只需要使用计算相关函数
  • 用来表示有理数和进行操作时只需要使用对应构造函数与选择器
  • 用来构建构造函数与选择器时需要用到列表与元素选择

一种跨越了抽象屏障的反例:

1
2
3
4
add_rational([1,2],[1,4])

def divide_rational(x,y):
    return [x[0]*y[1], x[1]*y[0]]

Pair

创建与访问

通常使用列表表示一个pair

1
2
3
>>> pair = [1,2]
>>> pair
[1,2]

通过序列解包或列表下标获得pair中的两个值

1
2
3
4
5
6
7
>>> x,y=pair
>>> x
1
>>> y
2
>>> pair[0]
1

还可以用getitem函数获得值,在operator库中

用法:getitem(<list>, <index>)

1
2
3
4
>>> getitem(pair, 0)
1
>>> getitem(pair, 1)
2

用pair表示有理数

1
2
3
def rational(n,d):
    """Construct a rational number that represents N/D"""
    return [n, d]

返回一个列表,记录分子分母用来表示有理数

1
2
3
4
5
6
7
def numer(x):
    """Return the numerator of rational number x"""
    return x[0]

def denom(x):
    """Return the denominator of rational number x"""
    return x[1]

通过访问列表中的元素返回分子分母

应用

分数通分

有理数的分子分母应互质,故我们可以用gcd获取它们的最大公因数来同时除以分子分母以确保获得互质的分子分母

1
2
3
4
from fractions import gcd
def rational(n,d):
    g = gcd(n,d)
    return [n//g, d//g]

Data Representation

数据抽象的基本思想:通过它的行为来识别该抽象数据类型

改变有理数的表现形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def rational(n,d):
    def select(name):
        if name == 'n':
            return n
        elif name == 'd':
            return d
    return select
def numer(x):
    """Return the numerator of rational number x"""
    return x('n')

def denom(x):
    """Return the denominator of rational number x"""
    return x('d')

这里使x成为一个函数,这样就可以不需要内置的列表数据类型了

此时rational是一个高阶函数,返回一个表示有理数的函数