写在前面:待完善(补图以及还有一些hw,lab,proj未做)

2.1 引言

2.1.1 对象隐喻

即使我们还不能精确描述对象如何工作,我们还是可以开始将数据看做对象,因为 Python 中万物皆对象。

2.1.2 原始数据类型(Native Data Types)

Python 中每个对象都拥有一个类型。type函数可以让我们查看对象的类型:

>>> type(today)
<class 'datetime.date'>

Python包含了三种原始数值类型:整数(int)、实数(float)和复数(complex)。

2.2 数据抽象

2.2.1 有理数的算术

有理数通常可表示为整数的比值(即:分数):<numerator>/<denominator>

用分子和分母构造有理数:

  • 构造器:make_rat(n, d) 返回分子为n和分母为d的有理数
  • 选择器
    numer(x) 返回有理数x的分子
    denom(x) 返回有理数x的分母

构造函数来实现有理数的加减乘除:

>>> def add_rat(x, y):
        nx, dx = numer(x), denom(x)
        ny, dy = numer(y), denom(y)
        return make_rat(nx * dy + ny * dx, dx * dy)
>>> def mul_rat(x, y):
        return make_rat(numer(x) * numer(y), denom(x) * denom(y))
>>> def eq_rat(x, y):
        return numer(x) * denom(y) == numer(y) * denom(x)

2.2.2 元组

元组的构造:

>>> pair = (1, 2)
>>> pair
(1, 2)

元组的解构:

  • 法一:多重赋值
>>> x, y = pair
>>> x
1
>>> y
2
  • 法二:通过下标运算符
>>> pair[0]
1
>>> pair[1]
2
  • 法三:通过内置函数
>>> from operator import getitem
>>> getitem(pair, 0)
1

通过二元组实现有理数构造器和选择器函数:

>>> from fractions import gcd

>>> def make_rat(n, d):
        g = gcd(n, d) #gcd用于求n和d的最大公约数
        return (n//g, d//g)
>>> def numer(x):
        return getitem(x, 0)
>>> def denom(x):
        return getitem(x, 1)

2.2.3 抽象界限

我们可以将有理数系统想象为一系列层级:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4v4BQm6s-1633510173212)(image/2021-09-20-12-28-49.png)]

平行线表示隔离系统不同层级的界限。该图表明了:利用元组实现有理数;有理数利用make_rat,numerdenom实现解构,获得分子和分母;分子和分母利用add_rat,mul_rat,eq_rat实现运算。

这样,抽象界限可以表现为一系列函数。

2.2.4 数据属性

通常,我们可以将抽象数据类型当做一些选择器和构造器的集合,并带有一些行为条件。只要满足了行为条件,这些函数就组成了数据类型的有效表示。

二元组通常叫做偶对。

2.3 序列

序列不是特定的抽象数据类型,而是不同类型共有的一组行为。序列都有一定的属性,比如:长度、元素选择(即:利用下标提取元素)。

2.3.1 嵌套偶对

元组可以嵌套。例子:每个偶对的元素本身也可以是偶对:

>>> ((1, 2), (3, 4))
((1, 2), (3, 4))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T9vD066p-1633510173215)(image/2021-09-20-12-53-17.png)]

  • 封闭性
  • 我们将这种将元组以这种方式嵌套的能力叫做元组数据类型的封闭性。
  • 通常,如果组合结果自己可以使用相同的方式组合,组合数据值的方式就满足封闭性。

2.3.2 递归列表

构造递归列表rlist

>>> empty_rlist = None
>>> def make_rlist(first, rest):
        """Make a recursive list from its first element and the rest."""
        return (first, rest)
>>> def first(s):
        """Return the first element of a recursive list s."""
        return s[0]
>>> def rest(s):
        """Return the rest of the elements of a recursive list s."""
        return s[1]
>>> counts = make_rlist(1, make_rlist(2, make_rlist(3, make_rlist(4, empty_rlist))))
>>> first(counts)
1
>>> rest(counts)
(2, (3, (4, None)))

对递归列表实现长度和元素选择:

>>> def len_rlist(s):
        """Return the length of recursive list s."""
        length = 0
        while s != empty_rlist:
            s, length = rest(s), length + 1
        return length
>>> def getitem_rlist(s, i):
        """Return the element at index i of recursive list s."""
        while i > 0:
            s, i = rest(s), i - 1
        return first(s)
>>> len_rlist(counts)
4
>>> getitem_rlist(counts, 1)  # The second item has index 1
2

2.3.2 元组II

  • 元组的基本操作:
>>> digits = (1, 8, 2, 8)
>>> len(digits) # 返回元组的长度
4
>>> digits[3] # 元组的元素选择
8
>>> (2, 7) + digits * 2 # 元组的加法和乘法
(2, 7, 1, 8, 2, 8, 1, 8, 2, 8)
  • map()函数
    map()会根据提供的函数对指定序列做映射。

语法:map(function, iterable, …)

map的结果是一个本身不是序列的对象,但是可以通过调用序列构造函数(如:tuple(),list()等来转换为序列。

>>> alternates = (-1, 2, -3, 4, -5)
>>> tuple(map(abs, alternates))
(1, 2, 3, 4, 5)
>>> def square(x) :         # 计算平方数
...     return x ** 2
...
>>> map(square, [1,2,3,4,5])    # 计算列表各个元素的平方
<map object at 0x100d3d550>     # 返回迭代器
>>> list(map(square, [1,2,3,4,5]))   # 使用 list() 转换为列表
[1, 4, 9, 16, 25]
>>> list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))   # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]

2.3.4 序列迭代

Python中的for语句:
若s为一个序列,则遍历其中的元素可以用for elem in s

for语句可在头部中包含多个名称,将每个元素序列“解构”为各个元素:

>>> pairs = ((1, 2), (2, 2), (2, 3), (4, 4))
>>> for x, y in pairs:
        if x == y:
            same_count = same_count + 1
>>> same_count
2

这个绑定多个名称到定长序列中多个值的模式,叫做序列解构。

range

>>> tuple(range(5, 8)) # 开头开始,结尾减一
(5, 6, 7)

>>> tuple(range(3)) # 从零开始,结尾减一
(0, 1, 2)

常见的惯例是将单下划线字符用于for头部,如果这个名称在语句组中不会使用

>>> for _ in range(3):
        print('Go Bears!')

Go Bears!
Go Bears!
Go Bears!

要注意对解释器来说,下划线只是另一个名称,但是在程序员中具有固定含义,它表明这个名称不应出现在任何表达式中。(也就是说_也不能出现在for从句中)

2.3.5 序列抽象

Python 还包含了两种序列类型的行为,它们扩展了序列抽象。

  • 成员性 —— innot in
>>> digits
(1, 8, 2, 8)
>>> 2 in digits
True
>>> 1828 not in digits
True

所有序列都有叫做indexcount的方法,前者返回序列中某个值第一次出现时的下标,后者返回序列中某个值出现的次数。

>>> digits
(1, 8, 2, 8)
>>> digits.index(8)
1
>>> digits.count(8)
2
  • 切片
>>> digits[0:2]
(1, 8)
>>> digits[1:]
(8, 2, 8)

列表切片

2.3.6 字符串

字符串的构造器是str

字符串也是一种序列,有长度和元素选择。

Python中的字符串可以表达任意文本,被单引号或双引号包围。
不像许多其它编程语言那样,Python 没有单独的字符类型,任何文本都是字符串,表示单一字符的字符串长度为1。

>>> city = 'Berkeley'
>>> len(city) # 长度
8

>>> city[3] # 元素选择
'k'

>>> city + city # 加法和乘法
>>> 'BerkeleyBerkeley'
>>> city * 2
>>> 'BerkeleyBerkeley'

>>> 'here' in "Where's Waldo?" # 成员性
True
>>> 'c' in 'city'
>>> True
>>> 'c' in 'ci ty'
>>> True

>>> 'Mississippi'.count('i') # 注意`count`统计字符串中非重叠字串的出现次数。
4
>>> 'Mississippi'.count('issi')
1

>>> 'Mississippi'.index('i')
1

Python中的多行文本要用三个引号。

注意:\n虽然表示为两个字符,但它在长度和元素选择上被认为是单个字符。

字符串强制。 字符串可以从 Python 的任何对象通过以某个对象值作为参数调用str构造函数来创建,这个字符串的特性对于从多种类型的对象中构造描述性字符串非常实用。

字符串的一些方法:

>>> '1234'.isnumeric()
True
>>> 'rOBERT dE nIRO'.swapcase()
'Robert De Niro'
>>> 'snakeyes'.upper().endswith('YES')
True

2.3.7 接口约定

接口约定:这里说的意思大概是,一个函数把另一个的函数的输出作为输入值。(类似shell中的pipe?

filter()函数
filter()函数用于过滤序列,过滤掉不符合条件的元素。

语法:filter(function, iterable),其中function为判断函数,iterable为可迭代对象。用法与map类似。

>>> def isodd(n):
    return n % 2 == 1
 
>>> newlist = filter(isodd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
[1, 3, 5, 7, 9]

生成器表达式
生成器表达式组合了filtermap的概念,并集成于单一的表达式中。

语法:<map expression> for <name> in <sequence expression> if <filter expression>

生成器表达式的求解结果值本身是个可迭代值。函数如tuple、sum、max和min可以将返回的对象作为参数。

>>> def acronym(name):
        return tuple(w[0] for w in name.split() if iscap(w))
>>> def sum_even_fibs(n):
        return sum(fib(k) for k in range(1, n+1) if fib(k) % 2 == 0)

reduce()函数
对序列中的元素从左到右依次调用二元函数,将序列归约为一个值。

语法:reduce(function, iterable[, initializer])

  • function – 函数,有两个参数
  • iterable – 可迭代对象
  • initializer – 可选,初始参数
>>> from operator import mul
>>> from functools import reduce
>>> reduce(mul, (1, 2, 3, 4, 5))
120

2.4 可变数据

2.4.1 局部状态

例子 – 从银行取钱

描述:创建withdraw函数,它将要取出的金额作为参数。如果账户中有足够的钱来取出,withdraw应该返回取钱之后的余额。否则,withdraw应该返回’Insufficient funds’。

withdraw求值两次,产生了不同的值,由此可知withdraw是非纯函数。

实现:

>>> def make_withdraw(balance):
        """Return a withdraw function that draws down balance with each call."""
        def withdraw(amount):
            nonlocal balance                 # Declare the name "balance" nonlocal
            if amount > balance:
                return 'Insufficient funds'
            balance = balance - amount       # Re-bind the existing balance name
            return balance
        return withdraw
>>> wd = make_withdraw(20)
>>> wd(5)
15
>>> wd(5)
10

这里值得注意的是nonlocal语句,我们先看一个程序:

def make_withdraw(balance):
    b = 6
    def withdraw(amount):
        if b > amount:
            return 'oh'
        b = b - amount
    return withdraw
        
wd = make_withdraw(20)
a = wd(5)

当执行到b = b - amount时,会报错UnboundLocalError: local variable 'b' referenced before assignment;但在执行if b > amount时并不会——这是因为withdraw作为make_withdraw的子函数,可以读取但无法改变母环境中的值。这也类似于make_withdraw能够读取但无法改变全局变量的值。

但我们可以使用nonlocal语句使子函数可以改变母函数环境中的值。无论什么时候我们修改了名称balance的绑定,绑定都会在balance所绑定的第一个帧中修改。

注意第二次调用wdbalance的值已经变成15。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fM0V5H6-1633510173217)(image/2021-09-20-18-55-27.png)]

但如果是make_withdraw再次调用,它会创建单独的帧,带有单独的balance绑定。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMnbgNbc-1633510173220)(image/2021-09-20-21-43-04.png)]
注意紫框内。

如果我们令wd2 = wd,则wd2wd绑定同一个函数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F9DmcGE9-1633510173222)(image/2021-09-21-14-52-31.png)]

>>> wd = make_withdraw(12)
>>> wd2 = wd
>>> wd2(1)
11
>>> wd(1)
10

正确分析带有非局部赋值代码的关键是,记住只有函数调用可以创建新的帧。赋值语句始终改变现有帧中的绑定。 这里,除非make_withdraw调用了两次,balance还是只有一个绑定。

2.4.4 列表

列表基本操作

>>> chinese_suits = ['coin', 'string', 'myriad']  # 构建列表
>>> suits = chinese_suits                         # 注意:这两个名称绑定到的是同一个列表,对一个列表施加的改变也会使另一个发生相同的改变

>>> suits.pop()             # 移除并返回最后一个元素
'myriad'
>>> suits.remove('string')  # Removes the first element that equals the argument

>>> suits.append('cup')              # Add an element to the end
>>> suits.extend(['sword', 'club'])  # Add all elements of a list to the end

>>> suits[2] = 'spade'  # Replace an element

>>> suits[0:2] = ['heart', 'diamond']  # Replace a slice
>>> suits
['heart', 'diamond', 'spade', 'club']

>>> chinese_suits  # This name co-refers with "suits" to the same list
['heart', 'diamond', 'spade', 'club']

>>> nest = list(suits)  # 注意:这里`nest`是一个与`suits`元素相同的列表,但它们绑定的是不同的列表,对一个施加改变不会影响另一个
>>> nest[0] = suits     # Create a nested list

>>> suits.insert(2, 'Joker')  # Insert an element at index 2, shifting the rest
>>> nest
[['heart', 'diamond', 'Joker', 'spade', 'club'], 'diamond', 'spade', 'club']

isis not
在Python中,isis not是两个比较运算符,用于测试两个对象的身份是否相同。如果两个对象的当前值相等,并且一个对象的改变始终会影响另一个,那么两个对象是同一个对象。身份是个比相等性更强的条件。

注:两个对象当且仅当在内存中的位置相同时为同一个对象。CPython的实现直接比较对象的地址来确定。

>>> suits is nest[0]
True
>>> suits is ['heart', 'diamond', 'spade', 'club']
False
>>> suits == ['heart', 'diamond', 'spade', 'club'] # `==`只检查内容的相等性
True

列表推导式
与生成器表达式的语法相似。

例如,unicodedata模块跟踪了Unicode字母表中每个字符的官方名称。我们可以查找与名称对应的字符,包含这些卡牌花色的字符。

>>> from unicodedata import lookup
>>> [lookup('WHITE ' + s.upper() + ' SUIT') for s in suits]
['♡', '♢', '♤', '♧']

列表的函数实现
注意:我们不能使用None来表示任何空的可变列表,因为两个空列表并不是相同的值(例如,向一个列表添加元素并不会添加到另一个),但是None is None。另一方面,两个不同的函数足以区分两个两个空列表,它们都将empty_rlist作为局部状态。

>>> def make_mutable_rlist():
        """Return a functional implementation of a mutable recursive list."""
        contents = empty_rlist
        def dispatch(message, value=None):
            nonlocal contents
            if message == 'len':
                return len_rlist(contents)
            elif message == 'getitem':
                return getitem_rlist(contents, value)
            elif message == 'push_first':
                contents = make_rlist(value, contents)
            elif message == 'pop_first':
                f = first(contents)
                contents = rest(contents)
                return f
            elif message == 'str':
                return str(contents)
        return dispatch

>>> def to_mutable_rlist(source):
        """Return a functional list with the same contents as source."""
        s = make_mutable_rlist()
        for element in reversed(source): #这里注意`reversed`,就是把原列表顺序倒过来迭代,比如当`source = [1, 2]时,这个`for`循环放出的第一个元素就是2
            s('push_first', element)
        return s

>>> s = to_mutable_rlist(suits)
>>> type(s)
<class 'function'>
>>> s('str')
"('heart', ('diamond', ('spade', ('club', None))))"
>>> s('pop_first')
'heart'
>>> s('str')
"('diamond', ('spade', ('club', None)))"

2.4.5 字典

字典的限制:

  • 字典的键不能是可变内建类型的对象。
  • 一个给定的键最多只能有一个值。

字典基本操作

>>> numerals = {'I': 1.0, 'V': 5, 'X': 10} #字典的构建

>>> numerals['X'] #返回key对应的value
10

>>> numerals['I'] = 1 #为key赋值value
>>> numerals['L'] = 50
>>> numerals
{'I': 1, 'X': 10, 'L': 50, 'V': 5}

>>> numerals.keys() # 返回keys
dict_keys(['I', 'X', 'L', 'V'])
>>> numerals.values() # 返回values
dict_values([1, 10, 50, 5])
>>> sum(numerals.values())
66
>>> type(numerals.keys()) #但可以通过`list`,`tuple`来转成别的类型
<class 'dict_keys'>

>>> dict([(3, 9), (4, 16), (5, 25)]) #转成`dict`
{3: 9, 4: 16, 5: 25}

>>> numerals.get('A', 0) #找`key`,找到了就返回value没找到就返回规定的数(默认为`None`,规定的数是括号里第二个数)
0
>>> numerals.get('V', 0)
5

>>> {x: x*x for x in range(3,6)} #字典推导式
{3: 9, 4: 16, 5: 25}

字典的函数实现

>>> def make_dict():
        """Return a functional implementation of a dictionary."""
        records = []
        def getitem(key):
            for k, v in records:
                if k == key:
                    return v
        def setitem(key, value):
            for item in records:
                if item[0] == key:
                    item[1] = value
                    return
            records.append([key, value])
        def dispatch(message, key=None, value=None):
            if message == 'getitem':
                return getitem(key)
            elif message == 'setitem':
                setitem(key, value)
            elif message == 'keys':
                return tuple(k for k, _ in records)
            elif message == 'values':
                return tuple(v for _, v in records)
        return dispatch

>>> d = make_dict()
>>> d('setitem', 3, 9)
>>> d('setitem', 4, 16)
>>> d('getitem', 3)
9
>>> d('getitem', 4)
16
>>> d('keys')
(3, 4)
>>> d('values')
(9, 16)

2.4.6 示例:传播约束

看不懂,救大命

2.5 面向对象编程(OOP)

2.5.1 对象和类

类的定义

class <name>(<base class>):
    <suite>

class语句的<suite>部分包含def语句,它们为该类的对象定义了新的方法。

首先来看最特殊的,用于实例化对象的方法,该方法在Python中的名称固定为__init__

>>> class Account(object):
        def __init__(self, account_holder):
            self.balance = 0
            self.holder = account_holder

# `self`绑定的是新创建的对象,如果放到下文看就是指a

>>> a = Account('Jim') # 这一步叫做类的实例化,创建了新的对象,它是`Account`类的实例

>>> a.balance # 使用点运算符访问对象的属性值
0
>>> a.holder
'Jim'

Account__init__方法有两个形参:第一个是self,绑定到新创建的Account对象上,即a。第二个参数,account_holder,在被调用来实例化的时候,绑定到传给该类的参数上,即令a.holder = 'Jim'。(其实__init__就是构造函数)

类的每个实例对象都有独特的身份,可以使用isis not来比较:

>>> a is a
True
>>> a is not b
True

注意,令c = a,则c和a绑定到同一个对象,赋值语句不会创建新的对象,只有类(比如Account)使用调用表达式被实例化的时候能创建新对象

>>> c = a
>>> c is a
True

方法

>>> class Account(object):
        def __init__(self, account_holder):
            self.balance = 0
            self.holder = account_holder
        def deposit(self, amount):
            self.balance = self.balance + amount
            return self.balance
        def withdraw(self, amount):
            if amount > self.balance:
                return 'Insufficient funds'
            self.balance = self.balance - amount
            return self.balance

每个方法的第一个参数都是self,它绑定到方法所调用的对象上,使方法能够操作并访问对象的状态。

>>> tom_account = Account('Tom')
>>> tom_account.deposit(100)
100
>>> tom_account.withdraw(90)
10
>>> tom_account.withdraw(90)
'Insufficient funds'
>>> tom_account.holder
'Tom'

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-reaQznNg-1633510173223)(image/2021-09-22-18-49-43.png)]

2.5.3 消息传递和点表达式

点表达式:类似tom_account.deposit的代码片段叫做点表达式。点表达式包含一个表达式,一个点和一个名称:

<expression> . <name>

内建的函数getattr也会按名称返回对象的属性。它是等价于点运算符的函数:

>>> getattr(tom_account, 'balance')
10

我们也可以使用hasattr测试对象是否拥有某个具名属性:

>>> hasattr(tom_account, 'deposit')
True

对象的属性包含所有实例属性,以及所有定义在类中的属性(包括方法)。方法是需要特别处理的类的属性。

方法和函数
当一个方法在对象上调用时,对象隐式地作为第一个参数传递给方法,即:对象会绑定到参数self上,这一点我们在之前的Python Online Tutor分析图中也看到过。

下面对Python中的函数和绑定方法进行区分:

>>> type(Account.deposit)
<class 'function'>
>>> type(tom_account.deposit)
<class 'method'>

可以看出,作为类的属性,方法只是个函数,但是作为实例属性,它是绑定方法。
前者:标准的二元函数,带有参数selfamount 后者:一元方法,名称self自动绑定到了名为tom_account的对象上

调用deposit函数的两种方式:

>>> Account.deposit(tom_account, 1001)  # The deposit function requires 2 arguments
1011

>>> tom_account.deposit(1000)           # The deposit method takes 1 argument
2011

类名称的编写:首字母大写
方法名称:遵循函数命名的管理,使用以下划线分隔的小写字母
Python中,如果属性名称以下划线开始,它只能在方法或类中访问,而不能被类的用户访问。

2.5.4 类属性

类属性(又名:类变量/静态变量):由class语句组中的赋值语句创建,位于任何方法定义之外。

例子:

>>> class Account(object):
        interest = 0.02            # A class attribute(这里定义了一个类属性,为利率)
        def __init__(self, account_holder):
            self.balance = 0
            self.holder = account_holder
        # Additional methods would be defined here

一旦改变类属性的赋值,则所有该类实例上的属性值都被改变。

点表达式:<expression> . <name> 求解点表达式的步骤:

  1. 求出点左边的,会产生点运算符的对象。
  2. 会和对象的实例属性匹配;如果该名称的属性存在,会返回它的值。
  3. 如果不存在于实例属性,那么会在类中查找,这会产生类的属性值。
  4. 这个值会被返回,如果它是个函数,则会返回绑定方法。

在这个求值过程中,实例属性在类的属性之前查找,就像局部名称具有高于全局的优先级。

赋值
点表达式的赋值既可以对实例对象进行,也可以对类进行。如果对象是个类,那么赋值会设置类属性。

对对象属性的赋值不能影响类的属性。

对某个实例对象的某个属性进行赋值以后,再对相应类中的相同属性赋值,类属性的赋值不会改变该实例对象属性的赋值。

>>> jim_account.interest = 0.08
>>> jim_account.interest
0.08
>>> tom_account.interest
0.04

>>> Account.interest = 0.05  # changing the class attribute
>>> tom_account.interest     # changes instances without like-named instance attributes
0.05
>>> jim_account.interest     # but the existing instance attribute is unaffected
0.08

2.5.5 继承

>>> class Account(object):
        """A bank account that has a non-negative balance."""
        interest = 0.02
        def __init__(self, account_holder):
            self.balance = 0
            self.holder = account_holder
        def deposit(self, amount):
            """Increase the account balance by amount and return the new balance."""
            self.balance = self.balance + amount
            return self.balance
        def withdraw(self, amount):
            """Decrease the account balance by amount and return the new balance."""
            if amount > self.balance:
                return 'Insufficient funds'
            self.balance = self.balance - amount
            return self.balance

>>> class CheckingAccount(Account): # 将基类放置到类名称后面的圆括号内来指定继承
        """A bank account that charges for withdrawals."""
        withdraw_charge = 1
        interest = 0.01
        def withdraw(self, amount):
            return Account.withdraw(self, amount + self.withdraw_charge)

CheckingAccountAccount的特化。
AccountCheckingAccount的基类/父类/超类。CheckingAccountAccount的子类/派生类。

子类中未指定的东西会自动假设和基类中相同。

>>> checking = CheckingAccount('Sam')
>>> checking.deposit(10)
10
>>> checking.withdraw(5)
4
>>> checking.interest
0.01

在类中查找名称的步骤(如执行到checking.deposit时,程序查找deposit这个名称):

  1. 如果类中有带有这个名称的属性,返回属性值。
  2. 否则,如果有基类的话,在基类中查找该名称。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U2HTtkNq-1633510173225)(image/2021-09-25-12-58-54.png)]

2.5.7 多重继承

Python支持子类从多个基类继承属性的概念,这是一种叫做多重继承的语言特性。

例子:

>>> class AsSeenOnTVAccount(CheckingAccount, SavingsAccount):
        def __init__(self, account_holder):
            self.holder = account_holder
            self.balance = 1           # A free dollar!
>>> such_a_deal = AsSeenOnTVAccount("John")
>>> such_a_deal.balance
1
>>> such_a_deal.deposit(20)            # $2 fee from SavingsAccount.deposit
19
>>> such_a_deal.withdraw(5)            # $1 fee from CheckingAccount.withdraw
13

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Suz5EWD9-1633510173226)(image/2021-09-25-19-37-56.png)]
这张图展示了AsSeenOnTVAccount类的继承图,箭头由子类指向基类。

Python按从左到右、从上到下的顺序解析、查找名称,比如在该例中就是:AsSeenOnTVAccount, CheckingAccount, SavingsAccount, Account, object.

Python 使用一种叫做 C3 Method Resolution Ordering 的递归算法来解析名称。任何类的方法解析顺序都使用所有类上的mro方法来查询。

>>> [c.__name__ for c in AsSeenOnTVAccount.mro()]
['AsSeenOnTVAccount', 'CheckingAccount', 'SavingsAccount', 'Account', 'obje

2.6 实现类和对象

2.6.1 实例

2.7 泛用方法

2.7.2 多重表示

>>> def add_complex(z1, z2):
        return ComplexRI(z1.real + z2.real, z1.imag + z2.imag)
>>> def mul_complex(z1, z2):
        return ComplexMA(z1.magnitude * z2.magnitude, z1.angle + z2.angle)

>>> from math import atan2
>>> class ComplexRI(object):
        def __init__(self, real, imag):
            self.real = real
            self.imag = imag
        @property
        def magnitude(self):
            return (self.real ** 2 + self.imag ** 2) ** 0.5
        @property
        def angle(self):
            return atan2(self.imag, self.real)
        def __repr__(self):
            return 'ComplexRI({0}, {1})'.format(self.real, self.imag)

>>> from math import sin, cos
>>> class ComplexMA(object):
        def __init__(self, magnitude, angle):
            self.magnitude = magnitude
            self.angle = angle
        @property
        def real(self):
            return self.magnitude * cos(self.angle)
        @property
        def imag(self):
            return self.magnitude * sin(self.angle)
        def __repr__(self):
            return 'ComplexMA({0}, {1})'.format(self.magnitude, self.angle)
>>> from math import pi
>>> add_complex(ComplexRI(1, 2), ComplexMA(2, pi/2))
ComplexRI(1.0000000000000002, 4.0)
>>> mul_complex(ComplexRI(0, 1), ComplexRI(0, 1))
ComplexMA(1.0, 3.141592653589793)

以下方法可以使程序在执行复数加法和乘法时直接使用+*运算符,以及operator模块中的addmul函数(即在自定义类中使用中缀符号):

>>> ComplexRI.__add__ = lambda self, other: add_complex(self, other)
>>> ComplexMA.__add__ = lambda self, other: add_complex(self, other)
>>> ComplexRI.__mul__ = lambda self, other: mul_complex(self, other)
>>> ComplexMA.__mul__ = lambda self, other: mul_complex(self, other)
>>> ComplexRI(1, 2) + ComplexMA(2, 0)
ComplexRI(3.0, 2.0)
>>> ComplexRI(0, 1) * ComplexRI(0, 1)
ComplexMA(1.0, 3.141592653589793)

求解含+运算符的表达式时,Python会先检查左操作数的__add__方法,再检查右操作数的__radd方法。如果二者之一被发现,这个方法把另一个操作数的值作为other调用。

2.7.3 泛用函数

本节的任务是实现实现不同数据类型间的运算。

  1. 重新定义有理数以及有理数间的运算
>>> from fractions import gcd
>>> class Rational(object):
        def __init__(self, numer, denom):
            g = gcd(numer, denom)
            self.numer = numer // g
            self.denom = denom // g
        def __repr__(self):
            return 'Rational({0}, {1})'.format(self.numer, self.denom)

>>> def add_rational(x, y):
        nx, dx = x.numer, x.denom
        ny, dy = y.numer, y.denom
        return Rational(nx * dy + ny * dx, dx * dy)
>>> def mul_rational(x, y):
        return Rational(x.numer * y.numer, x.denom * y.denom)
  1. 方法一:判断操作数的类型,再为每种运算都定义函数
>>> def iscomplex(z):
        return type(z) in (ComplexRI, ComplexMA) # `type`可以检测对象类型
>>> def isrational(z):
        return type(z) == Rational
>>> def add_complex_and_rational(z, r):
            return ComplexRI(z.real + r.numer/r.denom, z.imag)
>>> def add(z1, z2):
        """Add z1 and z2, which may be complex or rational."""
        if iscomplex(z1) and iscomplex(z2):
            return add_complex(z1, z2)
        elif iscomplex(z1) and isrational(z2):
            return add_complex_and_rational(z1, z2)
        elif isrational(z1) and iscomplex(z2):
            return add_complex_and_rational(z2, z1)
        else:
            return add_rational(z1, z2)
  1. 方法二:利用字典实现类型分发
>>> def type_tag(x):
        return type_tag.tags[type(x)]
>>> type_tag.tags = {ComplexRI: 'com', ComplexMA: 'com', Rational: 'rat'}

>>> def add(z1, z2):
        types = (type_tag(z1), type_tag(z2))
        return add.implementations[types](z1, z2)

>>> add.implementations = {}
>>> add.implementations[('com', 'com')] = add_complex
>>> add.implementations[('com', 'rat')] = add_complex_and_rational
>>> add.implementations[('rat', 'com')] = lambda x, y: add_complex_and_rational(y, x)
>>> add.implementations[('rat', 'rat')] = add_rational
>>> add(ComplexRI(1.5, 0), Rational(3, 2))
ComplexRI(3.0, 0)
>>> add(Rational(5, 3), Rational(1, 2))
Rational(13, 6)
  1. 方法三:数据导向编程——将任意运算符作用于任意类型,并且使用字典来储存多种组合的实现
>>> def apply(operator_name, x, y):
        tags = (type_tag(x), type_tag(y))
        key = (operator_name, tags)
        return apply.implementations[key](x, y)

>>> def mul_complex_and_rational(z, r):
        return ComplexMA(z.magnitude * r.numer / r.denom, z.angle)
>>> mul_rational_and_complex = lambda r, z: mul_complex_and_rational(z, r)
>>> apply.implementations = {('mul', ('com', 'com')): mul_complex,
                             ('mul', ('com', 'rat')): mul_complex_and_rational,
                             ('mul', ('rat', 'com')): mul_rational_and_complex,
                             ('mul', ('rat', 'rat')): mul_rational}

>>> adders = add.implementations.items()
>>> apply.implementations.update({('add', tags):fn for (tags, fn) in adders}) # 使用字典的`update`方法,从`add`中将加法实现添加到`apply`
>>> apply('add', ComplexRI(1.5, 0), Rational(3, 2))
ComplexRI(3.0, 0)
>>> apply('mul', Rational(1, 2), ComplexMA(10, 1))
ComplexMA(5.0, 1)
  1. 方法四:强制转换
>>> def rational_to_complex(x):
        return ComplexRI(x.numer/x.denom, 0)

>>> coercions = {('rat', 'com'): rational_to_complex}

>>> def coerce_apply(operator_name, x, y):
        tx, ty = type_tag(x), type_tag(y)
        if tx != ty:
            if (tx, ty) in coercions:
                tx, x = ty, coercions[(tx, ty)](x)
            elif (ty, tx) in coercions:
                ty, y = tx, coercions[(ty, tx)](y)
            else:
                return 'No coercion possible.'
        key = (operator_name, tx)
        return coerce_apply.implementations[key](x, y)

>>> coerce_apply.implementations = {('mul', 'com'): mul_complex,
                                    ('mul', 'rat'): mul_rational,
                                    ('add', 'com'): add_complex,
                                    ('add', 'rat'): add_rational}
>>> coerce_apply('add', ComplexRI(1.5, 0), Rational(3, 2))
ComplexRI(3.0, 0)
>>> coerce_apply('mul', Rational(1, 2), ComplexMA(10, 1))
ComplexMA(5.0, 1.0)

hw05

hw05地址

Q3 & Q4:
递归常常离不开分治。

  1. 要找出问题相同的部分
  2. 不要拘泥于细节,只要找到结束的边界条件,问题里相同的部分用递归实现即可